Upload folder using huggingface_hub
Browse files- README.md +169 -0
- configs/data/uma_conserving_data_task_energy.yaml +61 -0
- configs/data/uma_conserving_data_task_energy_force.yaml +86 -0
- configs/data/uma_conserving_data_task_energy_force_stress.yaml +107 -0
- configs/uma_sm_finetune_template.yaml +115 -0
- demo/_parse_config.py +253 -0
- demo/configs/_oc20_ef_4dcu_filled.yaml +303 -0
- demo/configs/oc20_ef_16dcu.yaml +270 -0
- demo/configs/oc20_ef_4dcu.yaml +303 -0
- demo/configs/oc20_ef_8dcu.yaml +263 -0
- demo/outputs/oc20_ef_4dcu_20260721_114458/config.yaml +303 -0
- demo/outputs/oc20_ef_4dcu_20260721_114458/hydra_config.yaml +288 -0
- demo/outputs/oc20_ef_4dcu_20260721_114458/models/pretrained_models.json +56 -0
- demo/run.sh +300 -0
- demo/templates/env_setup.sh +43 -0
- demo/templates/preflight_check.sh +53 -0
- demo/templates/slurm_header.template +9 -0
- inference/batch_inference_with_dataloader.py +50 -0
- inference/calculate_spin_gap.py +65 -0
- inference/generate_batches_at_runtime.py +49 -0
- inference/inference_heterogeneous_batches.py +50 -0
- inference/models/pretrained_models.json +56 -0
- inference/relax_adsorbate_on_slab.py +67 -0
- inference/relax_inorganic_crystal.py +58 -0
- inference/run_molecular_md.py +76 -0
- model/__init__.py +30 -0
- model/base.py +222 -0
- model/models/__init__.py +1 -0
- model/models/base.py +1 -0
- model/models/uma/__init__.py +1 -0
- model/models/uma/escn_md.py +1 -0
- model/models/uma/escn_moe.py +1 -0
- model/uma_escn_md.py +621 -0
- model/uma_escn_moe.py +184 -0
- models-json/pretrained_models.json +56 -0
- scripts/convert_model.py +151 -0
- scripts/create_finetune_dataset.py +186 -0
- scripts/create_uma_finetune_dataset.py +140 -0
- scripts/update_demo_config.py +48 -0
- train.py +600 -0
- weight/Jd.pt +3 -0
README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<strong>
|
| 3 |
+
<span style="font-size: 30px;">UMA</span>
|
| 4 |
+
</strong>
|
| 5 |
+
</p>
|
| 6 |
+
|
| 7 |
+
# Model Introduction
|
| 8 |
+
|
| 9 |
+
UMA (Universal Materials Interaction Model) is a universal machine-learning interatomic potential model for materials and catalysis systems, built on equivariant graph neural networks. It predicts energy and forces for atomic structures.
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Model Description
|
| 13 |
+
|
| 14 |
+
UMA is based on an equivariant graph neural network architecture and is trained on diverse materials and catalysis datasets such as OC20, OC22, OC25, OMat, OMOL, ODAC, and OMC. It performs energy and force prediction and structure optimization for catalytic adsorption, inorganic materials, molecular systems, MOFs, and related scenarios.
|
| 15 |
+
|
| 16 |
+
# Applicable Scenarios
|
| 17 |
+
|
| 18 |
+
| Scenario | Description |
|
| 19 |
+
| :---: | :---: |
|
| 20 |
+
| OC20 energy and force fine-tuning | Train the UMA model using the standard configuration to read OC20 fine-tuning data |
|
| 21 |
+
| OC22 oxide catalysis fine-tuning | Train the UMA model using the standard configuration to read OC22 fine-tuning data (1P2 only) |
|
| 22 |
+
| OC25 (electro-)catalysis fine-tuning | Train the UMA model using the standard configuration to read OC25 fine-tuning data (1P2 only) |
|
| 23 |
+
| OMat inorganic materials fine-tuning | Train the UMA model using the standard configuration to read OMat fine-tuning data |
|
| 24 |
+
| OMOL molecules + polymers fine-tuning | Train the UMA model using the standard configuration to read OMOL fine-tuning data |
|
| 25 |
+
| ODAC MOFs fine-tuning | Train the UMA model using the standard configuration to read ODAC fine-tuning data |
|
| 26 |
+
| OMC molecular crystals fine-tuning | Train the UMA model using the standard configuration to read OMC fine-tuning data |
|
| 27 |
+
| Training workflow pre-check | Check configuration, data paths, run scripts, and checkpoint placement |
|
| 28 |
+
| Catalytic adsorption system modeling | Refer to OC20/OC22/OC25 tasks for adsorption/catalytic surface system training and inference |
|
| 29 |
+
| Inference script reference | Use upstream inference examples for crystal relaxation, adsorption system relaxation, or molecular MD adaptation |
|
| 30 |
+
| Custom data migration | Convert ASE-readable structures into UMA fine-tuning data and replace the training and validation paths |
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# Usage Instructions
|
| 35 |
+
|
| 36 |
+
## 1. Using OneCode
|
| 37 |
+
|
| 38 |
+
You can try out intelligent one-click AI4S programming in the OneCode online environment:
|
| 39 |
+
|
| 40 |
+
[Try intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 41 |
+
|
| 42 |
+
## 2. Manual Installation and Usage
|
| 43 |
+
|
| 44 |
+
**Hardware Requirements**
|
| 45 |
+
|
| 46 |
+
- GPU or DCU is recommended for fine-tuning training.
|
| 47 |
+
- CPU can be used for configuration and data-path pre-checks; formal UMA training is not recommended on CPU.
|
| 48 |
+
- 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.
|
| 49 |
+
|
| 50 |
+
### Download the Model Package
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
modelscope download --model OneScience/UMA --local_dir ./UMA
|
| 54 |
+
cd UMA
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Install the Runtime Environment
|
| 58 |
+
|
| 59 |
+
**DCU Environment**
|
| 60 |
+
```bash
|
| 61 |
+
# Please activate DTK and CONDA first
|
| 62 |
+
conda create -n onescience311 python=3.11 -y
|
| 63 |
+
conda activate onescience311
|
| 64 |
+
# uv installation is also supported
|
| 65 |
+
pip install onescience[matchem-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
**GPU Environment**
|
| 69 |
+
|
| 70 |
+
```bash
|
| 71 |
+
# Please activate CONDA first
|
| 72 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 73 |
+
conda activate onescience311
|
| 74 |
+
# uv installation is also supported
|
| 75 |
+
pip install onescience[matchem-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
### Training Dataset Description
|
| 80 |
+
|
| 81 |
+
This repository does not include built-in training data. The following uses **OC20 fine-tuning** as an example; the procedures for OC22, OC25, OMat, OMOL, ODAC, OMC, and other tasks are the same—just replace `--uma-task` and the data path accordingly.
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
modelscope download --dataset OneScience/oc20 --local_dir ./data
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
**Data Format Conversion**
|
| 90 |
+
|
| 91 |
+
The downloaded raw data is usually in `.extxyz` format and must first be converted to ASE-lmdb format using `scripts/create_uma_finetune_dataset.py`, which also computes `elem_refs` and `normalizer_rmsd`. The script supports the following tasks:
|
| 92 |
+
|
| 93 |
+
| Task | Description |
|
| 94 |
+
| --- | --- |
|
| 95 |
+
| `oc20` | Catalysis (example) |
|
| 96 |
+
| `oc22` | Oxide catalysis (1P2 only) |
|
| 97 |
+
| `oc25` | (Electro-)catalysis (1P2 only) |
|
| 98 |
+
| `omat` | Inorganic materials |
|
| 99 |
+
| `omol` | Molecules + polymers |
|
| 100 |
+
| `odac` | MOFs |
|
| 101 |
+
| `omc` | Molecular crystals |
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
python scripts/create_uma_finetune_dataset.py \
|
| 106 |
+
--train-dir data/oc20/s2ef_200k_uncompressed \
|
| 107 |
+
--val-dir data/oc20/s2ef_val_id_uncompressed \
|
| 108 |
+
--uma-task oc20 \
|
| 109 |
+
--regression-tasks ef \
|
| 110 |
+
--output-dir data/oc20_finetune \
|
| 111 |
+
--num-workers 8
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
After conversion, the following are generated:
|
| 115 |
+
|
| 116 |
+
```text
|
| 117 |
+
data/oc20_finetune/
|
| 118 |
+
├── train/ # ASE-lmdb training data
|
| 119 |
+
├── val/ # ASE-lmdb validation data
|
| 120 |
+
└── data/ # Generated data configuration yaml
|
| 121 |
+
└── uma_conserving_data_task_energy_force.yaml
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
Then use `scripts/update_demo_config.py` to update the generated `elem_refs`, `normalizer_rmsd`, and data paths into the demo configuration file:
|
| 125 |
+
|
| 126 |
+
```bash
|
| 127 |
+
python scripts/update_demo_config.py --demo-config demo/configs/oc20_ef_4dcu.yaml
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
`demo/run.sh` automatically uses the repository root as `ONESCIENCE_DATASETS_DIR`, so relative paths in the configuration file will match automatically.
|
| 131 |
+
|
| 132 |
+
### Training Weights
|
| 133 |
+
|
| 134 |
+
This repository already includes the rotation basis file `weight/Jd.pt`. UMA pre-trained checkpoints (e.g., `uma-s-1p1_converted.pt`) need to be downloaded from the fairchem official repository, converted to UMA format, and placed at (upload coming soon):
|
| 135 |
+
|
| 136 |
+
```text
|
| 137 |
+
weight/uma-s-1p1_converted.pt
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
- fairchem official repository: https://github.com/facebookresearch/fairchem
|
| 141 |
+
|
| 142 |
+
`demo/run.sh` and the example scripts under `inference/` will automatically detect `weight/Jd.pt` and set `ONESCIENCE_UMA_JD_PATH`.
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
### Fine-tuning
|
| 146 |
+
|
| 147 |
+
```bash
|
| 148 |
+
bash demo/run.sh --config demo/configs/oc20_ef_4dcu.yaml
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
### Inference
|
| 152 |
+
```bash
|
| 153 |
+
python inference/run_molecular_md.py
|
| 154 |
+
```
|
| 155 |
+
You need to specify the pre-trained weights path yourself.
|
| 156 |
+
|
| 157 |
+
## OneScience Official Information
|
| 158 |
+
|
| 159 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 160 |
+
| --- | --- | --- |
|
| 161 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 162 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## Citation and License
|
| 167 |
+
|
| 168 |
+
- The UMA example code comes from the matchem example implementation in the OneScience project and references the upstream fairchem project (https://github.com/facebookresearch/fairchem). The upstream fairchem repository software is released under the [MIT License](https://fair-chem.github.io/core/install.html#license); fairchem model checkpoints and datasets may carry their own separate licenses, so please follow the corresponding instructions when using them.
|
| 169 |
+
- If you use UMA fine-tuning or inference results in scientific research, we recommend citing the UMA/related universal materials interaction model methods, fairchem/OneScience project information, and the sources of the datasets actually used.
|
configs/data/uma_conserving_data_task_energy.yaml
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset_name: ??
|
| 2 |
+
elem_refs: ??
|
| 3 |
+
normalizer_rmsd: ??
|
| 4 |
+
|
| 5 |
+
train_dataset:
|
| 6 |
+
splits:
|
| 7 |
+
train:
|
| 8 |
+
src: ??
|
| 9 |
+
format: ase_db
|
| 10 |
+
transforms:
|
| 11 |
+
common_transform:
|
| 12 |
+
dataset_name: ${data.dataset_name}
|
| 13 |
+
stress_reshape_transform:
|
| 14 |
+
dataset_name: ${data.dataset_name}
|
| 15 |
+
|
| 16 |
+
val_dataset:
|
| 17 |
+
splits:
|
| 18 |
+
val:
|
| 19 |
+
src: ??
|
| 20 |
+
format: ase_db
|
| 21 |
+
transforms:
|
| 22 |
+
common_transform:
|
| 23 |
+
dataset_name: ${data.dataset_name}
|
| 24 |
+
stress_reshape_transform:
|
| 25 |
+
dataset_name: ${data.dataset_name}
|
| 26 |
+
|
| 27 |
+
regress_stress: False
|
| 28 |
+
pass_through_head_outputs: False
|
| 29 |
+
|
| 30 |
+
heads:
|
| 31 |
+
energy:
|
| 32 |
+
module: onescience.modules.head.uma_head.MLP_Energy_Head
|
| 33 |
+
|
| 34 |
+
tasks_list:
|
| 35 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 36 |
+
name: energy
|
| 37 |
+
level: system
|
| 38 |
+
property: energy
|
| 39 |
+
loss_fn:
|
| 40 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 41 |
+
loss_fn:
|
| 42 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 43 |
+
coefficient: 20
|
| 44 |
+
out_spec:
|
| 45 |
+
dim: [1]
|
| 46 |
+
dtype: float32
|
| 47 |
+
normalizer:
|
| 48 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 49 |
+
mean: 0.0
|
| 50 |
+
rmsd: ${data.normalizer_rmsd}
|
| 51 |
+
element_references:
|
| 52 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 53 |
+
element_references:
|
| 54 |
+
_target_: torch.DoubleTensor
|
| 55 |
+
_args_:
|
| 56 |
+
- ${data.elem_refs}
|
| 57 |
+
datasets:
|
| 58 |
+
- ${data.dataset_name}
|
| 59 |
+
metrics:
|
| 60 |
+
- mae
|
| 61 |
+
- per_atom_mae
|
configs/data/uma_conserving_data_task_energy_force.yaml
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset_name: ??
|
| 2 |
+
elem_refs: ??
|
| 3 |
+
normalizer_rmsd: ??
|
| 4 |
+
|
| 5 |
+
train_dataset:
|
| 6 |
+
splits:
|
| 7 |
+
train:
|
| 8 |
+
src: ??
|
| 9 |
+
format: ase_db
|
| 10 |
+
transforms:
|
| 11 |
+
common_transform:
|
| 12 |
+
dataset_name: ${data.dataset_name}
|
| 13 |
+
stress_reshape_transform:
|
| 14 |
+
dataset_name: ${data.dataset_name}
|
| 15 |
+
|
| 16 |
+
val_dataset:
|
| 17 |
+
splits:
|
| 18 |
+
val:
|
| 19 |
+
src: ??
|
| 20 |
+
format: ase_db
|
| 21 |
+
transforms:
|
| 22 |
+
common_transform:
|
| 23 |
+
dataset_name: ${data.dataset_name}
|
| 24 |
+
stress_reshape_transform:
|
| 25 |
+
dataset_name: ${data.dataset_name}
|
| 26 |
+
|
| 27 |
+
regress_stress: False
|
| 28 |
+
pass_through_head_outputs: True
|
| 29 |
+
|
| 30 |
+
heads:
|
| 31 |
+
efs:
|
| 32 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 33 |
+
|
| 34 |
+
tasks_list:
|
| 35 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 36 |
+
name: energy
|
| 37 |
+
level: system
|
| 38 |
+
property: energy
|
| 39 |
+
loss_fn:
|
| 40 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 41 |
+
loss_fn:
|
| 42 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 43 |
+
coefficient: 20
|
| 44 |
+
out_spec:
|
| 45 |
+
dim: [1]
|
| 46 |
+
dtype: float32
|
| 47 |
+
normalizer:
|
| 48 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 49 |
+
mean: 0.0
|
| 50 |
+
rmsd: ${data.normalizer_rmsd}
|
| 51 |
+
element_references:
|
| 52 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 53 |
+
element_references:
|
| 54 |
+
_target_: torch.DoubleTensor
|
| 55 |
+
_args_:
|
| 56 |
+
- ${data.elem_refs}
|
| 57 |
+
datasets:
|
| 58 |
+
- ${data.dataset_name}
|
| 59 |
+
metrics:
|
| 60 |
+
- mae
|
| 61 |
+
- per_atom_mae
|
| 62 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 63 |
+
name: forces
|
| 64 |
+
level: atom
|
| 65 |
+
property: forces
|
| 66 |
+
train_on_free_atoms: True
|
| 67 |
+
eval_on_free_atoms: True
|
| 68 |
+
loss_fn:
|
| 69 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 70 |
+
loss_fn:
|
| 71 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 72 |
+
reduction: mean
|
| 73 |
+
coefficient: 2
|
| 74 |
+
out_spec:
|
| 75 |
+
dim: [3]
|
| 76 |
+
dtype: float32
|
| 77 |
+
normalizer:
|
| 78 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 79 |
+
mean: 0.0
|
| 80 |
+
rmsd: ${data.normalizer_rmsd}
|
| 81 |
+
datasets:
|
| 82 |
+
- ${data.dataset_name}
|
| 83 |
+
metrics:
|
| 84 |
+
- mae
|
| 85 |
+
- cosine_similarity
|
| 86 |
+
- magnitude_error
|
configs/data/uma_conserving_data_task_energy_force_stress.yaml
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset_name: ??
|
| 2 |
+
elem_refs: ??
|
| 3 |
+
normalizer_rmsd: ??
|
| 4 |
+
|
| 5 |
+
train_dataset:
|
| 6 |
+
splits:
|
| 7 |
+
train:
|
| 8 |
+
src: ??
|
| 9 |
+
format: ase_db
|
| 10 |
+
transforms:
|
| 11 |
+
common_transform:
|
| 12 |
+
dataset_name: ${data.dataset_name}
|
| 13 |
+
stress_reshape_transform:
|
| 14 |
+
dataset_name: ${data.dataset_name}
|
| 15 |
+
|
| 16 |
+
val_dataset:
|
| 17 |
+
splits:
|
| 18 |
+
val:
|
| 19 |
+
src: ??
|
| 20 |
+
format: ase_db
|
| 21 |
+
transforms:
|
| 22 |
+
common_transform:
|
| 23 |
+
dataset_name: ${data.dataset_name}
|
| 24 |
+
stress_reshape_transform:
|
| 25 |
+
dataset_name: ${data.dataset_name}
|
| 26 |
+
|
| 27 |
+
regress_stress: True
|
| 28 |
+
pass_through_head_outputs: True
|
| 29 |
+
|
| 30 |
+
heads:
|
| 31 |
+
efs:
|
| 32 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 33 |
+
|
| 34 |
+
tasks_list:
|
| 35 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 36 |
+
name: energy
|
| 37 |
+
level: system
|
| 38 |
+
property: energy
|
| 39 |
+
loss_fn:
|
| 40 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 41 |
+
loss_fn:
|
| 42 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 43 |
+
coefficient: 20
|
| 44 |
+
out_spec:
|
| 45 |
+
dim: [1]
|
| 46 |
+
dtype: float32
|
| 47 |
+
normalizer:
|
| 48 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 49 |
+
mean: 0.0
|
| 50 |
+
rmsd: ${data.normalizer_rmsd}
|
| 51 |
+
element_references:
|
| 52 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 53 |
+
element_references:
|
| 54 |
+
_target_: torch.DoubleTensor
|
| 55 |
+
_args_:
|
| 56 |
+
- ${data.elem_refs}
|
| 57 |
+
datasets:
|
| 58 |
+
- ${data.dataset_name}
|
| 59 |
+
metrics:
|
| 60 |
+
- mae
|
| 61 |
+
- per_atom_mae
|
| 62 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 63 |
+
name: forces
|
| 64 |
+
level: atom
|
| 65 |
+
property: forces
|
| 66 |
+
train_on_free_atoms: True
|
| 67 |
+
eval_on_free_atoms: True
|
| 68 |
+
loss_fn:
|
| 69 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 70 |
+
loss_fn:
|
| 71 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 72 |
+
reduction: mean
|
| 73 |
+
coefficient: 2
|
| 74 |
+
out_spec:
|
| 75 |
+
dim: [3]
|
| 76 |
+
dtype: float32
|
| 77 |
+
normalizer:
|
| 78 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 79 |
+
mean: 0.0
|
| 80 |
+
rmsd: ${data.normalizer_rmsd}
|
| 81 |
+
datasets:
|
| 82 |
+
- ${data.dataset_name}
|
| 83 |
+
metrics:
|
| 84 |
+
- mae
|
| 85 |
+
- cosine_similarity
|
| 86 |
+
- magnitude_error
|
| 87 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 88 |
+
name: stress
|
| 89 |
+
level: system
|
| 90 |
+
property: stress
|
| 91 |
+
loss_fn:
|
| 92 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 93 |
+
loss_fn:
|
| 94 |
+
_target_: onescience.modules.loss.uma_loss.MAELoss
|
| 95 |
+
reduction: mean
|
| 96 |
+
coefficient: 1
|
| 97 |
+
out_spec:
|
| 98 |
+
dim: [1, 9]
|
| 99 |
+
dtype: float32
|
| 100 |
+
normalizer:
|
| 101 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 102 |
+
mean: 0.0
|
| 103 |
+
rmsd: ${data.normalizer_rmsd}
|
| 104 |
+
datasets:
|
| 105 |
+
- ${data.dataset_name}
|
| 106 |
+
metrics:
|
| 107 |
+
- mae
|
configs/uma_sm_finetune_template.yaml
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- data: ??
|
| 3 |
+
- _self_
|
| 4 |
+
|
| 5 |
+
job:
|
| 6 |
+
device_type: CUDA
|
| 7 |
+
scheduler:
|
| 8 |
+
mode: LOCAL
|
| 9 |
+
ranks_per_node: 1
|
| 10 |
+
num_nodes: 1
|
| 11 |
+
debug: True
|
| 12 |
+
run_dir: ./uma_finetune_runs/
|
| 13 |
+
run_name: uma_finetune
|
| 14 |
+
logger:
|
| 15 |
+
_target_: onescience.utils.uma.common.logger.WandBSingletonLogger.init_wandb
|
| 16 |
+
_partial_: true
|
| 17 |
+
entity: example
|
| 18 |
+
project: uma_finetune
|
| 19 |
+
|
| 20 |
+
#base_model_name: uma-s-1p1
|
| 21 |
+
# 300 is effectively infinite neighbors, but if you are memory strained, using 100 is usually just as good
|
| 22 |
+
max_neighbors: 300
|
| 23 |
+
# choose to either run for integer number of epochs or steps, only 1 can be step, the other must be null
|
| 24 |
+
epochs: 1
|
| 25 |
+
steps: null
|
| 26 |
+
# customize batch size depending on your system
|
| 27 |
+
batch_size: 2
|
| 28 |
+
lr: 4e-4
|
| 29 |
+
weight_decay: 1e-3
|
| 30 |
+
evaluate_every_n_steps: 100
|
| 31 |
+
checkpoint_every_n_steps: 1000
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
train_dataset:
|
| 35 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 36 |
+
dataset_configs:
|
| 37 |
+
finetune_dataset: ${data.train_dataset}
|
| 38 |
+
combined_dataset_config: { sampling: {type: temperature, temperature: 1.0} }
|
| 39 |
+
|
| 40 |
+
train_dataloader:
|
| 41 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 42 |
+
dataset: ${train_dataset}
|
| 43 |
+
batch_sampler_fn:
|
| 44 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 45 |
+
_partial_: True
|
| 46 |
+
batch_size: ${batch_size}
|
| 47 |
+
shuffle: True
|
| 48 |
+
seed: 0
|
| 49 |
+
num_workers: 0
|
| 50 |
+
collate_fn:
|
| 51 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 52 |
+
tasks: ${data.tasks_list}
|
| 53 |
+
exclude_keys: []
|
| 54 |
+
val_dataset:
|
| 55 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 56 |
+
dataset_configs:
|
| 57 |
+
finetune_dataset: ${data.val_dataset}
|
| 58 |
+
combined_dataset_config: { sampling: {type: temperature, temperature: 1.0} }
|
| 59 |
+
|
| 60 |
+
eval_dataloader:
|
| 61 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 62 |
+
dataset: ${val_dataset}
|
| 63 |
+
batch_sampler_fn:
|
| 64 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 65 |
+
_partial_: True
|
| 66 |
+
batch_size: ${batch_size}
|
| 67 |
+
shuffle: False
|
| 68 |
+
seed: 0
|
| 69 |
+
num_workers: 0
|
| 70 |
+
collate_fn:
|
| 71 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 72 |
+
tasks: ${data.tasks_list}
|
| 73 |
+
exclude_keys: []
|
| 74 |
+
runner:
|
| 75 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 76 |
+
train_dataloader: ${train_dataloader}
|
| 77 |
+
eval_dataloader: ${eval_dataloader}
|
| 78 |
+
train_eval_unit:
|
| 79 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 80 |
+
job_config: ${job}
|
| 81 |
+
tasks: ${data.tasks_list}
|
| 82 |
+
model:
|
| 83 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 84 |
+
checkpoint_location: "${oc.env:ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt"
|
| 85 |
+
overrides:
|
| 86 |
+
backbone:
|
| 87 |
+
otf_graph: True
|
| 88 |
+
max_neighbors: ${max_neighbors}
|
| 89 |
+
regress_stress: ${data.regress_stress}
|
| 90 |
+
always_use_pbc: False
|
| 91 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 92 |
+
heads: ${data.heads}
|
| 93 |
+
optimizer_fn:
|
| 94 |
+
_target_: torch.optim.AdamW
|
| 95 |
+
_partial_: true
|
| 96 |
+
lr: ${lr}
|
| 97 |
+
weight_decay: ${weight_decay}
|
| 98 |
+
cosine_lr_scheduler_fn:
|
| 99 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 100 |
+
_partial_: true
|
| 101 |
+
warmup_factor: 0.2
|
| 102 |
+
warmup_epochs: 0.01
|
| 103 |
+
lr_min_factor: 0.01
|
| 104 |
+
epochs: ${epochs}
|
| 105 |
+
steps: ${steps}
|
| 106 |
+
print_every: 10
|
| 107 |
+
clip_grad_norm: 100
|
| 108 |
+
max_epochs: ${epochs}
|
| 109 |
+
max_steps: ${steps}
|
| 110 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 111 |
+
callbacks:
|
| 112 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 113 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 114 |
+
max_saved_checkpoints: 5
|
| 115 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/_parse_config.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""UMA demo YAML 配置解析器 - 把 YAML 转换为训练启动命令与相关变量。
|
| 3 |
+
|
| 4 |
+
用法:
|
| 5 |
+
python _parse_config.py <config.yaml> <action> [hydra_config_path]
|
| 6 |
+
|
| 7 |
+
action:
|
| 8 |
+
name 打印实验名称
|
| 9 |
+
command 打印训练启动命令 (python/torchrun/srun 风格)
|
| 10 |
+
需要额外传入 hydra_config_path,作为 train.py -c 的目标
|
| 11 |
+
env 打印环境变量 export 语句
|
| 12 |
+
env-args 打印 env_setup.sh 的参数 (conda_env module1 module2 ...)
|
| 13 |
+
data-files 打印 preflight_check.sh 要检查的路径
|
| 14 |
+
(checkpoint_location + data.train_dataset.splits.*.src + data.val_dataset.splits.*.src)
|
| 15 |
+
slurm 打印 SLURM 配置变量
|
| 16 |
+
hydra-config 把 YAML 剥离 demo meta 后的部分(纯 hydra 内容)打印到 stdout
|
| 17 |
+
run.sh 会把它重定向到 outputs/xxx_ts/hydra_config.yaml
|
| 18 |
+
|
| 19 |
+
顶层 meta 键(只给 demo 工具用,不进 hydra):
|
| 20 |
+
name, description, launch, env, slurm, nccl
|
| 21 |
+
|
| 22 |
+
其余顶层键(data, job, runner, reducer, train_dataset, ...)作为 hydra 内容。
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
import sys
|
| 27 |
+
|
| 28 |
+
import yaml
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# 这些顶层键只给 demo 工具用,在写 hydra_config 时会被剥离
|
| 32 |
+
META_KEYS = {"name", "description", "launch", "env", "slurm", "nccl"}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def load_config(path):
|
| 36 |
+
with open(path) as f:
|
| 37 |
+
return yaml.safe_load(f)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def get_train_py_path(config_path):
|
| 41 |
+
"""根据 demo/configs/xxx.yaml 的位置推算 UMA/train.py 路径。"""
|
| 42 |
+
# config_path: .../UMA/demo/configs/xxx.yaml
|
| 43 |
+
demo_dir = os.path.dirname(os.path.dirname(os.path.abspath(config_path)))
|
| 44 |
+
uma_dir = os.path.dirname(demo_dir)
|
| 45 |
+
return os.path.join(uma_dir, "train.py")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# -----------------------------------------------------------------------------
|
| 49 |
+
# action 实现
|
| 50 |
+
# -----------------------------------------------------------------------------
|
| 51 |
+
|
| 52 |
+
def print_name(cfg):
|
| 53 |
+
print(cfg.get("name", "uma_train"))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def print_command(cfg, config_path, hydra_config_path):
|
| 57 |
+
"""拼 python/torchrun/srun train.py -c <hydra_config_path>。"""
|
| 58 |
+
if not hydra_config_path:
|
| 59 |
+
print(
|
| 60 |
+
"[ERROR] command 动作需要提供 hydra_config_path 作为第三个参数",
|
| 61 |
+
file=sys.stderr,
|
| 62 |
+
)
|
| 63 |
+
sys.exit(1)
|
| 64 |
+
|
| 65 |
+
launch = cfg.get("launch", {}) or {}
|
| 66 |
+
num_nodes = launch.get("num_nodes", 1)
|
| 67 |
+
num_gpus = launch.get("num_gpus", 1)
|
| 68 |
+
launcher = launch.get("launcher", "python")
|
| 69 |
+
|
| 70 |
+
train_py = get_train_py_path(config_path)
|
| 71 |
+
|
| 72 |
+
if num_nodes > 1:
|
| 73 |
+
# 多节点: sbatch 内会用 srun 包裹 torchrun, 命令本体是 torchrun + train.py
|
| 74 |
+
# 注意: master_addr/master_port/node_rank 在 run.sh 生成的 submit.sh 里
|
| 75 |
+
# 基于 $SLURM_* 变量导出, 这里占位用 ${MASTER_ADDR} 等 shell 变量
|
| 76 |
+
print(
|
| 77 |
+
"torchrun \\\n"
|
| 78 |
+
f" --nnodes={num_nodes} \\\n"
|
| 79 |
+
" --node_rank=${SLURM_NODEID} \\\n"
|
| 80 |
+
f" --nproc_per_node={num_gpus} \\\n"
|
| 81 |
+
" --rdzv_id=${SLURM_JOB_ID} \\\n"
|
| 82 |
+
" --rdzv_backend=c10d \\\n"
|
| 83 |
+
" --rdzv_endpoint=${MASTER_ADDR}:${MASTER_PORT} \\\n"
|
| 84 |
+
f" {train_py} \\\n"
|
| 85 |
+
f" -c {hydra_config_path}"
|
| 86 |
+
)
|
| 87 |
+
elif launcher == "torchrun" or num_gpus > 1:
|
| 88 |
+
# 单节点多卡: torchrun
|
| 89 |
+
print(
|
| 90 |
+
"torchrun \\\n"
|
| 91 |
+
" --nnodes=1 \\\n"
|
| 92 |
+
f" --nproc_per_node={num_gpus} \\\n"
|
| 93 |
+
f" {train_py} \\\n"
|
| 94 |
+
f" -c {hydra_config_path}"
|
| 95 |
+
)
|
| 96 |
+
else:
|
| 97 |
+
# 单卡: 直接 python
|
| 98 |
+
print(f"python {train_py} -c {hydra_config_path}")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def print_env(cfg):
|
| 102 |
+
"""打印环境变量 export 语句。"""
|
| 103 |
+
launch = cfg.get("launch", {}) or {}
|
| 104 |
+
num_nodes = launch.get("num_nodes", 1)
|
| 105 |
+
num_gpus = launch.get("num_gpus", 1)
|
| 106 |
+
|
| 107 |
+
omp = launch.get("omp_num_threads", 1)
|
| 108 |
+
print(f"export OMP_NUM_THREADS={omp}")
|
| 109 |
+
|
| 110 |
+
if num_gpus > 1:
|
| 111 |
+
devices = ",".join(str(i) for i in range(num_gpus))
|
| 112 |
+
print(f"export HIP_VISIBLE_DEVICES={devices}")
|
| 113 |
+
elif num_gpus == 1:
|
| 114 |
+
print("export HIP_VISIBLE_DEVICES=0")
|
| 115 |
+
|
| 116 |
+
# 多节点 NCCL 配置
|
| 117 |
+
if num_nodes > 1:
|
| 118 |
+
nccl = cfg.get("nccl", {}) or {}
|
| 119 |
+
print("export HSA_FORCE_FINE_GRAIN_PCIE=1")
|
| 120 |
+
if nccl.get("socket_ifname"):
|
| 121 |
+
print(f"export NCCL_SOCKET_IFNAME={nccl['socket_ifname']}")
|
| 122 |
+
if nccl.get("ib_hca"):
|
| 123 |
+
print(f"export NCCL_IB_HCA={nccl['ib_hca']}")
|
| 124 |
+
if nccl.get("proto"):
|
| 125 |
+
print(f"export NCCL_PROTO={nccl['proto']}")
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def print_env_args(cfg):
|
| 129 |
+
"""打印 env_setup.sh 的参数: conda_env module1 module2 ..."""
|
| 130 |
+
env = cfg.get("env", {}) or {}
|
| 131 |
+
conda_env = env.get("conda_env", "chem")
|
| 132 |
+
modules = env.get("modules", []) or []
|
| 133 |
+
parts = [conda_env] + list(modules)
|
| 134 |
+
print(" ".join(parts))
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _dig_src_paths(node, out):
|
| 138 |
+
"""递归找出 splits 下所有 src 字段。"""
|
| 139 |
+
if isinstance(node, dict):
|
| 140 |
+
if "splits" in node and isinstance(node["splits"], dict):
|
| 141 |
+
for split_name, split_val in node["splits"].items():
|
| 142 |
+
if isinstance(split_val, dict) and "src" in split_val:
|
| 143 |
+
out.append(split_val["src"])
|
| 144 |
+
for v in node.values():
|
| 145 |
+
_dig_src_paths(v, out)
|
| 146 |
+
elif isinstance(node, list):
|
| 147 |
+
for item in node:
|
| 148 |
+
_dig_src_paths(item, out)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _dig_checkpoint_locations(node, out):
|
| 152 |
+
"""递归找出 checkpoint_location 字段。"""
|
| 153 |
+
if isinstance(node, dict):
|
| 154 |
+
if "checkpoint_location" in node and isinstance(
|
| 155 |
+
node["checkpoint_location"], str
|
| 156 |
+
):
|
| 157 |
+
out.append(node["checkpoint_location"])
|
| 158 |
+
for v in node.values():
|
| 159 |
+
_dig_checkpoint_locations(v, out)
|
| 160 |
+
elif isinstance(node, list):
|
| 161 |
+
for item in node:
|
| 162 |
+
_dig_checkpoint_locations(item, out)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def print_data_files(cfg):
|
| 166 |
+
"""打印 preflight 要检查的路径: checkpoint + 数据 src。"""
|
| 167 |
+
paths = []
|
| 168 |
+
data = cfg.get("data", {})
|
| 169 |
+
_dig_src_paths(data, paths)
|
| 170 |
+
_dig_checkpoint_locations(cfg, paths)
|
| 171 |
+
# 去重保持顺序
|
| 172 |
+
seen = set()
|
| 173 |
+
for p in paths:
|
| 174 |
+
if p and p not in seen and not str(p).startswith("??"):
|
| 175 |
+
seen.add(p)
|
| 176 |
+
print(p)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def print_slurm(cfg):
|
| 180 |
+
"""打印 SLURM 配置变量。"""
|
| 181 |
+
launch = cfg.get("launch", {}) or {}
|
| 182 |
+
slurm = cfg.get("slurm", {}) or {}
|
| 183 |
+
num_nodes = launch.get("num_nodes", 1)
|
| 184 |
+
num_gpus = launch.get("num_gpus", 1)
|
| 185 |
+
|
| 186 |
+
name = cfg.get("name", "uma_train")
|
| 187 |
+
partition = slurm.get("partition", "k100ai")
|
| 188 |
+
time_limit = slurm.get("time", "12:00:00")
|
| 189 |
+
cpus = slurm.get("cpus_per_task", 128)
|
| 190 |
+
|
| 191 |
+
# UMA 的多节点策略: 每个节点 1 个 srun task, 由 torchrun 在该节点内再 spawn
|
| 192 |
+
# nproc_per_node=num_gpus 个进程. 因此无论单/多节点 NTASKS_PER_NODE 都是 1.
|
| 193 |
+
ntasks = 1
|
| 194 |
+
cpus_per_task = cpus
|
| 195 |
+
_ = num_gpus # 保留 num_gpus 以便 GPUS_PER_NODE 正确填写
|
| 196 |
+
|
| 197 |
+
print(f"JOB_NAME={name}")
|
| 198 |
+
print(f"PARTITION={partition}")
|
| 199 |
+
print(f"NODES={num_nodes}")
|
| 200 |
+
print(f"NTASKS_PER_NODE={ntasks}")
|
| 201 |
+
print(f"CPUS_PER_TASK={cpus_per_task}")
|
| 202 |
+
print(f"GPUS_PER_NODE={num_gpus}")
|
| 203 |
+
print(f"TIME={time_limit}")
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def print_hydra_config(cfg):
|
| 207 |
+
"""把 cfg 剥离 META_KEYS 后的部分用 YAML 输出。"""
|
| 208 |
+
hydra_only = {k: v for k, v in cfg.items() if k not in META_KEYS}
|
| 209 |
+
# sort_keys=False 保留原顺序; default_flow_style=False 强制 block 风格
|
| 210 |
+
yaml.safe_dump(
|
| 211 |
+
hydra_only,
|
| 212 |
+
sys.stdout,
|
| 213 |
+
sort_keys=False,
|
| 214 |
+
default_flow_style=False,
|
| 215 |
+
allow_unicode=True,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
# -----------------------------------------------------------------------------
|
| 220 |
+
# main
|
| 221 |
+
# -----------------------------------------------------------------------------
|
| 222 |
+
|
| 223 |
+
def main():
|
| 224 |
+
if len(sys.argv) < 3:
|
| 225 |
+
print(__doc__, file=sys.stderr)
|
| 226 |
+
sys.exit(1)
|
| 227 |
+
|
| 228 |
+
config_path = sys.argv[1]
|
| 229 |
+
action = sys.argv[2]
|
| 230 |
+
hydra_config_path = sys.argv[3] if len(sys.argv) >= 4 else None
|
| 231 |
+
|
| 232 |
+
cfg = load_config(config_path)
|
| 233 |
+
|
| 234 |
+
actions = {
|
| 235 |
+
"name": lambda: print_name(cfg),
|
| 236 |
+
"command": lambda: print_command(cfg, config_path, hydra_config_path),
|
| 237 |
+
"env": lambda: print_env(cfg),
|
| 238 |
+
"env-args": lambda: print_env_args(cfg),
|
| 239 |
+
"data-files": lambda: print_data_files(cfg),
|
| 240 |
+
"slurm": lambda: print_slurm(cfg),
|
| 241 |
+
"hydra-config": lambda: print_hydra_config(cfg),
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
fn = actions.get(action)
|
| 245 |
+
if fn is None:
|
| 246 |
+
print(f"未知 action: {action}", file=sys.stderr)
|
| 247 |
+
print(f"可用: {', '.join(actions.keys())}", file=sys.stderr)
|
| 248 |
+
sys.exit(1)
|
| 249 |
+
fn()
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
if __name__ == "__main__":
|
| 253 |
+
main()
|
demo/configs/_oc20_ef_4dcu_filled.yaml
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: oc20_ef_4dcu
|
| 2 |
+
description: UMA OC20 finetune - 1 node x 4 DCU (energy+forces)
|
| 3 |
+
launch:
|
| 4 |
+
launcher: torchrun
|
| 5 |
+
num_nodes: 1
|
| 6 |
+
num_gpus: 4
|
| 7 |
+
omp_num_threads: 1
|
| 8 |
+
env:
|
| 9 |
+
conda_env: chem
|
| 10 |
+
modules:
|
| 11 |
+
- sghpc-mpi-gcc/26.3
|
| 12 |
+
slurm:
|
| 13 |
+
partition: k100ai
|
| 14 |
+
time: '12:00:00'
|
| 15 |
+
cpus_per_task: 128
|
| 16 |
+
job:
|
| 17 |
+
device_type: CUDA
|
| 18 |
+
scheduler:
|
| 19 |
+
mode: LOCAL
|
| 20 |
+
ranks_per_node: 4
|
| 21 |
+
num_nodes: 1
|
| 22 |
+
debug: true
|
| 23 |
+
run_dir: ./uma_finetune_runs/
|
| 24 |
+
run_name: oc20_ef_4dcu_smoke
|
| 25 |
+
logger: null
|
| 26 |
+
max_neighbors: 300
|
| 27 |
+
epochs: 1
|
| 28 |
+
steps: 20
|
| 29 |
+
batch_size: 2
|
| 30 |
+
lr: 0.0004
|
| 31 |
+
weight_decay: 0.001
|
| 32 |
+
evaluate_every_n_steps: 100
|
| 33 |
+
checkpoint_every_n_steps: 1000
|
| 34 |
+
data:
|
| 35 |
+
dataset_name: oc20
|
| 36 |
+
elem_refs:
|
| 37 |
+
- -2.338112300549499e-17
|
| 38 |
+
- -3.397406033745951
|
| 39 |
+
- -3.552713678800501e-15
|
| 40 |
+
- -1.4883927423881005e-15
|
| 41 |
+
- -2.4424906541753444e-15
|
| 42 |
+
- -6.05474814945516
|
| 43 |
+
- -8.359419908238154
|
| 44 |
+
- -8.461445707766968
|
| 45 |
+
- -4.772501185489789
|
| 46 |
+
- -4.9960036108132044e-15
|
| 47 |
+
- -3.552713678800501e-15
|
| 48 |
+
- -1.505306152902931
|
| 49 |
+
- 6.217248937900877e-15
|
| 50 |
+
- -3.5935859953202867
|
| 51 |
+
- -5.298043899940335
|
| 52 |
+
- -5.289195691841476
|
| 53 |
+
- -4.586309292435159
|
| 54 |
+
- -3.145306223924418
|
| 55 |
+
- 8.43769498715119e-15
|
| 56 |
+
- -1.5534031951839165
|
| 57 |
+
- -2.452149990972268
|
| 58 |
+
- -6.698950860236158
|
| 59 |
+
- -7.784447374762749
|
| 60 |
+
- -8.35172207094275
|
| 61 |
+
- -8.475443777072595
|
| 62 |
+
- -7.835952917487167
|
| 63 |
+
- -7.177746464190568
|
| 64 |
+
- -6.276117296504732
|
| 65 |
+
- -4.906667819649391
|
| 66 |
+
- -2.9770431302950078
|
| 67 |
+
- -0.6870767989674853
|
| 68 |
+
- -2.570966565048506
|
| 69 |
+
- -4.112249499833631
|
| 70 |
+
- -4.501937385014317
|
| 71 |
+
- -3.7911090450737395
|
| 72 |
+
- 0.0
|
| 73 |
+
- 0.0
|
| 74 |
+
- -1.404014767200028
|
| 75 |
+
- -2.3340636233592837
|
| 76 |
+
- -6.977544301327938
|
| 77 |
+
- -8.429019781059974
|
| 78 |
+
- -9.558754587545973
|
| 79 |
+
- -9.914673095966668
|
| 80 |
+
- -9.262176302297178
|
| 81 |
+
- -8.344618489779986
|
| 82 |
+
- -6.81614547353467
|
| 83 |
+
- -4.823268946054286
|
| 84 |
+
- -1.9248732292365998
|
| 85 |
+
- -0.22855559486612576
|
| 86 |
+
- -2.0711980156857903
|
| 87 |
+
- -3.3848661082103417
|
| 88 |
+
- -3.693940774789746
|
| 89 |
+
- -3.0317412637444727
|
| 90 |
+
- 0.0
|
| 91 |
+
- 4.440892098500626e-16
|
| 92 |
+
- -1.69210743883596
|
| 93 |
+
- 0.0
|
| 94 |
+
- 0.0
|
| 95 |
+
- 0.0
|
| 96 |
+
- 0.0
|
| 97 |
+
- 0.0
|
| 98 |
+
- 0.0
|
| 99 |
+
- 0.0
|
| 100 |
+
- 0.0
|
| 101 |
+
- 0.0
|
| 102 |
+
- 0.0
|
| 103 |
+
- 0.0
|
| 104 |
+
- 0.0
|
| 105 |
+
- 0.0
|
| 106 |
+
- 0.0
|
| 107 |
+
- 0.0
|
| 108 |
+
- 0.0
|
| 109 |
+
- -9.847846174118736
|
| 110 |
+
- -11.050291739766923
|
| 111 |
+
- -11.612898250816091
|
| 112 |
+
- -11.043957156699099
|
| 113 |
+
- -10.20788381032279
|
| 114 |
+
- -8.329494152492249
|
| 115 |
+
- -5.758362379765965
|
| 116 |
+
- -2.705571683868682
|
| 117 |
+
- 0.3244215988004441
|
| 118 |
+
- -1.801036139937328
|
| 119 |
+
- -2.98927510335205
|
| 120 |
+
- -3.3414587373794893
|
| 121 |
+
- 0.0
|
| 122 |
+
- 0.0
|
| 123 |
+
- 0.0
|
| 124 |
+
- 0.0
|
| 125 |
+
- 0.0
|
| 126 |
+
- 0.0
|
| 127 |
+
- 0.0
|
| 128 |
+
- 0.0
|
| 129 |
+
- 0.0
|
| 130 |
+
- 0.0
|
| 131 |
+
- 0.0
|
| 132 |
+
- 0.0
|
| 133 |
+
- 0.0
|
| 134 |
+
- 0.0
|
| 135 |
+
- 0.0
|
| 136 |
+
- 0.0
|
| 137 |
+
normalizer_rmsd: 0.3387841421237734
|
| 138 |
+
train_dataset:
|
| 139 |
+
splits:
|
| 140 |
+
train:
|
| 141 |
+
src: "${ONESCIENCE_DATASETS_DIR}/oc20/s2ef_200k_uncompressed"
|
| 142 |
+
format: ase_db
|
| 143 |
+
transforms:
|
| 144 |
+
common_transform:
|
| 145 |
+
dataset_name: ${data.dataset_name}
|
| 146 |
+
stress_reshape_transform:
|
| 147 |
+
dataset_name: ${data.dataset_name}
|
| 148 |
+
val_dataset:
|
| 149 |
+
splits:
|
| 150 |
+
val:
|
| 151 |
+
src: "${ONESCIENCE_DATASETS_DIR}/oc20/s2ef_val_id_uncompressed"
|
| 152 |
+
format: ase_db
|
| 153 |
+
transforms:
|
| 154 |
+
common_transform:
|
| 155 |
+
dataset_name: ${data.dataset_name}
|
| 156 |
+
stress_reshape_transform:
|
| 157 |
+
dataset_name: ${data.dataset_name}
|
| 158 |
+
regress_stress: false
|
| 159 |
+
pass_through_head_outputs: true
|
| 160 |
+
heads:
|
| 161 |
+
efs:
|
| 162 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 163 |
+
tasks_list:
|
| 164 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 165 |
+
name: energy
|
| 166 |
+
level: system
|
| 167 |
+
property: energy
|
| 168 |
+
loss_fn:
|
| 169 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 170 |
+
loss_fn:
|
| 171 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 172 |
+
coefficient: 20
|
| 173 |
+
out_spec:
|
| 174 |
+
dim:
|
| 175 |
+
- 1
|
| 176 |
+
dtype: float32
|
| 177 |
+
normalizer:
|
| 178 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 179 |
+
mean: 0.0
|
| 180 |
+
rmsd: ${data.normalizer_rmsd}
|
| 181 |
+
element_references:
|
| 182 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 183 |
+
element_references:
|
| 184 |
+
_target_: torch.DoubleTensor
|
| 185 |
+
_args_:
|
| 186 |
+
- ${data.elem_refs}
|
| 187 |
+
datasets:
|
| 188 |
+
- ${data.dataset_name}
|
| 189 |
+
metrics:
|
| 190 |
+
- mae
|
| 191 |
+
- per_atom_mae
|
| 192 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 193 |
+
name: forces
|
| 194 |
+
level: atom
|
| 195 |
+
property: forces
|
| 196 |
+
train_on_free_atoms: true
|
| 197 |
+
eval_on_free_atoms: true
|
| 198 |
+
loss_fn:
|
| 199 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 200 |
+
loss_fn:
|
| 201 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 202 |
+
reduction: mean
|
| 203 |
+
coefficient: 2
|
| 204 |
+
out_spec:
|
| 205 |
+
dim:
|
| 206 |
+
- 3
|
| 207 |
+
dtype: float32
|
| 208 |
+
normalizer:
|
| 209 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 210 |
+
mean: 0.0
|
| 211 |
+
rmsd: ${data.normalizer_rmsd}
|
| 212 |
+
datasets:
|
| 213 |
+
- ${data.dataset_name}
|
| 214 |
+
metrics:
|
| 215 |
+
- mae
|
| 216 |
+
- cosine_similarity
|
| 217 |
+
- magnitude_error
|
| 218 |
+
train_dataset:
|
| 219 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 220 |
+
dataset_configs:
|
| 221 |
+
finetune_dataset: ${data.train_dataset}
|
| 222 |
+
combined_dataset_config:
|
| 223 |
+
sampling:
|
| 224 |
+
type: temperature
|
| 225 |
+
temperature: 1.0
|
| 226 |
+
train_dataloader:
|
| 227 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 228 |
+
dataset: ${train_dataset}
|
| 229 |
+
batch_sampler_fn:
|
| 230 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 231 |
+
_partial_: true
|
| 232 |
+
batch_size: ${batch_size}
|
| 233 |
+
shuffle: true
|
| 234 |
+
seed: 0
|
| 235 |
+
num_workers: 0
|
| 236 |
+
collate_fn:
|
| 237 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 238 |
+
tasks: ${data.tasks_list}
|
| 239 |
+
exclude_keys: []
|
| 240 |
+
val_dataset:
|
| 241 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 242 |
+
dataset_configs:
|
| 243 |
+
finetune_dataset: ${data.val_dataset}
|
| 244 |
+
combined_dataset_config:
|
| 245 |
+
sampling:
|
| 246 |
+
type: temperature
|
| 247 |
+
temperature: 1.0
|
| 248 |
+
eval_dataloader:
|
| 249 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 250 |
+
dataset: ${val_dataset}
|
| 251 |
+
batch_sampler_fn:
|
| 252 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 253 |
+
_partial_: true
|
| 254 |
+
batch_size: ${batch_size}
|
| 255 |
+
shuffle: false
|
| 256 |
+
seed: 0
|
| 257 |
+
num_workers: 0
|
| 258 |
+
collate_fn:
|
| 259 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 260 |
+
tasks: ${data.tasks_list}
|
| 261 |
+
exclude_keys: []
|
| 262 |
+
runner:
|
| 263 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 264 |
+
train_dataloader: ${train_dataloader}
|
| 265 |
+
eval_dataloader: ${eval_dataloader}
|
| 266 |
+
train_eval_unit:
|
| 267 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 268 |
+
job_config: ${job}
|
| 269 |
+
tasks: ${data.tasks_list}
|
| 270 |
+
model:
|
| 271 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 272 |
+
checkpoint_location: "${ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt"
|
| 273 |
+
overrides:
|
| 274 |
+
backbone:
|
| 275 |
+
otf_graph: true
|
| 276 |
+
max_neighbors: ${max_neighbors}
|
| 277 |
+
regress_stress: ${data.regress_stress}
|
| 278 |
+
always_use_pbc: false
|
| 279 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 280 |
+
heads: ${data.heads}
|
| 281 |
+
optimizer_fn:
|
| 282 |
+
_target_: torch.optim.AdamW
|
| 283 |
+
_partial_: true
|
| 284 |
+
lr: ${lr}
|
| 285 |
+
weight_decay: ${weight_decay}
|
| 286 |
+
cosine_lr_scheduler_fn:
|
| 287 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 288 |
+
_partial_: true
|
| 289 |
+
warmup_factor: 0.2
|
| 290 |
+
warmup_epochs: 0.01
|
| 291 |
+
lr_min_factor: 0.01
|
| 292 |
+
epochs: ${epochs}
|
| 293 |
+
steps: ${steps}
|
| 294 |
+
print_every: 10
|
| 295 |
+
clip_grad_norm: 100
|
| 296 |
+
max_epochs: ${epochs}
|
| 297 |
+
max_steps: ${steps}
|
| 298 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 299 |
+
callbacks:
|
| 300 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 301 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 302 |
+
max_saved_checkpoints: 5
|
| 303 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/configs/oc20_ef_16dcu.yaml
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# UMA OC20 微调示例 - 2 节点 x 8 DCU, energy + forces
|
| 3 |
+
# ============================================================
|
| 4 |
+
#
|
| 5 |
+
# 使用前置:
|
| 6 |
+
# 1. 已运行 scripts/create_uma_finetune_dataset.py 转换数据集
|
| 7 |
+
# (或手动将 ASE-lmdb 格式的 train/ val/ 放到合适路径)
|
| 8 |
+
# 2. 已运行 scripts/convert_model.py 得到可微调的 checkpoint .pt
|
| 9 |
+
# 3. 将 data.dataset_name / elem_refs / normalizer_rmsd 填为数据生成脚本输出的值
|
| 10 |
+
#
|
| 11 |
+
# 运行:
|
| 12 |
+
# bash run.sh --config configs/oc20_ef_16dcu.yaml --submit # 多节点只能 sbatch
|
| 13 |
+
# bash run.sh --config configs/oc20_ef_16dcu.yaml --dry-run # 预览
|
| 14 |
+
# ============================================================
|
| 15 |
+
|
| 16 |
+
name: oc20_ef_16dcu
|
| 17 |
+
description: "UMA OC20 finetune - 2 nodes x 8 DCU (energy+forces)"
|
| 18 |
+
|
| 19 |
+
# ============================================================
|
| 20 |
+
# 启动配置 (控制 python / torchrun / srun 选择)
|
| 21 |
+
# ============================================================
|
| 22 |
+
launch:
|
| 23 |
+
launcher: torchrun
|
| 24 |
+
num_nodes: 2
|
| 25 |
+
num_gpus: 8
|
| 26 |
+
omp_num_threads: 1
|
| 27 |
+
|
| 28 |
+
# ============================================================
|
| 29 |
+
# 环境配置
|
| 30 |
+
# ============================================================
|
| 31 |
+
env:
|
| 32 |
+
conda_env: chem
|
| 33 |
+
modules:
|
| 34 |
+
- sghpc-mpi-gcc/26.3
|
| 35 |
+
|
| 36 |
+
# ============================================================
|
| 37 |
+
# SLURM 配置 (仅 --submit 模式使用)
|
| 38 |
+
# ============================================================
|
| 39 |
+
slurm:
|
| 40 |
+
partition: k100ai
|
| 41 |
+
time: "48:00:00"
|
| 42 |
+
cpus_per_task: 128
|
| 43 |
+
|
| 44 |
+
# ============================================================
|
| 45 |
+
# 多节点 NCCL 通信配置
|
| 46 |
+
# ============================================================
|
| 47 |
+
nccl:
|
| 48 |
+
socket_ifname: ib0
|
| 49 |
+
ib_hca: mlx5_0
|
| 50 |
+
proto: Simple
|
| 51 |
+
|
| 52 |
+
# ============================================================
|
| 53 |
+
# ---------- 下面是 hydra 配置内容 (直接传给 train.py) ----------
|
| 54 |
+
# 注意: 本文件的 name/launch/env/slurm/nccl 会被 run.sh 在写出
|
| 55 |
+
# outputs/xxx_ts/hydra_config.yaml 时剥离, 不会进入 hydra 配置。
|
| 56 |
+
# ============================================================
|
| 57 |
+
|
| 58 |
+
job:
|
| 59 |
+
device_type: CUDA
|
| 60 |
+
scheduler:
|
| 61 |
+
mode: LOCAL # 由 run.sh 驱动 srun+torchrun, 不让 train.py submitit
|
| 62 |
+
ranks_per_node: 8
|
| 63 |
+
num_nodes: 2
|
| 64 |
+
debug: True
|
| 65 |
+
run_dir: ./uma_finetune_runs/
|
| 66 |
+
run_name: oc20_ef_16dcu
|
| 67 |
+
logger:
|
| 68 |
+
_target_: onescience.utils.uma.common.logger.WandBSingletonLogger.init_wandb
|
| 69 |
+
_partial_: true
|
| 70 |
+
entity: example
|
| 71 |
+
project: uma_finetune
|
| 72 |
+
|
| 73 |
+
max_neighbors: 300
|
| 74 |
+
epochs: 1
|
| 75 |
+
steps: null
|
| 76 |
+
batch_size: 2
|
| 77 |
+
lr: 4.0e-4
|
| 78 |
+
weight_decay: 1.0e-3
|
| 79 |
+
evaluate_every_n_steps: 100
|
| 80 |
+
checkpoint_every_n_steps: 1000
|
| 81 |
+
|
| 82 |
+
# ============================================================
|
| 83 |
+
# 数据配置 (来自 configs/data/uma_conserving_data_task_energy_force.yaml)
|
| 84 |
+
# 下面 4 项必须由用户填写:
|
| 85 |
+
# dataset_name : 例如 "oc20" / "omat" / "omol" (与 base model 预训练任务对应)
|
| 86 |
+
# elem_refs : 每元素参考能量列表 (长度 118), 由 create_finetune_dataset.py 生成
|
| 87 |
+
# normalizer_rmsd : 力的 RMSD 归一化常数, 由 create_finetune_dataset.py 生成
|
| 88 |
+
# train/val src : ASE-lmdb 数据目录的绝对/相对路径
|
| 89 |
+
# ============================================================
|
| 90 |
+
data:
|
| 91 |
+
dataset_name: ?? # 请填写, e.g. "oc20"
|
| 92 |
+
elem_refs: ?? # 请填写, 列表
|
| 93 |
+
normalizer_rmsd: ?? # 请填写, float
|
| 94 |
+
|
| 95 |
+
train_dataset:
|
| 96 |
+
splits:
|
| 97 |
+
train:
|
| 98 |
+
src: "${ONESCIENCE_DATASETS_DIR}/oc20/s2ef_200k_uncompressed"
|
| 99 |
+
format: ase_db
|
| 100 |
+
transforms:
|
| 101 |
+
common_transform:
|
| 102 |
+
dataset_name: ${data.dataset_name}
|
| 103 |
+
stress_reshape_transform:
|
| 104 |
+
dataset_name: ${data.dataset_name}
|
| 105 |
+
|
| 106 |
+
val_dataset:
|
| 107 |
+
splits:
|
| 108 |
+
val:
|
| 109 |
+
src: "${ONESCIENCE_DATASETS_DIR}/oc20/s2ef_val_id_uncompressed"
|
| 110 |
+
format: ase_db
|
| 111 |
+
transforms:
|
| 112 |
+
common_transform:
|
| 113 |
+
dataset_name: ${data.dataset_name}
|
| 114 |
+
stress_reshape_transform:
|
| 115 |
+
dataset_name: ${data.dataset_name}
|
| 116 |
+
|
| 117 |
+
regress_stress: False
|
| 118 |
+
pass_through_head_outputs: True
|
| 119 |
+
|
| 120 |
+
heads:
|
| 121 |
+
efs:
|
| 122 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 123 |
+
|
| 124 |
+
tasks_list:
|
| 125 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 126 |
+
name: energy
|
| 127 |
+
level: system
|
| 128 |
+
property: energy
|
| 129 |
+
loss_fn:
|
| 130 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 131 |
+
loss_fn:
|
| 132 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 133 |
+
coefficient: 20
|
| 134 |
+
out_spec:
|
| 135 |
+
dim: [1]
|
| 136 |
+
dtype: float32
|
| 137 |
+
normalizer:
|
| 138 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 139 |
+
mean: 0.0
|
| 140 |
+
rmsd: ${data.normalizer_rmsd}
|
| 141 |
+
element_references:
|
| 142 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 143 |
+
element_references:
|
| 144 |
+
_target_: torch.DoubleTensor
|
| 145 |
+
_args_:
|
| 146 |
+
- ${data.elem_refs}
|
| 147 |
+
datasets:
|
| 148 |
+
- ${data.dataset_name}
|
| 149 |
+
metrics:
|
| 150 |
+
- mae
|
| 151 |
+
- per_atom_mae
|
| 152 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 153 |
+
name: forces
|
| 154 |
+
level: atom
|
| 155 |
+
property: forces
|
| 156 |
+
train_on_free_atoms: True
|
| 157 |
+
eval_on_free_atoms: True
|
| 158 |
+
loss_fn:
|
| 159 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 160 |
+
loss_fn:
|
| 161 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 162 |
+
reduction: mean
|
| 163 |
+
coefficient: 2
|
| 164 |
+
out_spec:
|
| 165 |
+
dim: [3]
|
| 166 |
+
dtype: float32
|
| 167 |
+
normalizer:
|
| 168 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 169 |
+
mean: 0.0
|
| 170 |
+
rmsd: ${data.normalizer_rmsd}
|
| 171 |
+
datasets:
|
| 172 |
+
- ${data.dataset_name}
|
| 173 |
+
metrics:
|
| 174 |
+
- mae
|
| 175 |
+
- cosine_similarity
|
| 176 |
+
- magnitude_error
|
| 177 |
+
|
| 178 |
+
# ============================================================
|
| 179 |
+
# 数据流与训练 Runner (来自 configs/uma_sm_finetune_template.yaml)
|
| 180 |
+
# ============================================================
|
| 181 |
+
train_dataset:
|
| 182 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 183 |
+
dataset_configs:
|
| 184 |
+
finetune_dataset: ${data.train_dataset}
|
| 185 |
+
combined_dataset_config:
|
| 186 |
+
sampling:
|
| 187 |
+
type: temperature
|
| 188 |
+
temperature: 1.0
|
| 189 |
+
|
| 190 |
+
train_dataloader:
|
| 191 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 192 |
+
dataset: ${train_dataset}
|
| 193 |
+
batch_sampler_fn:
|
| 194 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 195 |
+
_partial_: True
|
| 196 |
+
batch_size: ${batch_size}
|
| 197 |
+
shuffle: True
|
| 198 |
+
seed: 0
|
| 199 |
+
num_workers: 0
|
| 200 |
+
collate_fn:
|
| 201 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 202 |
+
tasks: ${data.tasks_list}
|
| 203 |
+
exclude_keys: []
|
| 204 |
+
|
| 205 |
+
val_dataset:
|
| 206 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 207 |
+
dataset_configs:
|
| 208 |
+
finetune_dataset: ${data.val_dataset}
|
| 209 |
+
combined_dataset_config:
|
| 210 |
+
sampling:
|
| 211 |
+
type: temperature
|
| 212 |
+
temperature: 1.0
|
| 213 |
+
|
| 214 |
+
eval_dataloader:
|
| 215 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 216 |
+
dataset: ${val_dataset}
|
| 217 |
+
batch_sampler_fn:
|
| 218 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 219 |
+
_partial_: True
|
| 220 |
+
batch_size: ${batch_size}
|
| 221 |
+
shuffle: False
|
| 222 |
+
seed: 0
|
| 223 |
+
num_workers: 0
|
| 224 |
+
collate_fn:
|
| 225 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 226 |
+
tasks: ${data.tasks_list}
|
| 227 |
+
exclude_keys: []
|
| 228 |
+
|
| 229 |
+
runner:
|
| 230 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 231 |
+
train_dataloader: ${train_dataloader}
|
| 232 |
+
eval_dataloader: ${eval_dataloader}
|
| 233 |
+
train_eval_unit:
|
| 234 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 235 |
+
job_config: ${job}
|
| 236 |
+
tasks: ${data.tasks_list}
|
| 237 |
+
model:
|
| 238 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 239 |
+
checkpoint_location: "${ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt"
|
| 240 |
+
overrides:
|
| 241 |
+
backbone:
|
| 242 |
+
otf_graph: True
|
| 243 |
+
max_neighbors: ${max_neighbors}
|
| 244 |
+
regress_stress: ${data.regress_stress}
|
| 245 |
+
always_use_pbc: False
|
| 246 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 247 |
+
heads: ${data.heads}
|
| 248 |
+
optimizer_fn:
|
| 249 |
+
_target_: torch.optim.AdamW
|
| 250 |
+
_partial_: true
|
| 251 |
+
lr: ${lr}
|
| 252 |
+
weight_decay: ${weight_decay}
|
| 253 |
+
cosine_lr_scheduler_fn:
|
| 254 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 255 |
+
_partial_: true
|
| 256 |
+
warmup_factor: 0.2
|
| 257 |
+
warmup_epochs: 0.01
|
| 258 |
+
lr_min_factor: 0.01
|
| 259 |
+
epochs: ${epochs}
|
| 260 |
+
steps: ${steps}
|
| 261 |
+
print_every: 10
|
| 262 |
+
clip_grad_norm: 100
|
| 263 |
+
max_epochs: ${epochs}
|
| 264 |
+
max_steps: ${steps}
|
| 265 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 266 |
+
callbacks:
|
| 267 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 268 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 269 |
+
max_saved_checkpoints: 5
|
| 270 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/configs/oc20_ef_4dcu.yaml
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: oc20_ef_4dcu
|
| 2 |
+
description: UMA OC20 finetune - 1 node x 4 DCU (energy+forces)
|
| 3 |
+
launch:
|
| 4 |
+
launcher: torchrun
|
| 5 |
+
num_nodes: 1
|
| 6 |
+
num_gpus: 4
|
| 7 |
+
omp_num_threads: 1
|
| 8 |
+
env:
|
| 9 |
+
conda_env: uv311_mat
|
| 10 |
+
modules:
|
| 11 |
+
- sghpc-mpi-gcc/26.3
|
| 12 |
+
slurm:
|
| 13 |
+
partition: hpctest01
|
| 14 |
+
time: '2:00:00'
|
| 15 |
+
cpus_per_task: 64
|
| 16 |
+
job:
|
| 17 |
+
device_type: CUDA
|
| 18 |
+
scheduler:
|
| 19 |
+
mode: LOCAL
|
| 20 |
+
ranks_per_node: 4
|
| 21 |
+
num_nodes: 1
|
| 22 |
+
debug: true
|
| 23 |
+
run_dir: ./uma_finetune_runs/
|
| 24 |
+
run_name: oc20_ef_4dcu
|
| 25 |
+
logger: null
|
| 26 |
+
max_neighbors: 300
|
| 27 |
+
epochs: 1
|
| 28 |
+
steps: null
|
| 29 |
+
batch_size: 2
|
| 30 |
+
lr: 0.0004
|
| 31 |
+
weight_decay: 0.001
|
| 32 |
+
evaluate_every_n_steps: 100
|
| 33 |
+
checkpoint_every_n_steps: null
|
| 34 |
+
data:
|
| 35 |
+
dataset_name: oc20
|
| 36 |
+
elem_refs:
|
| 37 |
+
- -9.01836389963216e-17
|
| 38 |
+
- -3.384793156087369
|
| 39 |
+
- -3.552713678800501e-15
|
| 40 |
+
- -1.4210854715202004e-14
|
| 41 |
+
- -3.552713678800501e-15
|
| 42 |
+
- -6.096600969433027
|
| 43 |
+
- -8.288307365078008
|
| 44 |
+
- -8.377401871630358
|
| 45 |
+
- -4.73277382959691
|
| 46 |
+
- -4.218847493575595e-15
|
| 47 |
+
- 8.881784197001252e-15
|
| 48 |
+
- -1.5758719946770752
|
| 49 |
+
- 3.552713678800501e-15
|
| 50 |
+
- -3.605510955946432
|
| 51 |
+
- -5.341968801697664
|
| 52 |
+
- -5.26720503097145
|
| 53 |
+
- -4.566219949129778
|
| 54 |
+
- -3.0961819892844145
|
| 55 |
+
- 0.0
|
| 56 |
+
- -1.5790045471096414
|
| 57 |
+
- -2.4721953289673877
|
| 58 |
+
- -6.6807503507860915
|
| 59 |
+
- -7.74796841292303
|
| 60 |
+
- -8.403945117874883
|
| 61 |
+
- -8.507943997230633
|
| 62 |
+
- -7.859727128191719
|
| 63 |
+
- -7.202700747006435
|
| 64 |
+
- -6.283218856635957
|
| 65 |
+
- -4.8972399918354865
|
| 66 |
+
- -2.9438503901650144
|
| 67 |
+
- -0.6766002592520115
|
| 68 |
+
- -2.577391165910655
|
| 69 |
+
- -4.06962014738207
|
| 70 |
+
- -4.497309369632559
|
| 71 |
+
- -3.780665648345848
|
| 72 |
+
- -5.329070518200751e-15
|
| 73 |
+
- -1.3322676295501878e-15
|
| 74 |
+
- -1.4256648977370752
|
| 75 |
+
- -2.412526481733912
|
| 76 |
+
- -7.014442857905434
|
| 77 |
+
- -8.512272383088028
|
| 78 |
+
- -9.592355889199172
|
| 79 |
+
- -9.910578824883942
|
| 80 |
+
- -9.267018354580234
|
| 81 |
+
- -8.354773494931816
|
| 82 |
+
- -6.854409258193916
|
| 83 |
+
- -4.840002859370445
|
| 84 |
+
- -1.918019107887611
|
| 85 |
+
- -0.23821365616560497
|
| 86 |
+
- -2.0561553805596358
|
| 87 |
+
- -3.3842132848566258
|
| 88 |
+
- -3.667988196267832
|
| 89 |
+
- -3.032988525381068
|
| 90 |
+
- 2.220446049250313e-16
|
| 91 |
+
- 0.0
|
| 92 |
+
- -1.6559264334420458
|
| 93 |
+
- 0.0
|
| 94 |
+
- 0.0
|
| 95 |
+
- 0.0
|
| 96 |
+
- 0.0
|
| 97 |
+
- 0.0
|
| 98 |
+
- 0.0
|
| 99 |
+
- 0.0
|
| 100 |
+
- 0.0
|
| 101 |
+
- 0.0
|
| 102 |
+
- 0.0
|
| 103 |
+
- 0.0
|
| 104 |
+
- 0.0
|
| 105 |
+
- 0.0
|
| 106 |
+
- 0.0
|
| 107 |
+
- 0.0
|
| 108 |
+
- 0.0
|
| 109 |
+
- -9.875111813171198
|
| 110 |
+
- -11.052180114376393
|
| 111 |
+
- -11.621851352916064
|
| 112 |
+
- -11.117519848695258
|
| 113 |
+
- -10.219707389902464
|
| 114 |
+
- -8.357987841676932
|
| 115 |
+
- -5.796390731438155
|
| 116 |
+
- -2.701232714681403
|
| 117 |
+
- 0.32034288848399406
|
| 118 |
+
- -1.8259293180072989
|
| 119 |
+
- -2.991135014870159
|
| 120 |
+
- -3.3375851657056166
|
| 121 |
+
- 0.0
|
| 122 |
+
- 0.0
|
| 123 |
+
- 0.0
|
| 124 |
+
- 0.0
|
| 125 |
+
- 0.0
|
| 126 |
+
- 0.0
|
| 127 |
+
- 0.0
|
| 128 |
+
- 0.0
|
| 129 |
+
- 0.0
|
| 130 |
+
- 0.0
|
| 131 |
+
- 0.0
|
| 132 |
+
- 0.0
|
| 133 |
+
- 0.0
|
| 134 |
+
- 0.0
|
| 135 |
+
- 0.0
|
| 136 |
+
- 0.0
|
| 137 |
+
normalizer_rmsd: 0.3184592617813517
|
| 138 |
+
train_dataset:
|
| 139 |
+
splits:
|
| 140 |
+
train:
|
| 141 |
+
src: ${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/train
|
| 142 |
+
format: ase_db
|
| 143 |
+
transforms:
|
| 144 |
+
common_transform:
|
| 145 |
+
dataset_name: ${data.dataset_name}
|
| 146 |
+
stress_reshape_transform:
|
| 147 |
+
dataset_name: ${data.dataset_name}
|
| 148 |
+
val_dataset:
|
| 149 |
+
splits:
|
| 150 |
+
val:
|
| 151 |
+
src: ${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/val
|
| 152 |
+
format: ase_db
|
| 153 |
+
transforms:
|
| 154 |
+
common_transform:
|
| 155 |
+
dataset_name: ${data.dataset_name}
|
| 156 |
+
stress_reshape_transform:
|
| 157 |
+
dataset_name: ${data.dataset_name}
|
| 158 |
+
regress_stress: false
|
| 159 |
+
pass_through_head_outputs: true
|
| 160 |
+
heads:
|
| 161 |
+
efs:
|
| 162 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 163 |
+
tasks_list:
|
| 164 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 165 |
+
name: energy
|
| 166 |
+
level: system
|
| 167 |
+
property: energy
|
| 168 |
+
loss_fn:
|
| 169 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 170 |
+
loss_fn:
|
| 171 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 172 |
+
coefficient: 20
|
| 173 |
+
out_spec:
|
| 174 |
+
dim:
|
| 175 |
+
- 1
|
| 176 |
+
dtype: float32
|
| 177 |
+
normalizer:
|
| 178 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 179 |
+
mean: 0.0
|
| 180 |
+
rmsd: ${data.normalizer_rmsd}
|
| 181 |
+
element_references:
|
| 182 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 183 |
+
element_references:
|
| 184 |
+
_target_: torch.DoubleTensor
|
| 185 |
+
_args_:
|
| 186 |
+
- ${data.elem_refs}
|
| 187 |
+
datasets:
|
| 188 |
+
- ${data.dataset_name}
|
| 189 |
+
metrics:
|
| 190 |
+
- mae
|
| 191 |
+
- per_atom_mae
|
| 192 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 193 |
+
name: forces
|
| 194 |
+
level: atom
|
| 195 |
+
property: forces
|
| 196 |
+
train_on_free_atoms: true
|
| 197 |
+
eval_on_free_atoms: true
|
| 198 |
+
loss_fn:
|
| 199 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 200 |
+
loss_fn:
|
| 201 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 202 |
+
reduction: mean
|
| 203 |
+
coefficient: 2
|
| 204 |
+
out_spec:
|
| 205 |
+
dim:
|
| 206 |
+
- 3
|
| 207 |
+
dtype: float32
|
| 208 |
+
normalizer:
|
| 209 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 210 |
+
mean: 0.0
|
| 211 |
+
rmsd: ${data.normalizer_rmsd}
|
| 212 |
+
datasets:
|
| 213 |
+
- ${data.dataset_name}
|
| 214 |
+
metrics:
|
| 215 |
+
- mae
|
| 216 |
+
- cosine_similarity
|
| 217 |
+
- magnitude_error
|
| 218 |
+
train_dataset:
|
| 219 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 220 |
+
dataset_configs:
|
| 221 |
+
finetune_dataset: ${data.train_dataset}
|
| 222 |
+
combined_dataset_config:
|
| 223 |
+
sampling:
|
| 224 |
+
type: temperature
|
| 225 |
+
temperature: 1.0
|
| 226 |
+
train_dataloader:
|
| 227 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 228 |
+
dataset: ${train_dataset}
|
| 229 |
+
batch_sampler_fn:
|
| 230 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 231 |
+
_partial_: true
|
| 232 |
+
batch_size: ${batch_size}
|
| 233 |
+
shuffle: true
|
| 234 |
+
seed: 0
|
| 235 |
+
num_workers: 0
|
| 236 |
+
collate_fn:
|
| 237 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 238 |
+
tasks: ${data.tasks_list}
|
| 239 |
+
exclude_keys: []
|
| 240 |
+
val_dataset:
|
| 241 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 242 |
+
dataset_configs:
|
| 243 |
+
finetune_dataset: ${data.val_dataset}
|
| 244 |
+
combined_dataset_config:
|
| 245 |
+
sampling:
|
| 246 |
+
type: temperature
|
| 247 |
+
temperature: 1.0
|
| 248 |
+
eval_dataloader:
|
| 249 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 250 |
+
dataset: ${val_dataset}
|
| 251 |
+
batch_sampler_fn:
|
| 252 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 253 |
+
_partial_: true
|
| 254 |
+
batch_size: ${batch_size}
|
| 255 |
+
shuffle: false
|
| 256 |
+
seed: 0
|
| 257 |
+
num_workers: 0
|
| 258 |
+
collate_fn:
|
| 259 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 260 |
+
tasks: ${data.tasks_list}
|
| 261 |
+
exclude_keys: []
|
| 262 |
+
runner:
|
| 263 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 264 |
+
train_dataloader: ${train_dataloader}
|
| 265 |
+
eval_dataloader: ${eval_dataloader}
|
| 266 |
+
train_eval_unit:
|
| 267 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 268 |
+
job_config: ${job}
|
| 269 |
+
tasks: ${data.tasks_list}
|
| 270 |
+
model:
|
| 271 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 272 |
+
checkpoint_location: ${ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt
|
| 273 |
+
overrides:
|
| 274 |
+
backbone:
|
| 275 |
+
otf_graph: true
|
| 276 |
+
max_neighbors: ${max_neighbors}
|
| 277 |
+
regress_stress: ${data.regress_stress}
|
| 278 |
+
always_use_pbc: false
|
| 279 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 280 |
+
heads: ${data.heads}
|
| 281 |
+
optimizer_fn:
|
| 282 |
+
_target_: torch.optim.AdamW
|
| 283 |
+
_partial_: true
|
| 284 |
+
lr: ${lr}
|
| 285 |
+
weight_decay: ${weight_decay}
|
| 286 |
+
cosine_lr_scheduler_fn:
|
| 287 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 288 |
+
_partial_: true
|
| 289 |
+
warmup_factor: 0.2
|
| 290 |
+
warmup_epochs: 0.01
|
| 291 |
+
lr_min_factor: 0.01
|
| 292 |
+
epochs: ${epochs}
|
| 293 |
+
steps: ${steps}
|
| 294 |
+
print_every: 10
|
| 295 |
+
clip_grad_norm: 100
|
| 296 |
+
max_epochs: ${epochs}
|
| 297 |
+
max_steps: ${steps}
|
| 298 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 299 |
+
callbacks:
|
| 300 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 301 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 302 |
+
max_saved_checkpoints: 5
|
| 303 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/configs/oc20_ef_8dcu.yaml
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# UMA OC20 微调示例 - 单节点 8 DCU, energy + forces
|
| 3 |
+
# ============================================================
|
| 4 |
+
#
|
| 5 |
+
# 使用前置:
|
| 6 |
+
# 1. 已运行 scripts/create_uma_finetune_dataset.py 转换数据集
|
| 7 |
+
# (或手动将 ASE-lmdb 格式的 train/ val/ 放到合适路径)
|
| 8 |
+
# 2. 已运行 scripts/convert_model.py 得到可微调的 checkpoint .pt
|
| 9 |
+
# 3. 将 data.dataset_name / elem_refs / normalizer_rmsd 填为数据生成脚本输出的值
|
| 10 |
+
#
|
| 11 |
+
# 运行:
|
| 12 |
+
# bash run.sh --config configs/oc20_ef_8dcu.yaml # 直接跑
|
| 13 |
+
# bash run.sh --config configs/oc20_ef_8dcu.yaml --submit # sbatch
|
| 14 |
+
# bash run.sh --config configs/oc20_ef_8dcu.yaml --dry-run # 预览
|
| 15 |
+
# ============================================================
|
| 16 |
+
|
| 17 |
+
name: oc20_ef_8dcu
|
| 18 |
+
description: "UMA OC20 finetune - 1 node x 8 DCU (energy+forces)"
|
| 19 |
+
|
| 20 |
+
# ============================================================
|
| 21 |
+
# 启动配置 (控制 python / torchrun / srun 选择)
|
| 22 |
+
# ============================================================
|
| 23 |
+
launch:
|
| 24 |
+
launcher: torchrun
|
| 25 |
+
num_nodes: 1
|
| 26 |
+
num_gpus: 8
|
| 27 |
+
omp_num_threads: 1
|
| 28 |
+
|
| 29 |
+
# ============================================================
|
| 30 |
+
# 环境配置
|
| 31 |
+
# ============================================================
|
| 32 |
+
env:
|
| 33 |
+
conda_env: chem
|
| 34 |
+
modules:
|
| 35 |
+
- sghpc-mpi-gcc/26.3
|
| 36 |
+
|
| 37 |
+
# ============================================================
|
| 38 |
+
# SLURM 配置 (仅 --submit 模式使用)
|
| 39 |
+
# ============================================================
|
| 40 |
+
slurm:
|
| 41 |
+
partition: k100ai
|
| 42 |
+
time: "12:00:00"
|
| 43 |
+
cpus_per_task: 128
|
| 44 |
+
|
| 45 |
+
# ============================================================
|
| 46 |
+
# ---------- 下面是 hydra 配置内容 (直接传给 train.py) ----------
|
| 47 |
+
# 注意: 本文件的 name/launch/env/slurm/nccl 会被 run.sh 在写出
|
| 48 |
+
# outputs/xxx_ts/hydra_config.yaml 时剥离, 不会进入 hydra 配置。
|
| 49 |
+
# ============================================================
|
| 50 |
+
|
| 51 |
+
job:
|
| 52 |
+
device_type: CUDA
|
| 53 |
+
scheduler:
|
| 54 |
+
mode: LOCAL # 由 run.sh 驱动 torchrun, 不让 train.py submitit
|
| 55 |
+
ranks_per_node: 8
|
| 56 |
+
num_nodes: 1
|
| 57 |
+
debug: True
|
| 58 |
+
run_dir: ./uma_finetune_runs/
|
| 59 |
+
run_name: oc20_ef_8dcu
|
| 60 |
+
logger:
|
| 61 |
+
_target_: onescience.utils.uma.common.logger.WandBSingletonLogger.init_wandb
|
| 62 |
+
_partial_: true
|
| 63 |
+
entity: example
|
| 64 |
+
project: uma_finetune
|
| 65 |
+
|
| 66 |
+
max_neighbors: 300
|
| 67 |
+
epochs: 1
|
| 68 |
+
steps: null
|
| 69 |
+
batch_size: 2
|
| 70 |
+
lr: 4.0e-4
|
| 71 |
+
weight_decay: 1.0e-3
|
| 72 |
+
evaluate_every_n_steps: 100
|
| 73 |
+
checkpoint_every_n_steps: 1000
|
| 74 |
+
|
| 75 |
+
# ============================================================
|
| 76 |
+
# 数据配置 (来自 configs/data/uma_conserving_data_task_energy_force.yaml)
|
| 77 |
+
# 下面 4 项必须由用户填写:
|
| 78 |
+
# dataset_name : 例如 "oc20" / "omat" / "omol" (与 base model 预训练任务对应)
|
| 79 |
+
# elem_refs : 每元素参考能量列表 (长度 118), 由 create_finetune_dataset.py 生成
|
| 80 |
+
# normalizer_rmsd : 力的 RMSD 归一化常数, 由 create_finetune_dataset.py 生成
|
| 81 |
+
# train/val src : ASE-lmdb 数据目录的绝对/相对路径
|
| 82 |
+
# ============================================================
|
| 83 |
+
data:
|
| 84 |
+
dataset_name: ?? # 请填写, e.g. "oc20"
|
| 85 |
+
elem_refs: ?? # 请填写, 列表
|
| 86 |
+
normalizer_rmsd: ?? # 请填写, float
|
| 87 |
+
|
| 88 |
+
train_dataset:
|
| 89 |
+
splits:
|
| 90 |
+
train:
|
| 91 |
+
src: "${ONESCIENCE_DATASETS_DIR}/oc20/s2ef_200k_uncompressed"
|
| 92 |
+
format: ase_db
|
| 93 |
+
transforms:
|
| 94 |
+
common_transform:
|
| 95 |
+
dataset_name: ${data.dataset_name}
|
| 96 |
+
stress_reshape_transform:
|
| 97 |
+
dataset_name: ${data.dataset_name}
|
| 98 |
+
|
| 99 |
+
val_dataset:
|
| 100 |
+
splits:
|
| 101 |
+
val:
|
| 102 |
+
src: "${ONESCIENCE_DATASETS_DIR}/oc20/s2ef_val_id_uncompressed"
|
| 103 |
+
format: ase_db
|
| 104 |
+
transforms:
|
| 105 |
+
common_transform:
|
| 106 |
+
dataset_name: ${data.dataset_name}
|
| 107 |
+
stress_reshape_transform:
|
| 108 |
+
dataset_name: ${data.dataset_name}
|
| 109 |
+
|
| 110 |
+
regress_stress: False
|
| 111 |
+
pass_through_head_outputs: True
|
| 112 |
+
|
| 113 |
+
heads:
|
| 114 |
+
efs:
|
| 115 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 116 |
+
|
| 117 |
+
tasks_list:
|
| 118 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 119 |
+
name: energy
|
| 120 |
+
level: system
|
| 121 |
+
property: energy
|
| 122 |
+
loss_fn:
|
| 123 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 124 |
+
loss_fn:
|
| 125 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 126 |
+
coefficient: 20
|
| 127 |
+
out_spec:
|
| 128 |
+
dim: [1]
|
| 129 |
+
dtype: float32
|
| 130 |
+
normalizer:
|
| 131 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 132 |
+
mean: 0.0
|
| 133 |
+
rmsd: ${data.normalizer_rmsd}
|
| 134 |
+
element_references:
|
| 135 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 136 |
+
element_references:
|
| 137 |
+
_target_: torch.DoubleTensor
|
| 138 |
+
_args_:
|
| 139 |
+
- ${data.elem_refs}
|
| 140 |
+
datasets:
|
| 141 |
+
- ${data.dataset_name}
|
| 142 |
+
metrics:
|
| 143 |
+
- mae
|
| 144 |
+
- per_atom_mae
|
| 145 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 146 |
+
name: forces
|
| 147 |
+
level: atom
|
| 148 |
+
property: forces
|
| 149 |
+
train_on_free_atoms: True
|
| 150 |
+
eval_on_free_atoms: True
|
| 151 |
+
loss_fn:
|
| 152 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 153 |
+
loss_fn:
|
| 154 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 155 |
+
reduction: mean
|
| 156 |
+
coefficient: 2
|
| 157 |
+
out_spec:
|
| 158 |
+
dim: [3]
|
| 159 |
+
dtype: float32
|
| 160 |
+
normalizer:
|
| 161 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 162 |
+
mean: 0.0
|
| 163 |
+
rmsd: ${data.normalizer_rmsd}
|
| 164 |
+
datasets:
|
| 165 |
+
- ${data.dataset_name}
|
| 166 |
+
metrics:
|
| 167 |
+
- mae
|
| 168 |
+
- cosine_similarity
|
| 169 |
+
- magnitude_error
|
| 170 |
+
|
| 171 |
+
# ============================================================
|
| 172 |
+
# 数据流与训练 Runner (来自 configs/uma_sm_finetune_template.yaml)
|
| 173 |
+
# ============================================================
|
| 174 |
+
train_dataset:
|
| 175 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 176 |
+
dataset_configs:
|
| 177 |
+
finetune_dataset: ${data.train_dataset}
|
| 178 |
+
combined_dataset_config:
|
| 179 |
+
sampling:
|
| 180 |
+
type: temperature
|
| 181 |
+
temperature: 1.0
|
| 182 |
+
|
| 183 |
+
train_dataloader:
|
| 184 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 185 |
+
dataset: ${train_dataset}
|
| 186 |
+
batch_sampler_fn:
|
| 187 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 188 |
+
_partial_: True
|
| 189 |
+
batch_size: ${batch_size}
|
| 190 |
+
shuffle: True
|
| 191 |
+
seed: 0
|
| 192 |
+
num_workers: 0
|
| 193 |
+
collate_fn:
|
| 194 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 195 |
+
tasks: ${data.tasks_list}
|
| 196 |
+
exclude_keys: []
|
| 197 |
+
|
| 198 |
+
val_dataset:
|
| 199 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 200 |
+
dataset_configs:
|
| 201 |
+
finetune_dataset: ${data.val_dataset}
|
| 202 |
+
combined_dataset_config:
|
| 203 |
+
sampling:
|
| 204 |
+
type: temperature
|
| 205 |
+
temperature: 1.0
|
| 206 |
+
|
| 207 |
+
eval_dataloader:
|
| 208 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 209 |
+
dataset: ${val_dataset}
|
| 210 |
+
batch_sampler_fn:
|
| 211 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 212 |
+
_partial_: True
|
| 213 |
+
batch_size: ${batch_size}
|
| 214 |
+
shuffle: False
|
| 215 |
+
seed: 0
|
| 216 |
+
num_workers: 0
|
| 217 |
+
collate_fn:
|
| 218 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 219 |
+
tasks: ${data.tasks_list}
|
| 220 |
+
exclude_keys: []
|
| 221 |
+
|
| 222 |
+
runner:
|
| 223 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 224 |
+
train_dataloader: ${train_dataloader}
|
| 225 |
+
eval_dataloader: ${eval_dataloader}
|
| 226 |
+
train_eval_unit:
|
| 227 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 228 |
+
job_config: ${job}
|
| 229 |
+
tasks: ${data.tasks_list}
|
| 230 |
+
model:
|
| 231 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 232 |
+
checkpoint_location: "${ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt"
|
| 233 |
+
overrides:
|
| 234 |
+
backbone:
|
| 235 |
+
otf_graph: True
|
| 236 |
+
max_neighbors: ${max_neighbors}
|
| 237 |
+
regress_stress: ${data.regress_stress}
|
| 238 |
+
always_use_pbc: False
|
| 239 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 240 |
+
heads: ${data.heads}
|
| 241 |
+
optimizer_fn:
|
| 242 |
+
_target_: torch.optim.AdamW
|
| 243 |
+
_partial_: true
|
| 244 |
+
lr: ${lr}
|
| 245 |
+
weight_decay: ${weight_decay}
|
| 246 |
+
cosine_lr_scheduler_fn:
|
| 247 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 248 |
+
_partial_: true
|
| 249 |
+
warmup_factor: 0.2
|
| 250 |
+
warmup_epochs: 0.01
|
| 251 |
+
lr_min_factor: 0.01
|
| 252 |
+
epochs: ${epochs}
|
| 253 |
+
steps: ${steps}
|
| 254 |
+
print_every: 10
|
| 255 |
+
clip_grad_norm: 100
|
| 256 |
+
max_epochs: ${epochs}
|
| 257 |
+
max_steps: ${steps}
|
| 258 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 259 |
+
callbacks:
|
| 260 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 261 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 262 |
+
max_saved_checkpoints: 5
|
| 263 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/outputs/oc20_ef_4dcu_20260721_114458/config.yaml
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: oc20_ef_4dcu
|
| 2 |
+
description: UMA OC20 finetune - 1 node x 4 DCU (energy+forces)
|
| 3 |
+
launch:
|
| 4 |
+
launcher: torchrun
|
| 5 |
+
num_nodes: 1
|
| 6 |
+
num_gpus: 4
|
| 7 |
+
omp_num_threads: 1
|
| 8 |
+
env:
|
| 9 |
+
conda_env: uv311_mat
|
| 10 |
+
modules:
|
| 11 |
+
- sghpc-mpi-gcc/26.3
|
| 12 |
+
slurm:
|
| 13 |
+
partition: hpctest01
|
| 14 |
+
time: '2:00:00'
|
| 15 |
+
cpus_per_task: 64
|
| 16 |
+
job:
|
| 17 |
+
device_type: CUDA
|
| 18 |
+
scheduler:
|
| 19 |
+
mode: LOCAL
|
| 20 |
+
ranks_per_node: 4
|
| 21 |
+
num_nodes: 1
|
| 22 |
+
debug: true
|
| 23 |
+
run_dir: ./uma_finetune_runs/
|
| 24 |
+
run_name: oc20_ef_4dcu
|
| 25 |
+
logger: null
|
| 26 |
+
max_neighbors: 300
|
| 27 |
+
epochs: 1
|
| 28 |
+
steps: null
|
| 29 |
+
batch_size: 2
|
| 30 |
+
lr: 0.0004
|
| 31 |
+
weight_decay: 0.001
|
| 32 |
+
evaluate_every_n_steps: 100
|
| 33 |
+
checkpoint_every_n_steps: null
|
| 34 |
+
data:
|
| 35 |
+
dataset_name: oc20
|
| 36 |
+
elem_refs:
|
| 37 |
+
- -9.01836389963216e-17
|
| 38 |
+
- -3.384793156087369
|
| 39 |
+
- -3.552713678800501e-15
|
| 40 |
+
- -1.4210854715202004e-14
|
| 41 |
+
- -3.552713678800501e-15
|
| 42 |
+
- -6.096600969433027
|
| 43 |
+
- -8.288307365078008
|
| 44 |
+
- -8.377401871630358
|
| 45 |
+
- -4.73277382959691
|
| 46 |
+
- -4.218847493575595e-15
|
| 47 |
+
- 8.881784197001252e-15
|
| 48 |
+
- -1.5758719946770752
|
| 49 |
+
- 3.552713678800501e-15
|
| 50 |
+
- -3.605510955946432
|
| 51 |
+
- -5.341968801697664
|
| 52 |
+
- -5.26720503097145
|
| 53 |
+
- -4.566219949129778
|
| 54 |
+
- -3.0961819892844145
|
| 55 |
+
- 0.0
|
| 56 |
+
- -1.5790045471096414
|
| 57 |
+
- -2.4721953289673877
|
| 58 |
+
- -6.6807503507860915
|
| 59 |
+
- -7.74796841292303
|
| 60 |
+
- -8.403945117874883
|
| 61 |
+
- -8.507943997230633
|
| 62 |
+
- -7.859727128191719
|
| 63 |
+
- -7.202700747006435
|
| 64 |
+
- -6.283218856635957
|
| 65 |
+
- -4.8972399918354865
|
| 66 |
+
- -2.9438503901650144
|
| 67 |
+
- -0.6766002592520115
|
| 68 |
+
- -2.577391165910655
|
| 69 |
+
- -4.06962014738207
|
| 70 |
+
- -4.497309369632559
|
| 71 |
+
- -3.780665648345848
|
| 72 |
+
- -5.329070518200751e-15
|
| 73 |
+
- -1.3322676295501878e-15
|
| 74 |
+
- -1.4256648977370752
|
| 75 |
+
- -2.412526481733912
|
| 76 |
+
- -7.014442857905434
|
| 77 |
+
- -8.512272383088028
|
| 78 |
+
- -9.592355889199172
|
| 79 |
+
- -9.910578824883942
|
| 80 |
+
- -9.267018354580234
|
| 81 |
+
- -8.354773494931816
|
| 82 |
+
- -6.854409258193916
|
| 83 |
+
- -4.840002859370445
|
| 84 |
+
- -1.918019107887611
|
| 85 |
+
- -0.23821365616560497
|
| 86 |
+
- -2.0561553805596358
|
| 87 |
+
- -3.3842132848566258
|
| 88 |
+
- -3.667988196267832
|
| 89 |
+
- -3.032988525381068
|
| 90 |
+
- 2.220446049250313e-16
|
| 91 |
+
- 0.0
|
| 92 |
+
- -1.6559264334420458
|
| 93 |
+
- 0.0
|
| 94 |
+
- 0.0
|
| 95 |
+
- 0.0
|
| 96 |
+
- 0.0
|
| 97 |
+
- 0.0
|
| 98 |
+
- 0.0
|
| 99 |
+
- 0.0
|
| 100 |
+
- 0.0
|
| 101 |
+
- 0.0
|
| 102 |
+
- 0.0
|
| 103 |
+
- 0.0
|
| 104 |
+
- 0.0
|
| 105 |
+
- 0.0
|
| 106 |
+
- 0.0
|
| 107 |
+
- 0.0
|
| 108 |
+
- 0.0
|
| 109 |
+
- -9.875111813171198
|
| 110 |
+
- -11.052180114376393
|
| 111 |
+
- -11.621851352916064
|
| 112 |
+
- -11.117519848695258
|
| 113 |
+
- -10.219707389902464
|
| 114 |
+
- -8.357987841676932
|
| 115 |
+
- -5.796390731438155
|
| 116 |
+
- -2.701232714681403
|
| 117 |
+
- 0.32034288848399406
|
| 118 |
+
- -1.8259293180072989
|
| 119 |
+
- -2.991135014870159
|
| 120 |
+
- -3.3375851657056166
|
| 121 |
+
- 0.0
|
| 122 |
+
- 0.0
|
| 123 |
+
- 0.0
|
| 124 |
+
- 0.0
|
| 125 |
+
- 0.0
|
| 126 |
+
- 0.0
|
| 127 |
+
- 0.0
|
| 128 |
+
- 0.0
|
| 129 |
+
- 0.0
|
| 130 |
+
- 0.0
|
| 131 |
+
- 0.0
|
| 132 |
+
- 0.0
|
| 133 |
+
- 0.0
|
| 134 |
+
- 0.0
|
| 135 |
+
- 0.0
|
| 136 |
+
- 0.0
|
| 137 |
+
normalizer_rmsd: 0.3184592617813517
|
| 138 |
+
train_dataset:
|
| 139 |
+
splits:
|
| 140 |
+
train:
|
| 141 |
+
src: ${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/train
|
| 142 |
+
format: ase_db
|
| 143 |
+
transforms:
|
| 144 |
+
common_transform:
|
| 145 |
+
dataset_name: ${data.dataset_name}
|
| 146 |
+
stress_reshape_transform:
|
| 147 |
+
dataset_name: ${data.dataset_name}
|
| 148 |
+
val_dataset:
|
| 149 |
+
splits:
|
| 150 |
+
val:
|
| 151 |
+
src: ${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/val
|
| 152 |
+
format: ase_db
|
| 153 |
+
transforms:
|
| 154 |
+
common_transform:
|
| 155 |
+
dataset_name: ${data.dataset_name}
|
| 156 |
+
stress_reshape_transform:
|
| 157 |
+
dataset_name: ${data.dataset_name}
|
| 158 |
+
regress_stress: false
|
| 159 |
+
pass_through_head_outputs: true
|
| 160 |
+
heads:
|
| 161 |
+
efs:
|
| 162 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 163 |
+
tasks_list:
|
| 164 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 165 |
+
name: energy
|
| 166 |
+
level: system
|
| 167 |
+
property: energy
|
| 168 |
+
loss_fn:
|
| 169 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 170 |
+
loss_fn:
|
| 171 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 172 |
+
coefficient: 20
|
| 173 |
+
out_spec:
|
| 174 |
+
dim:
|
| 175 |
+
- 1
|
| 176 |
+
dtype: float32
|
| 177 |
+
normalizer:
|
| 178 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 179 |
+
mean: 0.0
|
| 180 |
+
rmsd: ${data.normalizer_rmsd}
|
| 181 |
+
element_references:
|
| 182 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 183 |
+
element_references:
|
| 184 |
+
_target_: torch.DoubleTensor
|
| 185 |
+
_args_:
|
| 186 |
+
- ${data.elem_refs}
|
| 187 |
+
datasets:
|
| 188 |
+
- ${data.dataset_name}
|
| 189 |
+
metrics:
|
| 190 |
+
- mae
|
| 191 |
+
- per_atom_mae
|
| 192 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 193 |
+
name: forces
|
| 194 |
+
level: atom
|
| 195 |
+
property: forces
|
| 196 |
+
train_on_free_atoms: true
|
| 197 |
+
eval_on_free_atoms: true
|
| 198 |
+
loss_fn:
|
| 199 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 200 |
+
loss_fn:
|
| 201 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 202 |
+
reduction: mean
|
| 203 |
+
coefficient: 2
|
| 204 |
+
out_spec:
|
| 205 |
+
dim:
|
| 206 |
+
- 3
|
| 207 |
+
dtype: float32
|
| 208 |
+
normalizer:
|
| 209 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 210 |
+
mean: 0.0
|
| 211 |
+
rmsd: ${data.normalizer_rmsd}
|
| 212 |
+
datasets:
|
| 213 |
+
- ${data.dataset_name}
|
| 214 |
+
metrics:
|
| 215 |
+
- mae
|
| 216 |
+
- cosine_similarity
|
| 217 |
+
- magnitude_error
|
| 218 |
+
train_dataset:
|
| 219 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 220 |
+
dataset_configs:
|
| 221 |
+
finetune_dataset: ${data.train_dataset}
|
| 222 |
+
combined_dataset_config:
|
| 223 |
+
sampling:
|
| 224 |
+
type: temperature
|
| 225 |
+
temperature: 1.0
|
| 226 |
+
train_dataloader:
|
| 227 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 228 |
+
dataset: ${train_dataset}
|
| 229 |
+
batch_sampler_fn:
|
| 230 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 231 |
+
_partial_: true
|
| 232 |
+
batch_size: ${batch_size}
|
| 233 |
+
shuffle: true
|
| 234 |
+
seed: 0
|
| 235 |
+
num_workers: 0
|
| 236 |
+
collate_fn:
|
| 237 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 238 |
+
tasks: ${data.tasks_list}
|
| 239 |
+
exclude_keys: []
|
| 240 |
+
val_dataset:
|
| 241 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 242 |
+
dataset_configs:
|
| 243 |
+
finetune_dataset: ${data.val_dataset}
|
| 244 |
+
combined_dataset_config:
|
| 245 |
+
sampling:
|
| 246 |
+
type: temperature
|
| 247 |
+
temperature: 1.0
|
| 248 |
+
eval_dataloader:
|
| 249 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 250 |
+
dataset: ${val_dataset}
|
| 251 |
+
batch_sampler_fn:
|
| 252 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 253 |
+
_partial_: true
|
| 254 |
+
batch_size: ${batch_size}
|
| 255 |
+
shuffle: false
|
| 256 |
+
seed: 0
|
| 257 |
+
num_workers: 0
|
| 258 |
+
collate_fn:
|
| 259 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 260 |
+
tasks: ${data.tasks_list}
|
| 261 |
+
exclude_keys: []
|
| 262 |
+
runner:
|
| 263 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 264 |
+
train_dataloader: ${train_dataloader}
|
| 265 |
+
eval_dataloader: ${eval_dataloader}
|
| 266 |
+
train_eval_unit:
|
| 267 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 268 |
+
job_config: ${job}
|
| 269 |
+
tasks: ${data.tasks_list}
|
| 270 |
+
model:
|
| 271 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 272 |
+
checkpoint_location: ${ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt
|
| 273 |
+
overrides:
|
| 274 |
+
backbone:
|
| 275 |
+
otf_graph: true
|
| 276 |
+
max_neighbors: ${max_neighbors}
|
| 277 |
+
regress_stress: ${data.regress_stress}
|
| 278 |
+
always_use_pbc: false
|
| 279 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 280 |
+
heads: ${data.heads}
|
| 281 |
+
optimizer_fn:
|
| 282 |
+
_target_: torch.optim.AdamW
|
| 283 |
+
_partial_: true
|
| 284 |
+
lr: ${lr}
|
| 285 |
+
weight_decay: ${weight_decay}
|
| 286 |
+
cosine_lr_scheduler_fn:
|
| 287 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 288 |
+
_partial_: true
|
| 289 |
+
warmup_factor: 0.2
|
| 290 |
+
warmup_epochs: 0.01
|
| 291 |
+
lr_min_factor: 0.01
|
| 292 |
+
epochs: ${epochs}
|
| 293 |
+
steps: ${steps}
|
| 294 |
+
print_every: 10
|
| 295 |
+
clip_grad_norm: 100
|
| 296 |
+
max_epochs: ${epochs}
|
| 297 |
+
max_steps: ${steps}
|
| 298 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 299 |
+
callbacks:
|
| 300 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 301 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 302 |
+
max_saved_checkpoints: 5
|
| 303 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/outputs/oc20_ef_4dcu_20260721_114458/hydra_config.yaml
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
job:
|
| 2 |
+
device_type: CUDA
|
| 3 |
+
scheduler:
|
| 4 |
+
mode: LOCAL
|
| 5 |
+
ranks_per_node: 4
|
| 6 |
+
num_nodes: 1
|
| 7 |
+
debug: true
|
| 8 |
+
run_dir: ./uma_finetune_runs/
|
| 9 |
+
run_name: oc20_ef_4dcu
|
| 10 |
+
logger: null
|
| 11 |
+
max_neighbors: 300
|
| 12 |
+
epochs: 1
|
| 13 |
+
steps: null
|
| 14 |
+
batch_size: 2
|
| 15 |
+
lr: 0.0004
|
| 16 |
+
weight_decay: 0.001
|
| 17 |
+
evaluate_every_n_steps: 100
|
| 18 |
+
checkpoint_every_n_steps: null
|
| 19 |
+
data:
|
| 20 |
+
dataset_name: oc20
|
| 21 |
+
elem_refs:
|
| 22 |
+
- -9.01836389963216e-17
|
| 23 |
+
- -3.384793156087369
|
| 24 |
+
- -3.552713678800501e-15
|
| 25 |
+
- -1.4210854715202004e-14
|
| 26 |
+
- -3.552713678800501e-15
|
| 27 |
+
- -6.096600969433027
|
| 28 |
+
- -8.288307365078008
|
| 29 |
+
- -8.377401871630358
|
| 30 |
+
- -4.73277382959691
|
| 31 |
+
- -4.218847493575595e-15
|
| 32 |
+
- 8.881784197001252e-15
|
| 33 |
+
- -1.5758719946770752
|
| 34 |
+
- 3.552713678800501e-15
|
| 35 |
+
- -3.605510955946432
|
| 36 |
+
- -5.341968801697664
|
| 37 |
+
- -5.26720503097145
|
| 38 |
+
- -4.566219949129778
|
| 39 |
+
- -3.0961819892844145
|
| 40 |
+
- 0.0
|
| 41 |
+
- -1.5790045471096414
|
| 42 |
+
- -2.4721953289673877
|
| 43 |
+
- -6.6807503507860915
|
| 44 |
+
- -7.74796841292303
|
| 45 |
+
- -8.403945117874883
|
| 46 |
+
- -8.507943997230633
|
| 47 |
+
- -7.859727128191719
|
| 48 |
+
- -7.202700747006435
|
| 49 |
+
- -6.283218856635957
|
| 50 |
+
- -4.8972399918354865
|
| 51 |
+
- -2.9438503901650144
|
| 52 |
+
- -0.6766002592520115
|
| 53 |
+
- -2.577391165910655
|
| 54 |
+
- -4.06962014738207
|
| 55 |
+
- -4.497309369632559
|
| 56 |
+
- -3.780665648345848
|
| 57 |
+
- -5.329070518200751e-15
|
| 58 |
+
- -1.3322676295501878e-15
|
| 59 |
+
- -1.4256648977370752
|
| 60 |
+
- -2.412526481733912
|
| 61 |
+
- -7.014442857905434
|
| 62 |
+
- -8.512272383088028
|
| 63 |
+
- -9.592355889199172
|
| 64 |
+
- -9.910578824883942
|
| 65 |
+
- -9.267018354580234
|
| 66 |
+
- -8.354773494931816
|
| 67 |
+
- -6.854409258193916
|
| 68 |
+
- -4.840002859370445
|
| 69 |
+
- -1.918019107887611
|
| 70 |
+
- -0.23821365616560497
|
| 71 |
+
- -2.0561553805596358
|
| 72 |
+
- -3.3842132848566258
|
| 73 |
+
- -3.667988196267832
|
| 74 |
+
- -3.032988525381068
|
| 75 |
+
- 2.220446049250313e-16
|
| 76 |
+
- 0.0
|
| 77 |
+
- -1.6559264334420458
|
| 78 |
+
- 0.0
|
| 79 |
+
- 0.0
|
| 80 |
+
- 0.0
|
| 81 |
+
- 0.0
|
| 82 |
+
- 0.0
|
| 83 |
+
- 0.0
|
| 84 |
+
- 0.0
|
| 85 |
+
- 0.0
|
| 86 |
+
- 0.0
|
| 87 |
+
- 0.0
|
| 88 |
+
- 0.0
|
| 89 |
+
- 0.0
|
| 90 |
+
- 0.0
|
| 91 |
+
- 0.0
|
| 92 |
+
- 0.0
|
| 93 |
+
- 0.0
|
| 94 |
+
- -9.875111813171198
|
| 95 |
+
- -11.052180114376393
|
| 96 |
+
- -11.621851352916064
|
| 97 |
+
- -11.117519848695258
|
| 98 |
+
- -10.219707389902464
|
| 99 |
+
- -8.357987841676932
|
| 100 |
+
- -5.796390731438155
|
| 101 |
+
- -2.701232714681403
|
| 102 |
+
- 0.32034288848399406
|
| 103 |
+
- -1.8259293180072989
|
| 104 |
+
- -2.991135014870159
|
| 105 |
+
- -3.3375851657056166
|
| 106 |
+
- 0.0
|
| 107 |
+
- 0.0
|
| 108 |
+
- 0.0
|
| 109 |
+
- 0.0
|
| 110 |
+
- 0.0
|
| 111 |
+
- 0.0
|
| 112 |
+
- 0.0
|
| 113 |
+
- 0.0
|
| 114 |
+
- 0.0
|
| 115 |
+
- 0.0
|
| 116 |
+
- 0.0
|
| 117 |
+
- 0.0
|
| 118 |
+
- 0.0
|
| 119 |
+
- 0.0
|
| 120 |
+
- 0.0
|
| 121 |
+
- 0.0
|
| 122 |
+
normalizer_rmsd: 0.3184592617813517
|
| 123 |
+
train_dataset:
|
| 124 |
+
splits:
|
| 125 |
+
train:
|
| 126 |
+
src: ${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/train
|
| 127 |
+
format: ase_db
|
| 128 |
+
transforms:
|
| 129 |
+
common_transform:
|
| 130 |
+
dataset_name: ${data.dataset_name}
|
| 131 |
+
stress_reshape_transform:
|
| 132 |
+
dataset_name: ${data.dataset_name}
|
| 133 |
+
val_dataset:
|
| 134 |
+
splits:
|
| 135 |
+
val:
|
| 136 |
+
src: ${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/val
|
| 137 |
+
format: ase_db
|
| 138 |
+
transforms:
|
| 139 |
+
common_transform:
|
| 140 |
+
dataset_name: ${data.dataset_name}
|
| 141 |
+
stress_reshape_transform:
|
| 142 |
+
dataset_name: ${data.dataset_name}
|
| 143 |
+
regress_stress: false
|
| 144 |
+
pass_through_head_outputs: true
|
| 145 |
+
heads:
|
| 146 |
+
efs:
|
| 147 |
+
module: onescience.modules.head.uma_head.MLP_EFS_Head
|
| 148 |
+
tasks_list:
|
| 149 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 150 |
+
name: energy
|
| 151 |
+
level: system
|
| 152 |
+
property: energy
|
| 153 |
+
loss_fn:
|
| 154 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 155 |
+
loss_fn:
|
| 156 |
+
_target_: onescience.modules.loss.uma_loss.PerAtomMAELoss
|
| 157 |
+
coefficient: 20
|
| 158 |
+
out_spec:
|
| 159 |
+
dim:
|
| 160 |
+
- 1
|
| 161 |
+
dtype: float32
|
| 162 |
+
normalizer:
|
| 163 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 164 |
+
mean: 0.0
|
| 165 |
+
rmsd: ${data.normalizer_rmsd}
|
| 166 |
+
element_references:
|
| 167 |
+
_target_: onescience.utils.uma.normalization.element_references.ElementReferences
|
| 168 |
+
element_references:
|
| 169 |
+
_target_: torch.DoubleTensor
|
| 170 |
+
_args_:
|
| 171 |
+
- ${data.elem_refs}
|
| 172 |
+
datasets:
|
| 173 |
+
- ${data.dataset_name}
|
| 174 |
+
metrics:
|
| 175 |
+
- mae
|
| 176 |
+
- per_atom_mae
|
| 177 |
+
- _target_: onescience.utils.uma.units.mlip_unit.mlip_unit.Task
|
| 178 |
+
name: forces
|
| 179 |
+
level: atom
|
| 180 |
+
property: forces
|
| 181 |
+
train_on_free_atoms: true
|
| 182 |
+
eval_on_free_atoms: true
|
| 183 |
+
loss_fn:
|
| 184 |
+
_target_: onescience.modules.loss.uma_loss.DDPMTLoss
|
| 185 |
+
loss_fn:
|
| 186 |
+
_target_: onescience.modules.loss.uma_loss.L2NormLoss
|
| 187 |
+
reduction: mean
|
| 188 |
+
coefficient: 2
|
| 189 |
+
out_spec:
|
| 190 |
+
dim:
|
| 191 |
+
- 3
|
| 192 |
+
dtype: float32
|
| 193 |
+
normalizer:
|
| 194 |
+
_target_: onescience.utils.uma.normalization.normalizer.Normalizer
|
| 195 |
+
mean: 0.0
|
| 196 |
+
rmsd: ${data.normalizer_rmsd}
|
| 197 |
+
datasets:
|
| 198 |
+
- ${data.dataset_name}
|
| 199 |
+
metrics:
|
| 200 |
+
- mae
|
| 201 |
+
- cosine_similarity
|
| 202 |
+
- magnitude_error
|
| 203 |
+
train_dataset:
|
| 204 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 205 |
+
dataset_configs:
|
| 206 |
+
finetune_dataset: ${data.train_dataset}
|
| 207 |
+
combined_dataset_config:
|
| 208 |
+
sampling:
|
| 209 |
+
type: temperature
|
| 210 |
+
temperature: 1.0
|
| 211 |
+
train_dataloader:
|
| 212 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 213 |
+
dataset: ${train_dataset}
|
| 214 |
+
batch_sampler_fn:
|
| 215 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 216 |
+
_partial_: true
|
| 217 |
+
batch_size: ${batch_size}
|
| 218 |
+
shuffle: true
|
| 219 |
+
seed: 0
|
| 220 |
+
num_workers: 0
|
| 221 |
+
collate_fn:
|
| 222 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 223 |
+
tasks: ${data.tasks_list}
|
| 224 |
+
exclude_keys: []
|
| 225 |
+
val_dataset:
|
| 226 |
+
_target_: onescience.datapipes.materials.custom_stack.storage.mt_concat_dataset.create_concat_dataset
|
| 227 |
+
dataset_configs:
|
| 228 |
+
finetune_dataset: ${data.val_dataset}
|
| 229 |
+
combined_dataset_config:
|
| 230 |
+
sampling:
|
| 231 |
+
type: temperature
|
| 232 |
+
temperature: 1.0
|
| 233 |
+
eval_dataloader:
|
| 234 |
+
_target_: onescience.datapipes.materials.uma.dataloader_builder.get_dataloader
|
| 235 |
+
dataset: ${val_dataset}
|
| 236 |
+
batch_sampler_fn:
|
| 237 |
+
_target_: onescience.utils.uma.common.data_parallel.BalancedBatchSampler
|
| 238 |
+
_partial_: true
|
| 239 |
+
batch_size: ${batch_size}
|
| 240 |
+
shuffle: false
|
| 241 |
+
seed: 0
|
| 242 |
+
num_workers: 0
|
| 243 |
+
collate_fn:
|
| 244 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.mt_collater_adapter
|
| 245 |
+
tasks: ${data.tasks_list}
|
| 246 |
+
exclude_keys: []
|
| 247 |
+
runner:
|
| 248 |
+
_target_: onescience.utils.uma.components.train.train_runner.TrainEvalRunner
|
| 249 |
+
train_dataloader: ${train_dataloader}
|
| 250 |
+
eval_dataloader: ${eval_dataloader}
|
| 251 |
+
train_eval_unit:
|
| 252 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
|
| 253 |
+
job_config: ${job}
|
| 254 |
+
tasks: ${data.tasks_list}
|
| 255 |
+
model:
|
| 256 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit.initialize_finetuning_model
|
| 257 |
+
checkpoint_location: ${ONESCIENCE_MODELS_DIR}/weight/uma-s-1p1_converted.pt
|
| 258 |
+
overrides:
|
| 259 |
+
backbone:
|
| 260 |
+
otf_graph: true
|
| 261 |
+
max_neighbors: ${max_neighbors}
|
| 262 |
+
regress_stress: ${data.regress_stress}
|
| 263 |
+
always_use_pbc: false
|
| 264 |
+
pass_through_head_outputs: ${data.pass_through_head_outputs}
|
| 265 |
+
heads: ${data.heads}
|
| 266 |
+
optimizer_fn:
|
| 267 |
+
_target_: torch.optim.AdamW
|
| 268 |
+
_partial_: true
|
| 269 |
+
lr: ${lr}
|
| 270 |
+
weight_decay: ${weight_decay}
|
| 271 |
+
cosine_lr_scheduler_fn:
|
| 272 |
+
_target_: onescience.utils.uma.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
|
| 273 |
+
_partial_: true
|
| 274 |
+
warmup_factor: 0.2
|
| 275 |
+
warmup_epochs: 0.01
|
| 276 |
+
lr_min_factor: 0.01
|
| 277 |
+
epochs: ${epochs}
|
| 278 |
+
steps: ${steps}
|
| 279 |
+
print_every: 10
|
| 280 |
+
clip_grad_norm: 100
|
| 281 |
+
max_epochs: ${epochs}
|
| 282 |
+
max_steps: ${steps}
|
| 283 |
+
evaluate_every_n_steps: ${evaluate_every_n_steps}
|
| 284 |
+
callbacks:
|
| 285 |
+
- _target_: onescience.utils.uma.components.train.train_runner.TrainCheckpointCallback
|
| 286 |
+
checkpoint_every_n_steps: ${checkpoint_every_n_steps}
|
| 287 |
+
max_saved_checkpoints: 5
|
| 288 |
+
- _target_: torchtnt.framework.callbacks.TQDMProgressBar
|
demo/outputs/oc20_ef_4dcu_20260721_114458/models/pretrained_models.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"uma-s-1": {
|
| 3 |
+
"filename": "uma-s-1.pt",
|
| 4 |
+
"repo_id": "facebook/UMA",
|
| 5 |
+
"subfolder": "checkpoints",
|
| 6 |
+
"atom_refs": {
|
| 7 |
+
"subfolder": "references",
|
| 8 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 9 |
+
}
|
| 10 |
+
},
|
| 11 |
+
"uma-s-1p1": {
|
| 12 |
+
"filename": "uma-s-1p1.pt",
|
| 13 |
+
"repo_id": "facebook/UMA",
|
| 14 |
+
"subfolder": "checkpoints",
|
| 15 |
+
"atom_refs": {
|
| 16 |
+
"subfolder": "references",
|
| 17 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"uma-m-1p1": {
|
| 21 |
+
"filename": "uma-m-1p1.pt",
|
| 22 |
+
"repo_id": "facebook/UMA",
|
| 23 |
+
"subfolder": "checkpoints",
|
| 24 |
+
"atom_refs": {
|
| 25 |
+
"subfolder": "references",
|
| 26 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"esen-md-direct-all-omol": {
|
| 30 |
+
"filename": "esen_md_direct_all.pt",
|
| 31 |
+
"repo_id": "facebook/OMol25",
|
| 32 |
+
"subfolder": "checkpoints",
|
| 33 |
+
"atom_refs": {
|
| 34 |
+
"subfolder": "references",
|
| 35 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"esen-sm-conserving-all-omol": {
|
| 39 |
+
"filename": "esen_sm_conserving_all.pt",
|
| 40 |
+
"repo_id": "facebook/OMol25",
|
| 41 |
+
"subfolder": "checkpoints",
|
| 42 |
+
"atom_refs": {
|
| 43 |
+
"subfolder": "references",
|
| 44 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
"esen-sm-direct-all-omol": {
|
| 48 |
+
"filename": "esen_sm_direct_all.pt",
|
| 49 |
+
"repo_id": "facebook/OMol25",
|
| 50 |
+
"subfolder": "checkpoints",
|
| 51 |
+
"atom_refs": {
|
| 52 |
+
"subfolder": "references",
|
| 53 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
demo/run.sh
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# run.sh - UMA 训练统一入口脚本
|
| 3 |
+
#
|
| 4 |
+
# 用法:
|
| 5 |
+
# bash run.sh --config configs/oc20_ef_4dcu.yaml # 直接运行训练
|
| 6 |
+
# bash run.sh --config configs/oc20_ef_4dcu.yaml --submit # 提交 SLURM 作业
|
| 7 |
+
# bash run.sh --config configs/oc20_ef_4dcu.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 |
+
# 如果用户没有设置 ONESCIENCE_MODELS_DIR,默认也指向仓库根目录(用于定位 weight/ 下权重)
|
| 63 |
+
export ONESCIENCE_MODELS_DIR="${ONESCIENCE_MODELS_DIR:-$REPO_ROOT}"
|
| 64 |
+
|
| 65 |
+
# ============================================================
|
| 66 |
+
# 解析 meta 部分 (不依赖 hydra_config 路径)
|
| 67 |
+
# ============================================================
|
| 68 |
+
EXP_NAME=$(python3 "$PARSE_PY" "$CONFIG_ABS" name)
|
| 69 |
+
ENV_EXPORTS=$(python3 "$PARSE_PY" "$CONFIG_ABS" env)
|
| 70 |
+
ENV_ARGS=$(python3 "$PARSE_PY" "$CONFIG_ABS" env-args)
|
| 71 |
+
DATA_FILES=$(python3 "$PARSE_PY" "$CONFIG_ABS" data-files)
|
| 72 |
+
|
| 73 |
+
# ============================================================
|
| 74 |
+
# 预生成输出目录与 hydra_config.yaml(dry-run 也生成,便于预览)
|
| 75 |
+
# ============================================================
|
| 76 |
+
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
| 77 |
+
OUTPUT_DIR="$DEMO_DIR/outputs/${EXP_NAME}_${TIMESTAMP}"
|
| 78 |
+
HYDRA_CONFIG="$OUTPUT_DIR/hydra_config.yaml"
|
| 79 |
+
|
| 80 |
+
# dry-run 不实际创建目录
|
| 81 |
+
if $DRY_RUN; then
|
| 82 |
+
DRY_HYDRA_TMP=$(mktemp -t "uma_hydra_XXXXXX.yaml")
|
| 83 |
+
HYDRA_CONFIG="$DRY_HYDRA_TMP"
|
| 84 |
+
python3 "$PARSE_PY" "$CONFIG_ABS" hydra-config > "$HYDRA_CONFIG"
|
| 85 |
+
else
|
| 86 |
+
mkdir -p "$OUTPUT_DIR"
|
| 87 |
+
cp "$CONFIG_ABS" "$OUTPUT_DIR/config.yaml"
|
| 88 |
+
python3 "$PARSE_PY" "$CONFIG_ABS" hydra-config > "$HYDRA_CONFIG"
|
| 89 |
+
# UMA/calculate/pretrained_mlip.py 使用 os.getcwd()+"/models/pretrained_models.json"
|
| 90 |
+
# 定位预训练模型清单, 而 run.sh 会 cd 到 OUTPUT_DIR 再启动训练, 所以在 OUTPUT_DIR
|
| 91 |
+
# 下放一个指回 UMA/models-json 的软链 (软链名仍为 models), 保证 cwd 相对路径能找到 json。
|
| 92 |
+
UMA_ROOT_DIR="$(cd "$DEMO_DIR/.." && pwd)"
|
| 93 |
+
if [ -d "$UMA_ROOT_DIR/models-json" ] && [ ! -e "$OUTPUT_DIR/models" ]; then
|
| 94 |
+
ln -s "$UMA_ROOT_DIR/models-json" "$OUTPUT_DIR/models"
|
| 95 |
+
fi
|
| 96 |
+
fi
|
| 97 |
+
|
| 98 |
+
# 现在 hydra_config 路径就绪, 拼 command
|
| 99 |
+
TRAIN_CMD=$(python3 "$PARSE_PY" "$CONFIG_ABS" command "$HYDRA_CONFIG")
|
| 100 |
+
|
| 101 |
+
# ============================================================
|
| 102 |
+
# Dry-run 模式
|
| 103 |
+
# ============================================================
|
| 104 |
+
if $DRY_RUN; then
|
| 105 |
+
echo "========================================="
|
| 106 |
+
echo "Dry-run: $EXP_NAME"
|
| 107 |
+
echo "Config: $CONFIG_ABS"
|
| 108 |
+
echo "Output: $OUTPUT_DIR (未创建)"
|
| 109 |
+
echo "Hydra: $HYDRA_CONFIG (临时)"
|
| 110 |
+
echo "========================================="
|
| 111 |
+
echo ""
|
| 112 |
+
echo "# 环境变量:"
|
| 113 |
+
echo "$ENV_EXPORTS"
|
| 114 |
+
echo ""
|
| 115 |
+
echo "# 训练命令:"
|
| 116 |
+
echo "$TRAIN_CMD"
|
| 117 |
+
echo ""
|
| 118 |
+
echo "# env_setup.sh 参数: $ENV_ARGS"
|
| 119 |
+
echo "# 预检路径:"
|
| 120 |
+
echo "$DATA_FILES" | sed 's/^/# /'
|
| 121 |
+
echo ""
|
| 122 |
+
echo "# hydra_config.yaml 预览 (前 40 行):"
|
| 123 |
+
head -n 40 "$HYDRA_CONFIG" | sed 's/^/# /'
|
| 124 |
+
rm -f "$HYDRA_CONFIG"
|
| 125 |
+
exit 0
|
| 126 |
+
fi
|
| 127 |
+
|
| 128 |
+
# ============================================================
|
| 129 |
+
# SLURM 提交模式
|
| 130 |
+
# ============================================================
|
| 131 |
+
if $SUBMIT; then
|
| 132 |
+
SLURM_VARS=$(python3 "$PARSE_PY" "$CONFIG_ABS" slurm)
|
| 133 |
+
eval "$SLURM_VARS"
|
| 134 |
+
|
| 135 |
+
SLURM_SCRIPT="$OUTPUT_DIR/submit.sh"
|
| 136 |
+
|
| 137 |
+
# 生成 SLURM header
|
| 138 |
+
sed -e "s|{{JOB_NAME}}|${JOB_NAME}|g" \
|
| 139 |
+
-e "s|{{PARTITION}}|${PARTITION}|g" \
|
| 140 |
+
-e "s|{{NODES}}|${NODES}|g" \
|
| 141 |
+
-e "s|{{NTASKS_PER_NODE}}|${NTASKS_PER_NODE}|g" \
|
| 142 |
+
-e "s|{{CPUS_PER_TASK}}|${CPUS_PER_TASK}|g" \
|
| 143 |
+
-e "s|{{GPUS_PER_NODE}}|${GPUS_PER_NODE}|g" \
|
| 144 |
+
-e "s|{{TIME}}|${TIME}|g" \
|
| 145 |
+
"$DEMO_DIR/templates/slurm_header.template" > "$SLURM_SCRIPT"
|
| 146 |
+
|
| 147 |
+
# 环境初始化
|
| 148 |
+
cat >> "$SLURM_SCRIPT" << 'SETUP_BLOCK'
|
| 149 |
+
|
| 150 |
+
# 环境初始化
|
| 151 |
+
SETUP_BLOCK
|
| 152 |
+
cat >> "$SLURM_SCRIPT" << 'ENV_BLOCK'
|
| 153 |
+
set +u
|
| 154 |
+
if [ -n "${UMA_ENV_SCRIPT:-}" ] && [ -f "${UMA_ENV_SCRIPT}" ]; then
|
| 155 |
+
source "${UMA_ENV_SCRIPT}"
|
| 156 |
+
else
|
| 157 |
+
echo "[WARN] UMA_ENV_SCRIPT 未设置或文件不存在,跳过环境初始化。请自行确保 conda/matchem 环境已激活。"
|
| 158 |
+
fi
|
| 159 |
+
set -u
|
| 160 |
+
ENV_BLOCK
|
| 161 |
+
|
| 162 |
+
# 预检
|
| 163 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 164 |
+
echo "# 预检" >> "$SLURM_SCRIPT"
|
| 165 |
+
DATA_ARGS=""
|
| 166 |
+
while IFS= read -r line; do
|
| 167 |
+
[ -z "$line" ] && continue
|
| 168 |
+
DATA_ARGS="$DATA_ARGS \"$line\""
|
| 169 |
+
done <<< "$DATA_FILES"
|
| 170 |
+
echo "bash $DEMO_DIR/templates/preflight_check.sh $DATA_ARGS" >> "$SLURM_SCRIPT"
|
| 171 |
+
|
| 172 |
+
# 工作目录与环境变量
|
| 173 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 174 |
+
echo "# 工作目录" >> "$SLURM_SCRIPT"
|
| 175 |
+
echo "cd $OUTPUT_DIR" >> "$SLURM_SCRIPT"
|
| 176 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 177 |
+
echo "# 环境变量" >> "$SLURM_SCRIPT"
|
| 178 |
+
echo "$ENV_EXPORTS" >> "$SLURM_SCRIPT"
|
| 179 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 180 |
+
echo "# 将仓库根目录加入 PYTHONPATH,确保能 import 本地 model 包" >> "$SLURM_SCRIPT"
|
| 181 |
+
echo "export PYTHONPATH=\"$REPO_ROOT:\${PYTHONPATH:-}\"" >> "$SLURM_SCRIPT"
|
| 182 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 183 |
+
echo "# 屏蔽 PyTorch NCCL C++ INFO 日志" >> "$SLURM_SCRIPT"
|
| 184 |
+
echo "export TORCH_CPP_LOG_LEVEL=WARNING" >> "$SLURM_SCRIPT"
|
| 185 |
+
echo "export NCCL_DEBUG=ERROR" >> "$SLURM_SCRIPT"
|
| 186 |
+
echo "export GLOG_minloglevel=1" >> "$SLURM_SCRIPT"
|
| 187 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 188 |
+
echo "# AMD DCU: 避免 RCCL \"Missing HSA_FORCE_FINE_GRAIN_PCIE\" 警告" >> "$SLURM_SCRIPT"
|
| 189 |
+
echo "export HSA_FORCE_FINE_GRAIN_PCIE=1" >> "$SLURM_SCRIPT"
|
| 190 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 191 |
+
echo "# UMA 依赖: 清理 rocblas tensile 路径, 避免拉到错版本" >> "$SLURM_SCRIPT"
|
| 192 |
+
echo "unset ROCBLAS_TENSILE_LIBPATH" >> "$SLURM_SCRIPT"
|
| 193 |
+
|
| 194 |
+
# 多节点特殊处理: 设置 MASTER_ADDR/PORT, 用 srun 包裹
|
| 195 |
+
if [ "$NODES" -gt 1 ]; then
|
| 196 |
+
cat >> "$SLURM_SCRIPT" << 'MULTI_NODE'
|
| 197 |
+
|
| 198 |
+
# 多节点分布式设置
|
| 199 |
+
nodes=$(scontrol show hostnames "$SLURM_JOB_NODELIST")
|
| 200 |
+
nodes_array=($nodes)
|
| 201 |
+
export MASTER_ADDR=${nodes_array[0]}
|
| 202 |
+
export MASTER_PORT=29504
|
| 203 |
+
echo "SLURM_NNODES=$SLURM_NNODES"
|
| 204 |
+
echo "MASTER_ADDR=$MASTER_ADDR"
|
| 205 |
+
echo "MASTER_PORT=$MASTER_PORT"
|
| 206 |
+
|
| 207 |
+
# srun 启动分布式训练 (每节点 1 个 task, torchrun 在节点内 spawn 多个 rank)
|
| 208 |
+
MULTI_NODE
|
| 209 |
+
echo "srun --nodes=\$SLURM_NNODES --ntasks=\$SLURM_NNODES \\" >> "$SLURM_SCRIPT"
|
| 210 |
+
# 把 TRAIN_CMD 原样追加 (其中的 \${SLURM_*} / \${MASTER_*} 会在每节点上展开)
|
| 211 |
+
echo " $TRAIN_CMD" >> "$SLURM_SCRIPT"
|
| 212 |
+
else
|
| 213 |
+
echo "" >> "$SLURM_SCRIPT"
|
| 214 |
+
echo "# 训练命令" >> "$SLURM_SCRIPT"
|
| 215 |
+
echo "$TRAIN_CMD" >> "$SLURM_SCRIPT"
|
| 216 |
+
fi
|
| 217 |
+
|
| 218 |
+
echo "========================================="
|
| 219 |
+
echo "SLURM 脚本已生成: $SLURM_SCRIPT"
|
| 220 |
+
echo "hydra 配置已生成: $HYDRA_CONFIG"
|
| 221 |
+
echo "配置快照已保存: $OUTPUT_DIR/config.yaml"
|
| 222 |
+
echo "========================================="
|
| 223 |
+
echo ""
|
| 224 |
+
echo "提交作业..."
|
| 225 |
+
sbatch "$SLURM_SCRIPT"
|
| 226 |
+
exit 0
|
| 227 |
+
fi
|
| 228 |
+
|
| 229 |
+
# ============================================================
|
| 230 |
+
# 直接运行模式
|
| 231 |
+
# ============================================================
|
| 232 |
+
echo "========================================="
|
| 233 |
+
echo "实验: $EXP_NAME"
|
| 234 |
+
echo "配置: $CONFIG_ABS"
|
| 235 |
+
echo "输出: $OUTPUT_DIR"
|
| 236 |
+
echo "Hydra: $HYDRA_CONFIG"
|
| 237 |
+
echo "========================================="
|
| 238 |
+
|
| 239 |
+
# 环境初始化
|
| 240 |
+
set +u
|
| 241 |
+
if [ -n "${UMA_ENV_SCRIPT:-}" ] && [ -f "${UMA_ENV_SCRIPT}" ]; then
|
| 242 |
+
source "${UMA_ENV_SCRIPT}"
|
| 243 |
+
else
|
| 244 |
+
echo "[WARN] UMA_ENV_SCRIPT 未设置或文件不存在,跳过环境初始化。请自行确保 conda/matchem 环境已激活。"
|
| 245 |
+
fi
|
| 246 |
+
set -u
|
| 247 |
+
|
| 248 |
+
# 预检
|
| 249 |
+
DATA_ARGS=""
|
| 250 |
+
while IFS= read -r line; do
|
| 251 |
+
[ -z "$line" ] && continue
|
| 252 |
+
DATA_ARGS="$DATA_ARGS \"$line\""
|
| 253 |
+
done <<< "$DATA_FILES"
|
| 254 |
+
eval "bash $DEMO_DIR/templates/preflight_check.sh $DATA_ARGS"
|
| 255 |
+
|
| 256 |
+
# 设置环境变量
|
| 257 |
+
eval "$ENV_EXPORTS"
|
| 258 |
+
|
| 259 |
+
# 将仓库根目录加入 PYTHONPATH,确保能 import 本地 model 包
|
| 260 |
+
export PYTHONPATH="$REPO_ROOT:${PYTHONPATH:-}"
|
| 261 |
+
|
| 262 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 263 |
+
UMA_JD_PATH="$REPO_ROOT/weight/Jd.pt"
|
| 264 |
+
if [ -f "$UMA_JD_PATH" ]; then
|
| 265 |
+
export ONESCIENCE_UMA_JD_PATH="$UMA_JD_PATH"
|
| 266 |
+
echo "[OK] 找到 Jd.pt: $UMA_JD_PATH"
|
| 267 |
+
else
|
| 268 |
+
echo "[WARN] 未找到 Jd.pt: $UMA_JD_PATH,如训练/推理报错请检查"
|
| 269 |
+
fi
|
| 270 |
+
|
| 271 |
+
# 屏蔽 PyTorch NCCL C++ INFO 日志
|
| 272 |
+
export TORCH_CPP_LOG_LEVEL=WARNING
|
| 273 |
+
export NCCL_DEBUG=ERROR
|
| 274 |
+
export GLOG_minloglevel=1
|
| 275 |
+
|
| 276 |
+
# AMD DCU
|
| 277 |
+
export HSA_FORCE_FINE_GRAIN_PCIE=1
|
| 278 |
+
unset ROCBLAS_TENSILE_LIBPATH
|
| 279 |
+
|
| 280 |
+
# 切换到输出目录
|
| 281 |
+
cd "$OUTPUT_DIR"
|
| 282 |
+
|
| 283 |
+
# 执行训练
|
| 284 |
+
# 执行训练(实时合并日志)
|
| 285 |
+
echo "========================================="
|
| 286 |
+
echo "开始训练..."
|
| 287 |
+
echo "========================================="
|
| 288 |
+
|
| 289 |
+
export PYTHONUNBUFFERED=1
|
| 290 |
+
MERGED_LOG="${OUTPUT_DIR}/train_merged.out"
|
| 291 |
+
: > "${MERGED_LOG}"
|
| 292 |
+
echo "[LOG] merged realtime log: ${MERGED_LOG}"
|
| 293 |
+
|
| 294 |
+
set +e
|
| 295 |
+
eval "$TRAIN_CMD" 2>&1 | tee -a "${MERGED_LOG}"
|
| 296 |
+
TRAIN_EXIT=${PIPESTATUS[0]}
|
| 297 |
+
set -e
|
| 298 |
+
|
| 299 |
+
exit "${TRAIN_EXIT}"
|
| 300 |
+
|
demo/templates/env_setup.sh
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# env_setup.sh - shared environment setup for UMA demo
|
| 3 |
+
# Usage: source templates/env_setup.sh <conda_env> <module1> [module2 ...]
|
| 4 |
+
|
| 5 |
+
CONDA_ENV="${1:?Please provide conda environment name}"
|
| 6 |
+
shift
|
| 7 |
+
|
| 8 |
+
# System scripts (bashrc/conda/module) may reference unset vars.
|
| 9 |
+
# Temporarily relax bash strict mode during bootstrap.
|
| 10 |
+
set +eu
|
| 11 |
+
|
| 12 |
+
module purge
|
| 13 |
+
source ~/.bashrc
|
| 14 |
+
conda activate "$CONDA_ENV"
|
| 15 |
+
|
| 16 |
+
for mod in "$@"; do
|
| 17 |
+
module load "$mod"
|
| 18 |
+
done
|
| 19 |
+
|
| 20 |
+
set -eu
|
| 21 |
+
|
| 22 |
+
# Load shared dataset/model roots from repo env.sh if it exists
|
| 23 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 24 |
+
DEMO_DIR="$(dirname "$SCRIPT_DIR")"
|
| 25 |
+
REPO_ROOT="$(cd "$DEMO_DIR/../.." && pwd)"
|
| 26 |
+
if [ -f "$REPO_ROOT/env.sh" ]; then
|
| 27 |
+
source "$REPO_ROOT/env.sh"
|
| 28 |
+
else
|
| 29 |
+
# Fallback: point dataset/model roots to repo root
|
| 30 |
+
export ONESCIENCE_DATASETS_DIR="${ONESCIENCE_DATASETS_DIR:-$REPO_ROOT}"
|
| 31 |
+
export ONESCIENCE_MODELS_DIR="${ONESCIENCE_MODELS_DIR:-$REPO_ROOT}"
|
| 32 |
+
fi
|
| 33 |
+
|
| 34 |
+
# UMA-specific runtime asset: Jd.pt
|
| 35 |
+
# 如仓库根目录 weight/ 下存在 Jd.pt,会自动设置;否则仅打印警告,不影响其他推理。
|
| 36 |
+
export ONESCIENCE_UMA_JD_PATH="${ONESCIENCE_MODELS_DIR}/weight/Jd.pt"
|
| 37 |
+
|
| 38 |
+
if [ -f "${ONESCIENCE_UMA_JD_PATH}" ]; then
|
| 39 |
+
echo "[OK] 文件: ${ONESCIENCE_UMA_JD_PATH}"
|
| 40 |
+
else
|
| 41 |
+
echo "[WARN] 文件不存在: ${ONESCIENCE_UMA_JD_PATH}"
|
| 42 |
+
fi
|
| 43 |
+
|
demo/templates/preflight_check.sh
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# preflight_check.sh - UMA 训练前预检脚本
|
| 3 |
+
# 用法: bash templates/preflight_check.sh [path1] [path2] ...
|
| 4 |
+
#
|
| 5 |
+
# 每个 path 可以是:
|
| 6 |
+
# - 普通文件 (如 checkpoint .pt)
|
| 7 |
+
# - 目录 (如 ASE-lmdb 的 train/ val/)
|
| 8 |
+
# 检查项:
|
| 9 |
+
# 1. ONESCIENCE_DATASETS_DIR 环境变量 (如果 env.sh 已 source)
|
| 10 |
+
# 2. 每个路径是否存在
|
| 11 |
+
# 3. DCU/GPU 状态
|
| 12 |
+
|
| 13 |
+
ERRORS=0
|
| 14 |
+
|
| 15 |
+
echo "========================================="
|
| 16 |
+
echo "Node: $(hostname)"
|
| 17 |
+
echo "Time: $(date)"
|
| 18 |
+
echo "========================================="
|
| 19 |
+
|
| 20 |
+
# 检查路径是否存在 (文件或目录均可)
|
| 21 |
+
for p in "$@"; do
|
| 22 |
+
[ -z "$p" ] && continue
|
| 23 |
+
expanded=$(eval echo "$p")
|
| 24 |
+
if [ ! -e "$expanded" ]; then
|
| 25 |
+
echo "[ERROR] 路径不存在: $expanded"
|
| 26 |
+
ERRORS=$((ERRORS + 1))
|
| 27 |
+
elif [ -d "$expanded" ]; then
|
| 28 |
+
echo "[OK] 目录: $expanded"
|
| 29 |
+
else
|
| 30 |
+
echo "[OK] 文件: $expanded"
|
| 31 |
+
fi
|
| 32 |
+
done
|
| 33 |
+
|
| 34 |
+
# 打印 DCU/GPU 状态
|
| 35 |
+
echo "-----------------------------------------"
|
| 36 |
+
echo ">>> DCU/GPU 状态:"
|
| 37 |
+
if command -v hy-smi &>/dev/null; then
|
| 38 |
+
hy-smi
|
| 39 |
+
elif command -v rocm-smi &>/dev/null; then
|
| 40 |
+
rocm-smi
|
| 41 |
+
elif command -v nvidia-smi &>/dev/null; then
|
| 42 |
+
nvidia-smi
|
| 43 |
+
else
|
| 44 |
+
echo "[WARN] 未找到 GPU 监控工具 (hy-smi/rocm-smi/nvidia-smi)"
|
| 45 |
+
fi
|
| 46 |
+
echo "-----------------------------------------"
|
| 47 |
+
|
| 48 |
+
if [ $ERRORS -gt 0 ]; then
|
| 49 |
+
echo "[FATAL] 预检发现 $ERRORS 个错误,终止训练"
|
| 50 |
+
exit 1
|
| 51 |
+
fi
|
| 52 |
+
|
| 53 |
+
echo "[OK] 预检通过"
|
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}}
|
inference/batch_inference_with_dataloader.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from torch.utils.data import DataLoader
|
| 2 |
+
|
| 3 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 4 |
+
import os
|
| 5 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 7 |
+
if os.path.isfile(_JD_PATH):
|
| 8 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 9 |
+
|
| 10 |
+
from onescience.datapipes.materials.custom_stack.core.atomic_data import (
|
| 11 |
+
atomicdata_list_to_batch,
|
| 12 |
+
)
|
| 13 |
+
from onescience.datapipes.materials.custom_stack.storage.ase_datasets import AseDBDataset
|
| 14 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def main() -> None:
|
| 18 |
+
# Update these two paths before running.
|
| 19 |
+
db_path = "../dataset/omat24/val/rattled-300-subsampled/data.aselmdb"
|
| 20 |
+
checkpoint_path = "../weight/uma-s-1p1_converted.pt"
|
| 21 |
+
|
| 22 |
+
dataset = AseDBDataset(
|
| 23 |
+
config={
|
| 24 |
+
"src": db_path,
|
| 25 |
+
"a2g_args": {"task_name": "omat"},
|
| 26 |
+
}
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
loader = DataLoader(
|
| 30 |
+
dataset,
|
| 31 |
+
batch_size=16,
|
| 32 |
+
collate_fn=atomicdata_list_to_batch,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
predictor = load_predict_unit(checkpoint_path, device="cuda")
|
| 36 |
+
|
| 37 |
+
for i, batch in enumerate(loader):
|
| 38 |
+
preds = predictor.predict(batch)
|
| 39 |
+
|
| 40 |
+
for j in range(len(preds["energy"])):
|
| 41 |
+
energy = preds["energy"][j].item()
|
| 42 |
+
forces = preds["forces"][batch.batch == j].cpu().numpy()
|
| 43 |
+
|
| 44 |
+
print(f"\\n[Batch {i} | Structure {j}]")
|
| 45 |
+
print("Predicted energy:", energy)
|
| 46 |
+
print("Predicted forces:\\n", forces)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
main()
|
inference/calculate_spin_gap.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ase.build import molecule
|
| 2 |
+
from ase.io import write
|
| 3 |
+
from ase.optimize import LBFGS
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 7 |
+
import os
|
| 8 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 10 |
+
if os.path.isfile(_JD_PATH):
|
| 11 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 12 |
+
|
| 13 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 14 |
+
from onescience.utils.uma.calculate.ase_calculator import FAIRChemCalculator
|
| 15 |
+
|
| 16 |
+
# ===== 路径:改成你的本地检查点 =====
|
| 17 |
+
CKPT = "../weight/uma-s-1p1_converted.pt"
|
| 18 |
+
|
| 19 |
+
# ===== 选项:是否在各自自旋态下弛豫几何(绝热差) =====
|
| 20 |
+
RELAX = False
|
| 21 |
+
FMAX = 0.05
|
| 22 |
+
STEPS = 200
|
| 23 |
+
|
| 24 |
+
# ===== 载入预测器(GPU)与构造计算器 =====
|
| 25 |
+
predictor = load_predict_unit(CKPT, device="cuda")
|
| 26 |
+
|
| 27 |
+
def make_state(mol_name: str, mult: int, charge: int = 0):
|
| 28 |
+
atoms = molecule(mol_name)
|
| 29 |
+
atoms.pbc = False
|
| 30 |
+
# 关键:同时写入两个键,避免读取不到
|
| 31 |
+
atoms.info["spin"] = mult
|
| 32 |
+
atoms.info["spin_multiplicity"] = mult
|
| 33 |
+
atoms.info["charge"] = charge
|
| 34 |
+
atoms.calc = FAIRChemCalculator(predictor, task_name="omol")
|
| 35 |
+
return atoms
|
| 36 |
+
|
| 37 |
+
# 单重态 / 三重态 CH2
|
| 38 |
+
singlet = make_state("CH2_s1A1d", mult=1, charge=0)
|
| 39 |
+
triplet = make_state("CH2_s3B1d", mult=3, charge=0)
|
| 40 |
+
|
| 41 |
+
# (可选)各自自旋面上弛豫
|
| 42 |
+
if RELAX:
|
| 43 |
+
for tag, atoms in [("singlet", singlet), ("triplet", triplet)]:
|
| 44 |
+
opt = LBFGS(atoms, logfile=f"{tag}_opt.log")
|
| 45 |
+
opt.run(fmax=FMAX, steps=STEPS)
|
| 46 |
+
write(f"{tag}_final.xyz", atoms)
|
| 47 |
+
|
| 48 |
+
# 计算单点能(或弛豫后能量)
|
| 49 |
+
E_s = singlet.get_potential_energy()
|
| 50 |
+
E_t = triplet.get_potential_energy()
|
| 51 |
+
|
| 52 |
+
gap_ev = E_t - E_s
|
| 53 |
+
gap_kcal = gap_ev * 23.060543
|
| 54 |
+
|
| 55 |
+
print(f"Singlet energy (CH2 1A1): {E_s:.6f} eV")
|
| 56 |
+
print(f"Triplet energy (CH2 3B1): {E_t:.6f} eV")
|
| 57 |
+
print(f"Gap (T - S): {gap_ev:.6f} eV ({gap_kcal:.2f} kcal/mol)")
|
| 58 |
+
|
| 59 |
+
# 保存结构,便于复查
|
| 60 |
+
write("CH2_singlet.xyz", singlet)
|
| 61 |
+
write("CH2_triplet.xyz", triplet)
|
| 62 |
+
|
| 63 |
+
# 打印自旋/电荷确认,确保不会再触发“未设置自旋”的警告
|
| 64 |
+
print("Singlet info:", {k: singlet.info[k] for k in ("spin", "spin_multiplicity", "charge")})
|
| 65 |
+
print("Triplet info:", {k: triplet.info[k] for k in ("spin", "spin_multiplicity", "charge")})
|
inference/generate_batches_at_runtime.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
# 自动定位 UMA 旋转基文件 Jd.pt(如果在仓库根目录 weight/ 下存在)
|
| 4 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 5 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 6 |
+
if os.path.isfile(_JD_PATH):
|
| 7 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 8 |
+
|
| 9 |
+
from ase.build import bulk
|
| 10 |
+
|
| 11 |
+
from onescience.datapipes.materials.custom_stack.core.atomic_data import (
|
| 12 |
+
AtomicData,
|
| 13 |
+
atomicdata_list_to_batch,
|
| 14 |
+
)
|
| 15 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 16 |
+
|
| 17 |
+
# 构建多个结构,可替换为 molecule() 或 slab(...)
|
| 18 |
+
atoms_list = [
|
| 19 |
+
bulk("Pt"),
|
| 20 |
+
bulk("Cu"),
|
| 21 |
+
bulk("NaCl", crystalstructure="rocksalt", a=2.0),
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
# 转换为 AtomicData 并赋予任务名
|
| 25 |
+
atomic_data_list = [
|
| 26 |
+
AtomicData.from_ase(atoms, task_name="omat") for atoms in atoms_list
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
# 合并成一个 batch
|
| 30 |
+
batch = atomicdata_list_to_batch(atomic_data_list)
|
| 31 |
+
|
| 32 |
+
# 加载模型(默认从仓库根目录的 weight/ 下读取)
|
| 33 |
+
checkpoint_path = os.environ.get(
|
| 34 |
+
"UMA_CHECKPOINT_PATH",
|
| 35 |
+
os.path.join(_REPO_ROOT, "weight", "uma-s-1p1_converted.pt"),
|
| 36 |
+
)
|
| 37 |
+
predictor = load_predict_unit(checkpoint_path, device="cuda")
|
| 38 |
+
|
| 39 |
+
# 执行推理
|
| 40 |
+
preds = predictor.predict(batch)
|
| 41 |
+
|
| 42 |
+
# 输出每个结构的能量和原子力
|
| 43 |
+
for i, atoms in enumerate(atoms_list):
|
| 44 |
+
energy = preds["energy"][i].item()
|
| 45 |
+
forces = preds["forces"][batch.batch == i].cpu().numpy()
|
| 46 |
+
|
| 47 |
+
print(f"\nStructure #{i + 1}: {atoms.get_chemical_formula()}")
|
| 48 |
+
print("Predicted energy:", energy)
|
| 49 |
+
print("Predicted forces:\n", forces)
|
inference/inference_heterogeneous_batches.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ase.build import bulk, molecule, fcc100, add_adsorbate
|
| 2 |
+
|
| 3 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 4 |
+
import os
|
| 5 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 6 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 7 |
+
if os.path.isfile(_JD_PATH):
|
| 8 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 9 |
+
|
| 10 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 11 |
+
from onescience.datapipes.materials.custom_stack.core.atomic_data import AtomicData, atomicdata_list_to_batch
|
| 12 |
+
|
| 13 |
+
# 1. 创建异构结构
|
| 14 |
+
h2o = molecule("H2O")
|
| 15 |
+
h2o.info.update({"charge": 0, "spin": 1})
|
| 16 |
+
|
| 17 |
+
pt = bulk("Pt")
|
| 18 |
+
|
| 19 |
+
slab = fcc100("Cu", (3, 3, 3), vacuum=8, periodic=True)
|
| 20 |
+
adsorbate = molecule("CO")
|
| 21 |
+
add_adsorbate(slab, adsorbate, 2.0, "bridge")
|
| 22 |
+
|
| 23 |
+
# 2. 结构转为 AtomicData,并指定不同 task_name
|
| 24 |
+
atomic_data_list = [
|
| 25 |
+
AtomicData.from_ase(
|
| 26 |
+
h2o, task_name="omol", r_data_keys=["spin", "charge"], molecule_cell_size=12
|
| 27 |
+
),
|
| 28 |
+
AtomicData.from_ase(pt, task_name="omat"),
|
| 29 |
+
AtomicData.from_ase(slab, task_name="oc20"),
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
# 3. 合成 batch
|
| 33 |
+
batch = atomicdata_list_to_batch(atomic_data_list)
|
| 34 |
+
|
| 35 |
+
# 4. 加载 UMA 模型
|
| 36 |
+
predictor = load_predict_unit(
|
| 37 |
+
"../weight/uma-s-1p1_converted.pt", device="cuda"#替换为你的检查点路径
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
# 5. 执行联合推理
|
| 41 |
+
preds = predictor.predict(batch)
|
| 42 |
+
|
| 43 |
+
# 6. 输出每个结构结果
|
| 44 |
+
for i in range(len(preds["energy"])):
|
| 45 |
+
energy = preds["energy"][i].item()
|
| 46 |
+
forces = preds["forces"][batch.batch == i].cpu().numpy()
|
| 47 |
+
|
| 48 |
+
print(f"\n[Structure {i}]")
|
| 49 |
+
print("Predicted energy:", energy)
|
| 50 |
+
print("Predicted forces:\n", forces)
|
inference/models/pretrained_models.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"uma-s-1": {
|
| 3 |
+
"filename": "uma-s-1.pt",
|
| 4 |
+
"repo_id": "facebook/UMA",
|
| 5 |
+
"subfolder": "checkpoints",
|
| 6 |
+
"atom_refs": {
|
| 7 |
+
"subfolder": "references",
|
| 8 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 9 |
+
}
|
| 10 |
+
},
|
| 11 |
+
"uma-s-1p1": {
|
| 12 |
+
"filename": "uma-s-1p1.pt",
|
| 13 |
+
"repo_id": "facebook/UMA",
|
| 14 |
+
"subfolder": "checkpoints",
|
| 15 |
+
"atom_refs": {
|
| 16 |
+
"subfolder": "references",
|
| 17 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"uma-m-1p1": {
|
| 21 |
+
"filename": "uma-m-1p1.pt",
|
| 22 |
+
"repo_id": "facebook/UMA",
|
| 23 |
+
"subfolder": "checkpoints",
|
| 24 |
+
"atom_refs": {
|
| 25 |
+
"subfolder": "references",
|
| 26 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"esen-md-direct-all-omol": {
|
| 30 |
+
"filename": "esen_md_direct_all.pt",
|
| 31 |
+
"repo_id": "facebook/OMol25",
|
| 32 |
+
"subfolder": "checkpoints",
|
| 33 |
+
"atom_refs": {
|
| 34 |
+
"subfolder": "references",
|
| 35 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"esen-sm-conserving-all-omol": {
|
| 39 |
+
"filename": "esen_sm_conserving_all.pt",
|
| 40 |
+
"repo_id": "facebook/OMol25",
|
| 41 |
+
"subfolder": "checkpoints",
|
| 42 |
+
"atom_refs": {
|
| 43 |
+
"subfolder": "references",
|
| 44 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
"esen-sm-direct-all-omol": {
|
| 48 |
+
"filename": "esen_sm_direct_all.pt",
|
| 49 |
+
"repo_id": "facebook/OMol25",
|
| 50 |
+
"subfolder": "checkpoints",
|
| 51 |
+
"atom_refs": {
|
| 52 |
+
"subfolder": "references",
|
| 53 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
inference/relax_adsorbate_on_slab.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time, torch, numpy as np
|
| 2 |
+
from ase.build import fcc100, add_adsorbate, molecule
|
| 3 |
+
from ase.optimize import LBFGS
|
| 4 |
+
|
| 5 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 6 |
+
import os
|
| 7 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 8 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 9 |
+
if os.path.isfile(_JD_PATH):
|
| 10 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 11 |
+
|
| 12 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 13 |
+
from onescience.utils.uma.calculate.ase_calculator import FAIRChemCalculator
|
| 14 |
+
|
| 15 |
+
# === 工具函数 ===
|
| 16 |
+
def _sync():
|
| 17 |
+
if torch.cuda.is_available():
|
| 18 |
+
torch.cuda.synchronize()
|
| 19 |
+
|
| 20 |
+
def clear_cache(atoms):
|
| 21 |
+
# 避免命中 ASE/Calculator 的上次结果缓存,确保测到真实推理时间
|
| 22 |
+
if hasattr(atoms, "calc") and hasattr(atoms.calc, "results"):
|
| 23 |
+
atoms.calc.results.clear()
|
| 24 |
+
|
| 25 |
+
# === 加载模型 ===
|
| 26 |
+
predictor = load_predict_unit(
|
| 27 |
+
"../weight/uma-s-1p1_converted.pt",
|
| 28 |
+
device="cuda"
|
| 29 |
+
)
|
| 30 |
+
calc = FAIRChemCalculator(predictor, task_name="oc20")
|
| 31 |
+
|
| 32 |
+
# === 构建体系 ===
|
| 33 |
+
slab = fcc100("Cu", (3, 3, 3), vacuum=8, periodic=True)
|
| 34 |
+
adsorbate = molecule("CO")
|
| 35 |
+
add_adsorbate(slab, adsorbate, 2.0, "bridge")
|
| 36 |
+
slab.calc = calc
|
| 37 |
+
|
| 38 |
+
# === 预热(把模型/张量搬到显存,避免首轮开销影响统计)===
|
| 39 |
+
_ = slab.get_potential_energy(); _sync()
|
| 40 |
+
_ = slab.get_forces(); _sync()
|
| 41 |
+
|
| 42 |
+
# === 计时:LBFGS 优化(总耗时 & 平均每步)===
|
| 43 |
+
t0 = time.perf_counter()
|
| 44 |
+
opt = LBFGS(slab, logfile=None) # 如需日志,改成 "lbfgs.log"
|
| 45 |
+
opt.run(fmax=0.05, steps=100)
|
| 46 |
+
_sync()
|
| 47 |
+
lbfgs_total = time.perf_counter() - t0
|
| 48 |
+
steps_done = getattr(opt, "nsteps", None) or 100
|
| 49 |
+
|
| 50 |
+
# === 计时:单次能量 & 力 推理延迟 ===
|
| 51 |
+
clear_cache(slab); t0 = time.perf_counter()
|
| 52 |
+
energy = slab.get_potential_energy(); _sync()
|
| 53 |
+
t_energy = time.perf_counter() - t0
|
| 54 |
+
|
| 55 |
+
clear_cache(slab); t0 = time.perf_counter()
|
| 56 |
+
forces = slab.get_forces(); _sync()
|
| 57 |
+
t_forces = time.perf_counter() - t0
|
| 58 |
+
|
| 59 |
+
# === 打印结果 ===
|
| 60 |
+
print(f"Predicted energy: {energy}")
|
| 61 |
+
print(f"Predicted forces shape: {forces.shape}")
|
| 62 |
+
# 如需打印具体力矩阵,取消下一行注释(可能很长)
|
| 63 |
+
# print(forces)
|
| 64 |
+
|
| 65 |
+
print(f"[E] latency: {t_energy*1e3:.2f} ms/call")
|
| 66 |
+
print(f"[F] latency: {t_forces*1e3:.2f} ms/call")
|
| 67 |
+
print(f"[LBFGS] total: {lbfgs_total:.3f} s, per-step: {lbfgs_total/steps_done*1e3:.2f} ms/step")
|
inference/relax_inorganic_crystal.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ase.build import bulk
|
| 2 |
+
from ase.optimize import LBFGS
|
| 3 |
+
from ase.filters import FrechetCellFilter
|
| 4 |
+
from ase.io import write
|
| 5 |
+
|
| 6 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 7 |
+
import os
|
| 8 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 10 |
+
if os.path.isfile(_JD_PATH):
|
| 11 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 12 |
+
|
| 13 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 14 |
+
from onescience.utils.uma.calculate.ase_calculator import FAIRChemCalculator
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
| 17 |
+
# === 本地检查点(改成你的实际路径) ===
|
| 18 |
+
ckpt = "../weight/uma-s-1p1_converted.pt"
|
| 19 |
+
|
| 20 |
+
# === 加载预测器与计算器 ===
|
| 21 |
+
predictor = load_predict_unit(ckpt, device="cuda") # GPU 加速
|
| 22 |
+
calc = FAIRChemCalculator(predictor, task_name="omat")
|
| 23 |
+
|
| 24 |
+
# === 建 Fe 体相并绑定计算器 ===
|
| 25 |
+
atoms = bulk("Fe")
|
| 26 |
+
atoms.calc = calc
|
| 27 |
+
|
| 28 |
+
# === 原子+晶胞联合弛豫 ===
|
| 29 |
+
filt = FrechetCellFilter(atoms) # 同时优化原子位置与晶胞
|
| 30 |
+
opt = LBFGS(filt, logfile="relax.log") # 记录优化日志到文件
|
| 31 |
+
opt.run(fmax=0.05, steps=100)
|
| 32 |
+
|
| 33 |
+
# === 计算与打印结果 ===
|
| 34 |
+
E = atoms.get_potential_energy()
|
| 35 |
+
F = atoms.get_forces()
|
| 36 |
+
fmax = np.sqrt((F**2).sum(axis=1)).max()
|
| 37 |
+
cell = atoms.get_cell()
|
| 38 |
+
a, b, c, alpha, beta, gamma = atoms.get_cell_lengths_and_angles()
|
| 39 |
+
vol = atoms.get_volume()
|
| 40 |
+
|
| 41 |
+
print(f"Final energy: {E:.6f} eV (per atom: {E/len(atoms):.6f} eV/atom)")
|
| 42 |
+
print(f"Final fmax: {fmax:.6f} eV/Å")
|
| 43 |
+
print(f"Volume: {vol:.6f} Å^3")
|
| 44 |
+
try:
|
| 45 |
+
stress = atoms.get_stress() # Voigt, eV/Å^3
|
| 46 |
+
print("Final stress (Voigt, eV/Å^3):", stress)
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print("Stress not available:", e)
|
| 49 |
+
|
| 50 |
+
print("Cell (Å):\n", cell)
|
| 51 |
+
print(f"Lengths/angles: a={a:.4f} b={b:.4f} c={c:.4f} "
|
| 52 |
+
f"alpha={alpha:.2f} beta={beta:.2f} gamma={gamma:.2f}")
|
| 53 |
+
print("Positions (Å):\n", atoms.get_positions())
|
| 54 |
+
|
| 55 |
+
# === 保存结果到文件 ===
|
| 56 |
+
write("Fe_optimized.cif", atoms) # 方便后续可视化/复现实验
|
| 57 |
+
write("Fe_optimized.traj", atoms) # ASE 轨迹
|
| 58 |
+
np.savetxt("Fe_forces.txt", F) # 保存力矩阵
|
inference/run_molecular_md.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ase import units
|
| 2 |
+
from ase.io import write, Trajectory, read
|
| 3 |
+
from ase.md.langevin import Langevin
|
| 4 |
+
from ase.md.velocitydistribution import MaxwellBoltzmannDistribution, Stationary, ZeroRotation
|
| 5 |
+
from ase.build import molecule
|
| 6 |
+
from ase.md import MDLogger
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
# 自动定位 UMA 旋转基文件 Jd.pt
|
| 10 |
+
import os
|
| 11 |
+
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 12 |
+
_JD_PATH = os.path.join(_REPO_ROOT, "weight", "Jd.pt")
|
| 13 |
+
if os.path.isfile(_JD_PATH):
|
| 14 |
+
os.environ.setdefault("ONESCIENCE_UMA_JD_PATH", _JD_PATH)
|
| 15 |
+
|
| 16 |
+
from onescience.utils.uma.units.mlip_unit import load_predict_unit
|
| 17 |
+
from onescience.utils.uma.calculate.ase_calculator import FAIRChemCalculator
|
| 18 |
+
|
| 19 |
+
# === 本地检查点路径(改成你的实际路径)===
|
| 20 |
+
ckpt = "../weight/uma-s-1p1_converted.pt"
|
| 21 |
+
|
| 22 |
+
# === 加载预测器与计算器 ===
|
| 23 |
+
predictor = load_predict_unit(ckpt, device="cuda") # GPU加速
|
| 24 |
+
calc = FAIRChemCalculator(predictor, task_name="omol") # 分子任务
|
| 25 |
+
|
| 26 |
+
# === 构建体系 ===
|
| 27 |
+
atoms = molecule("H2O") # 可替换为其它分子
|
| 28 |
+
atoms.calc = calc
|
| 29 |
+
|
| 30 |
+
# === 速度初始化(400 K),去线动量/角动量 ===
|
| 31 |
+
T0 = 400 # K
|
| 32 |
+
MaxwellBoltzmannDistribution(atoms, temperature_K=T0)
|
| 33 |
+
Stationary(atoms) # 去整体平动
|
| 34 |
+
ZeroRotation(atoms) # 去整体转动
|
| 35 |
+
|
| 36 |
+
# === 动力学设置:Langevin ===
|
| 37 |
+
dt = 0.1 * units.fs
|
| 38 |
+
gamma = 0.001 / units.fs
|
| 39 |
+
dyn = Langevin(atoms, timestep=dt, temperature_K=T0, friction=gamma)
|
| 40 |
+
|
| 41 |
+
# === 轨迹与日志 ===
|
| 42 |
+
traj = Trajectory("my_md.traj", "w", atoms) # ASE 原生轨迹
|
| 43 |
+
dyn.attach(traj.write, interval=1) # 每步写一帧
|
| 44 |
+
logger = MDLogger(dyn, atoms, "md.log", header=True, stress=False, peratom=False)
|
| 45 |
+
dyn.attach(logger, interval=10) # 每10步记录一次
|
| 46 |
+
|
| 47 |
+
# 也打印到屏幕(可选)
|
| 48 |
+
def printer():
|
| 49 |
+
epot = atoms.get_potential_energy()
|
| 50 |
+
ekin = atoms.get_kinetic_energy()
|
| 51 |
+
Tinst = 2.0 * ekin / (3 * len(atoms) * units.kB)
|
| 52 |
+
print(f"Step {dyn.nsteps:5d} Epot={epot: .6f} eV Ekin={ekin: .6f} eV T={Tinst: .1f} K")
|
| 53 |
+
dyn.attach(printer, interval=50)
|
| 54 |
+
|
| 55 |
+
# === 运行MD ===
|
| 56 |
+
steps = 1000
|
| 57 |
+
dyn.run(steps=steps)
|
| 58 |
+
|
| 59 |
+
# === 结束后输出关键信息与保存 ===
|
| 60 |
+
Epot = atoms.get_potential_energy()
|
| 61 |
+
Ekin = atoms.get_kinetic_energy()
|
| 62 |
+
Tfinal = 2.0 * Ekin / (3 * len(atoms) * units.kB)
|
| 63 |
+
print("\n== MD Finished ==")
|
| 64 |
+
print(f"Final Epot = {Epot:.6f} eV, Ekin = {Ekin:.6f} eV, T = {Tfinal:.2f} K")
|
| 65 |
+
print("COM =", atoms.get_center_of_mass())
|
| 66 |
+
|
| 67 |
+
# 保存最终结构(xyz 与 cif)
|
| 68 |
+
write("final.xyz", atoms)
|
| 69 |
+
try:
|
| 70 |
+
write("final.cif", atoms) # 分子一般无PBC,cif可有可无
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print("Save CIF skipped:", e)
|
| 73 |
+
|
| 74 |
+
# 如需导出整段轨迹为 .xyz(多帧),取消注释以下两行
|
| 75 |
+
# frames = read("my_md.traj", ":")
|
| 76 |
+
# write("my_md_all.xyz", frames)
|
model/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from importlib.metadata import PackageNotFoundError, version
|
| 6 |
+
|
| 7 |
+
from model.base import HydraModel, HydraModelV2
|
| 8 |
+
|
| 9 |
+
try:
|
| 10 |
+
__version__ = version("onescience.utils.uma")
|
| 11 |
+
except PackageNotFoundError:
|
| 12 |
+
# package is not installed
|
| 13 |
+
__version__ = ""
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
"HydraModel",
|
| 17 |
+
"HydraModelV2",
|
| 18 |
+
"eSCNMDBackbone",
|
| 19 |
+
"eSCNMDMoeBackbone",
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def __getattr__(name: str):
|
| 24 |
+
if name == "eSCNMDBackbone":
|
| 25 |
+
from model.uma_escn_md import eSCNMDBackbone
|
| 26 |
+
return eSCNMDBackbone
|
| 27 |
+
if name == "eSCNMDMoeBackbone":
|
| 28 |
+
from model.uma_escn_moe import eSCNMDMoeBackbone
|
| 29 |
+
return eSCNMDMoeBackbone
|
| 30 |
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
model/base.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import copy
|
| 6 |
+
import logging
|
| 7 |
+
from abc import ABCMeta, abstractmethod
|
| 8 |
+
from typing import TYPE_CHECKING
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from torch import nn
|
| 12 |
+
|
| 13 |
+
from onescience.utils.uma.common.registry import registry
|
| 14 |
+
from onescience.utils.uma.common.utils import (
|
| 15 |
+
load_model_and_weights_from_checkpoint,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
if TYPE_CHECKING:
|
| 19 |
+
from onescience.datapipes.materials.custom_stack.core.atomic_data import AtomicData
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class HeadInterface(metaclass=ABCMeta):
|
| 23 |
+
@property
|
| 24 |
+
def use_amp(self):
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
@abstractmethod
|
| 28 |
+
def forward(
|
| 29 |
+
self, data: AtomicData, emb: dict[str, torch.Tensor]
|
| 30 |
+
) -> dict[str, torch.Tensor]:
|
| 31 |
+
"""Head forward.
|
| 32 |
+
|
| 33 |
+
Arguments
|
| 34 |
+
---------
|
| 35 |
+
data: AtomicData
|
| 36 |
+
Atomic systems as input
|
| 37 |
+
emb: dict[str->torch.Tensor]
|
| 38 |
+
Embeddings of the input as generated by the backbone
|
| 39 |
+
|
| 40 |
+
Returns
|
| 41 |
+
-------
|
| 42 |
+
outputs: dict[str->torch.Tensor]
|
| 43 |
+
Return one or more targets generated by this head
|
| 44 |
+
"""
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class BackboneInterface(metaclass=ABCMeta):
|
| 49 |
+
@abstractmethod
|
| 50 |
+
def forward(self, data: AtomicData) -> dict[str, torch.Tensor]:
|
| 51 |
+
"""Backbone forward.
|
| 52 |
+
|
| 53 |
+
Arguments
|
| 54 |
+
---------
|
| 55 |
+
data: AtomicData
|
| 56 |
+
Atomic systems as input
|
| 57 |
+
|
| 58 |
+
Returns
|
| 59 |
+
-------
|
| 60 |
+
embedding: dict[str->torch.Tensor]
|
| 61 |
+
Return backbone embeddings for the given input
|
| 62 |
+
"""
|
| 63 |
+
return
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@registry.register_model("hydra")
|
| 67 |
+
class HydraModel(nn.Module):
|
| 68 |
+
def __init__(
|
| 69 |
+
self,
|
| 70 |
+
backbone: dict | None = None,
|
| 71 |
+
heads: dict | None = None,
|
| 72 |
+
finetune_config: dict | None = None,
|
| 73 |
+
otf_graph: bool = True,
|
| 74 |
+
pass_through_head_outputs: bool = False,
|
| 75 |
+
freeze_backbone: bool = False,
|
| 76 |
+
):
|
| 77 |
+
super().__init__()
|
| 78 |
+
self.device = None
|
| 79 |
+
self.otf_graph = otf_graph
|
| 80 |
+
# This is required for hydras with models that have multiple outputs per head, since we will deprecate
|
| 81 |
+
# the old config system at some point, this will prevent the need to make major modifications to the trainer
|
| 82 |
+
# because they all expect the name of the outputs directly instead of the head_name.property_name
|
| 83 |
+
self.pass_through_head_outputs = pass_through_head_outputs
|
| 84 |
+
|
| 85 |
+
# if finetune_config is provided, then attempt to load the model from the given finetune checkpoint
|
| 86 |
+
starting_model = None
|
| 87 |
+
if finetune_config is not None:
|
| 88 |
+
# Make it hard to sneak more fields into finetuneconfig
|
| 89 |
+
assert (
|
| 90 |
+
len(set(finetune_config.keys()) - {"starting_checkpoint", "override"})
|
| 91 |
+
== 0
|
| 92 |
+
)
|
| 93 |
+
starting_model: HydraModel = load_model_and_weights_from_checkpoint(
|
| 94 |
+
finetune_config["starting_checkpoint"]
|
| 95 |
+
)
|
| 96 |
+
logging.info(
|
| 97 |
+
f"Found and loaded fine-tuning checkpoint: {finetune_config['starting_checkpoint']} (Note we are NOT loading the training state from this checkpoint, only parts of the model and weights)"
|
| 98 |
+
)
|
| 99 |
+
assert isinstance(
|
| 100 |
+
starting_model, HydraModel
|
| 101 |
+
), "Can only finetune starting from other hydra models!"
|
| 102 |
+
# TODO this is a bit hacky to overrride attrs in the backbone
|
| 103 |
+
if "override" in finetune_config:
|
| 104 |
+
for key, value in finetune_config["override"].items():
|
| 105 |
+
setattr(starting_model.backbone, key, value)
|
| 106 |
+
|
| 107 |
+
if backbone is not None:
|
| 108 |
+
backbone = copy.deepcopy(backbone)
|
| 109 |
+
backbone_model_name = backbone.pop("model")
|
| 110 |
+
self.backbone: BackboneInterface = registry.get_model_class(
|
| 111 |
+
backbone_model_name
|
| 112 |
+
)(
|
| 113 |
+
**backbone,
|
| 114 |
+
)
|
| 115 |
+
elif starting_model is not None:
|
| 116 |
+
self.backbone = starting_model.backbone
|
| 117 |
+
logging.info(
|
| 118 |
+
f"User did not specify a backbone, using the backbone from the starting checkpoint {self.backbone}"
|
| 119 |
+
)
|
| 120 |
+
else:
|
| 121 |
+
raise RuntimeError(
|
| 122 |
+
"Backbone not specified and not found in the starting checkpoint"
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
if freeze_backbone:
|
| 126 |
+
for param in self.backbone.parameters():
|
| 127 |
+
param.requires_grad = False
|
| 128 |
+
|
| 129 |
+
if heads is not None:
|
| 130 |
+
heads = copy.deepcopy(heads)
|
| 131 |
+
# Iterate through outputs_cfg and create heads
|
| 132 |
+
self.output_heads: dict[str, HeadInterface] = {}
|
| 133 |
+
|
| 134 |
+
head_names_sorted = sorted(heads.keys())
|
| 135 |
+
assert len(set(head_names_sorted)) == len(
|
| 136 |
+
head_names_sorted
|
| 137 |
+
), "Head names must be unique!"
|
| 138 |
+
for head_name in head_names_sorted:
|
| 139 |
+
head_config = heads[head_name]
|
| 140 |
+
if "module" not in head_config:
|
| 141 |
+
raise ValueError(
|
| 142 |
+
f"{head_name} head does not specify module to use for the head"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
module_name = head_config.pop("module")
|
| 146 |
+
self.output_heads[head_name] = registry.get_model_class(module_name)(
|
| 147 |
+
self.backbone,
|
| 148 |
+
**head_config,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
self.output_heads = torch.nn.ModuleDict(self.output_heads)
|
| 152 |
+
elif starting_model is not None:
|
| 153 |
+
self.output_heads = starting_model.output_heads
|
| 154 |
+
logging.info(
|
| 155 |
+
f"User did not specify heads, using the output heads from the starting checkpoint {self.output_heads}"
|
| 156 |
+
)
|
| 157 |
+
else:
|
| 158 |
+
raise RuntimeError(
|
| 159 |
+
"Heads not specified and not found in the starting checkpoint"
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
def forward(self, data: AtomicData):
|
| 163 |
+
# lazily get device from input to use with amp, at least one input must be a tensor to figure out it's device
|
| 164 |
+
if not self.device:
|
| 165 |
+
device_from_tensors = {
|
| 166 |
+
x.device.type for x in data.values() if isinstance(x, torch.Tensor)
|
| 167 |
+
}
|
| 168 |
+
assert (
|
| 169 |
+
len(device_from_tensors) == 1
|
| 170 |
+
), f"all inputs must be on the same device, found the following devices {device_from_tensors}"
|
| 171 |
+
self.device = device_from_tensors.pop()
|
| 172 |
+
|
| 173 |
+
emb = self.backbone(data)
|
| 174 |
+
# Predict all output properties for all structures in the batch for now.
|
| 175 |
+
out = {}
|
| 176 |
+
for k in self.output_heads:
|
| 177 |
+
with torch.autocast(
|
| 178 |
+
device_type=self.device, enabled=self.output_heads[k].use_amp
|
| 179 |
+
):
|
| 180 |
+
if self.pass_through_head_outputs:
|
| 181 |
+
out.update(self.output_heads[k](data, emb))
|
| 182 |
+
else:
|
| 183 |
+
out[k] = self.output_heads[k](data, emb)
|
| 184 |
+
|
| 185 |
+
return out
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class HydraModelV2(nn.Module):
|
| 189 |
+
def __init__(
|
| 190 |
+
self,
|
| 191 |
+
backbone: BackboneInterface,
|
| 192 |
+
heads: dict[str, HeadInterface],
|
| 193 |
+
freeze_backbone: bool = False,
|
| 194 |
+
):
|
| 195 |
+
super().__init__()
|
| 196 |
+
self.backbone = backbone
|
| 197 |
+
self.output_heads = torch.nn.ModuleDict(heads)
|
| 198 |
+
self.device = None
|
| 199 |
+
if freeze_backbone:
|
| 200 |
+
for param in self.backbone.parameters():
|
| 201 |
+
param.requires_grad = False
|
| 202 |
+
|
| 203 |
+
def forward(self, data):
|
| 204 |
+
# lazily get device from input to use with amp, at least one input must be a tensor to figure out it's device
|
| 205 |
+
if not self.device:
|
| 206 |
+
device_from_tensors = {
|
| 207 |
+
x.device.type for x in data.values() if isinstance(x, torch.Tensor)
|
| 208 |
+
}
|
| 209 |
+
assert (
|
| 210 |
+
len(device_from_tensors) == 1
|
| 211 |
+
), f"all inputs must be on the same device, found the following devices {device_from_tensors}"
|
| 212 |
+
self.device = device_from_tensors.pop()
|
| 213 |
+
|
| 214 |
+
emb = self.backbone(data)
|
| 215 |
+
# Predict all output properties for all structures in the batch for now.
|
| 216 |
+
out = {}
|
| 217 |
+
for k in self.output_heads:
|
| 218 |
+
with torch.autocast(
|
| 219 |
+
device_type=self.device, enabled=self.output_heads[k].use_amp
|
| 220 |
+
):
|
| 221 |
+
out[k] = self.output_heads[k](data, emb)
|
| 222 |
+
return out
|
model/models/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""compat aliases"""\n
|
model/models/base.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from model.base import *
|
model/models/uma/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""compat aliases"""
|
model/models/uma/escn_md.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from model.uma_escn_md import *
|
model/models/uma/escn_moe.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from model.uma_escn_moe import *
|
model/uma_escn_md.py
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from torch.profiler import record_function
|
| 10 |
+
|
| 11 |
+
from onescience.utils.uma.common import gp_utils
|
| 12 |
+
from onescience.utils.uma.common.distutils import get_device_for_local_rank
|
| 13 |
+
from onescience.utils.uma.common.registry import registry
|
| 14 |
+
from onescience.utils.uma.common.utils import conditional_grad
|
| 15 |
+
from onescience.modules.func_utils.uma_graph.compute import generate_graph
|
| 16 |
+
from onescience.modules.func_utils.uma_path_utils import resolve_jd_path
|
| 17 |
+
from onescience.modules.head.uma_head import (
|
| 18 |
+
Linear_Energy_Head,
|
| 19 |
+
Linear_Force_Head,
|
| 20 |
+
MLP_EFS_Head,
|
| 21 |
+
MLP_Energy_Head,
|
| 22 |
+
MLP_Stress_Head,
|
| 23 |
+
compose_tensor,
|
| 24 |
+
)
|
| 25 |
+
from onescience.modules.func_utils.uma_rotation import (
|
| 26 |
+
init_edge_rot_mat,
|
| 27 |
+
rotation_to_wigner,
|
| 28 |
+
)
|
| 29 |
+
from onescience.modules.func_utils.uma_rotation_cuda_graph import RotMatWignerCudaGraph
|
| 30 |
+
from onescience.modules.func_utils.uma_so3 import CoefficientMapping, SO3_Grid
|
| 31 |
+
from onescience.modules.embedding.uma_embedding import (
|
| 32 |
+
ChgSpinEmbedding,
|
| 33 |
+
DatasetEmbedding,
|
| 34 |
+
EdgeDegreeEmbedding,
|
| 35 |
+
)
|
| 36 |
+
from onescience.modules.layer.uma_layer_norm import (
|
| 37 |
+
EquivariantLayerNormArray,
|
| 38 |
+
EquivariantLayerNormArraySphericalHarmonics,
|
| 39 |
+
EquivariantRMSNormArraySphericalHarmonics,
|
| 40 |
+
EquivariantRMSNormArraySphericalHarmonicsV2,
|
| 41 |
+
get_normalization_layer,
|
| 42 |
+
)
|
| 43 |
+
from onescience.modules.func_utils.uma_mole_utils import MOLEInterface
|
| 44 |
+
from onescience.modules.layer.uma_radial import GaussianSmearing
|
| 45 |
+
from onescience.modules.layer.uma_so3_layers import SO3_Linear
|
| 46 |
+
|
| 47 |
+
from onescience.modules.block.uma_escn_md_block import eSCNMD_Block
|
| 48 |
+
|
| 49 |
+
ESCNMD_DEFAULT_EDGE_CHUNK_SIZE = 1024 * 128
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@registry.register_model("escnmd_backbone")
|
| 53 |
+
class eSCNMDBackbone(nn.Module, MOLEInterface):
|
| 54 |
+
def __init__(
|
| 55 |
+
self,
|
| 56 |
+
max_num_elements: int = 100,
|
| 57 |
+
sphere_channels: int = 128,
|
| 58 |
+
lmax: int = 2,
|
| 59 |
+
mmax: int = 2,
|
| 60 |
+
grid_resolution: int | None = None,
|
| 61 |
+
num_sphere_samples: int = 128, # NOTE not used
|
| 62 |
+
# NOTE: graph construction related, to remove
|
| 63 |
+
otf_graph: bool = False,
|
| 64 |
+
max_neighbors: int = 300,
|
| 65 |
+
use_pbc: bool = True, # deprecated
|
| 66 |
+
use_pbc_single: bool = True, # deprecated
|
| 67 |
+
cutoff: float = 5.0,
|
| 68 |
+
edge_channels: int = 128,
|
| 69 |
+
distance_function: str = "gaussian",
|
| 70 |
+
num_distance_basis: int = 512,
|
| 71 |
+
direct_forces: bool = True,
|
| 72 |
+
regress_forces: bool = True,
|
| 73 |
+
regress_stress: bool = False,
|
| 74 |
+
# escnmd specific
|
| 75 |
+
num_layers: int = 2,
|
| 76 |
+
hidden_channels: int = 128,
|
| 77 |
+
norm_type: str = "rms_norm_sh",
|
| 78 |
+
act_type: str = "gate",
|
| 79 |
+
ff_type: str = "grid",
|
| 80 |
+
activation_checkpointing: bool = False,
|
| 81 |
+
chg_spin_emb_type: str = "pos_emb",
|
| 82 |
+
cs_emb_grad: bool = False,
|
| 83 |
+
dataset_emb_grad: bool = False,
|
| 84 |
+
dataset_list: list[str] | None = None,
|
| 85 |
+
use_dataset_embedding: bool = True,
|
| 86 |
+
use_cuda_graph_wigner: bool = False,
|
| 87 |
+
radius_pbc_version: int = 1,
|
| 88 |
+
always_use_pbc: bool = True,
|
| 89 |
+
jd_path: str | None = None,
|
| 90 |
+
):
|
| 91 |
+
super().__init__()
|
| 92 |
+
self.max_num_elements = max_num_elements
|
| 93 |
+
self.lmax = lmax
|
| 94 |
+
self.mmax = mmax
|
| 95 |
+
self.sphere_channels = sphere_channels
|
| 96 |
+
self.grid_resolution = grid_resolution
|
| 97 |
+
self.num_sphere_samples = num_sphere_samples
|
| 98 |
+
# set this True if we want to ALWAYS use pbc for internal graph gen
|
| 99 |
+
# despite what's in the input data this only affects when otf_graph is True
|
| 100 |
+
# in this mode, the user must be responsible for providing a large vaccum box
|
| 101 |
+
# for aperiodic systems
|
| 102 |
+
self.always_use_pbc = always_use_pbc
|
| 103 |
+
|
| 104 |
+
# energy conservation related
|
| 105 |
+
self.regress_forces = regress_forces
|
| 106 |
+
self.direct_forces = direct_forces
|
| 107 |
+
self.regress_stress = regress_stress
|
| 108 |
+
|
| 109 |
+
# NOTE: graph construction related, to remove, except for cutoff
|
| 110 |
+
self.otf_graph = otf_graph
|
| 111 |
+
self.max_neighbors = max_neighbors
|
| 112 |
+
self.radius_pbc_version = radius_pbc_version
|
| 113 |
+
self.enforce_max_neighbors_strictly = False
|
| 114 |
+
|
| 115 |
+
activation_checkpoint_chunk_size = None
|
| 116 |
+
if activation_checkpointing:
|
| 117 |
+
# The size of edge blocks to use in activation checkpointing
|
| 118 |
+
activation_checkpoint_chunk_size = ESCNMD_DEFAULT_EDGE_CHUNK_SIZE
|
| 119 |
+
|
| 120 |
+
# related to charge spin dataset system embedding
|
| 121 |
+
self.chg_spin_emb_type = chg_spin_emb_type
|
| 122 |
+
self.cs_emb_grad = cs_emb_grad
|
| 123 |
+
self.dataset_emb_grad = dataset_emb_grad
|
| 124 |
+
self.dataset_list = dataset_list
|
| 125 |
+
self.use_dataset_embedding = use_dataset_embedding
|
| 126 |
+
self.use_cuda_graph_wigner = use_cuda_graph_wigner
|
| 127 |
+
assert (
|
| 128 |
+
self.dataset_list
|
| 129 |
+
), "the dataset list is empty, please add it to the model backbone config"
|
| 130 |
+
|
| 131 |
+
# rotation utils
|
| 132 |
+
Jd_list = torch.load(resolve_jd_path(jd_path))
|
| 133 |
+
for l in range(self.lmax + 1):
|
| 134 |
+
self.register_buffer(f"Jd_{l}", Jd_list[l])
|
| 135 |
+
self.sph_feature_size = int((self.lmax + 1) ** 2)
|
| 136 |
+
self.mappingReduced = CoefficientMapping(self.lmax, self.mmax)
|
| 137 |
+
|
| 138 |
+
# lmax_lmax for node, lmax_mmax for edge
|
| 139 |
+
self.SO3_grid = nn.ModuleDict()
|
| 140 |
+
self.SO3_grid["lmax_lmax"] = SO3_Grid(
|
| 141 |
+
self.lmax, self.lmax, resolution=grid_resolution, rescale=True
|
| 142 |
+
)
|
| 143 |
+
self.SO3_grid["lmax_mmax"] = SO3_Grid(
|
| 144 |
+
self.lmax, self.mmax, resolution=grid_resolution, rescale=True
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# atom embedding
|
| 148 |
+
self.sphere_embedding = nn.Embedding(
|
| 149 |
+
self.max_num_elements, self.sphere_channels
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# charge / spin embedding
|
| 153 |
+
self.charge_embedding = ChgSpinEmbedding(
|
| 154 |
+
self.chg_spin_emb_type,
|
| 155 |
+
"charge",
|
| 156 |
+
self.sphere_channels,
|
| 157 |
+
grad=self.cs_emb_grad,
|
| 158 |
+
)
|
| 159 |
+
self.spin_embedding = ChgSpinEmbedding(
|
| 160 |
+
self.chg_spin_emb_type,
|
| 161 |
+
"spin",
|
| 162 |
+
self.sphere_channels,
|
| 163 |
+
grad=self.cs_emb_grad,
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
# dataset embedding
|
| 167 |
+
if self.use_dataset_embedding:
|
| 168 |
+
self.dataset_embedding = DatasetEmbedding(
|
| 169 |
+
self.sphere_channels,
|
| 170 |
+
grad=self.dataset_emb_grad,
|
| 171 |
+
dataset_list=self.dataset_list,
|
| 172 |
+
)
|
| 173 |
+
# mix charge, spin, dataset embeddings
|
| 174 |
+
self.mix_csd = nn.Linear(3 * self.sphere_channels, self.sphere_channels)
|
| 175 |
+
else:
|
| 176 |
+
# mix charge, spin
|
| 177 |
+
self.mix_csd = nn.Linear(2 * self.sphere_channels, self.sphere_channels)
|
| 178 |
+
|
| 179 |
+
# edge distance embedding
|
| 180 |
+
self.cutoff = cutoff
|
| 181 |
+
self.edge_channels = edge_channels
|
| 182 |
+
self.distance_function = distance_function
|
| 183 |
+
self.num_distance_basis = num_distance_basis
|
| 184 |
+
|
| 185 |
+
if self.distance_function == "gaussian":
|
| 186 |
+
self.distance_expansion = GaussianSmearing(
|
| 187 |
+
0.0,
|
| 188 |
+
self.cutoff,
|
| 189 |
+
self.num_distance_basis,
|
| 190 |
+
2.0,
|
| 191 |
+
)
|
| 192 |
+
else:
|
| 193 |
+
raise ValueError("Unknown distance function")
|
| 194 |
+
|
| 195 |
+
# equivariant initial embedding
|
| 196 |
+
self.source_embedding = nn.Embedding(self.max_num_elements, self.edge_channels)
|
| 197 |
+
self.target_embedding = nn.Embedding(self.max_num_elements, self.edge_channels)
|
| 198 |
+
nn.init.uniform_(self.source_embedding.weight.data, -0.001, 0.001)
|
| 199 |
+
nn.init.uniform_(self.target_embedding.weight.data, -0.001, 0.001)
|
| 200 |
+
|
| 201 |
+
self.edge_channels_list = [
|
| 202 |
+
self.num_distance_basis + 2 * self.edge_channels,
|
| 203 |
+
self.edge_channels,
|
| 204 |
+
self.edge_channels,
|
| 205 |
+
]
|
| 206 |
+
|
| 207 |
+
self.edge_degree_embedding = EdgeDegreeEmbedding(
|
| 208 |
+
sphere_channels=self.sphere_channels,
|
| 209 |
+
lmax=self.lmax,
|
| 210 |
+
mmax=self.mmax,
|
| 211 |
+
max_num_elements=self.max_num_elements,
|
| 212 |
+
edge_channels_list=self.edge_channels_list,
|
| 213 |
+
rescale_factor=5.0, # NOTE: sqrt avg degree
|
| 214 |
+
cutoff=self.cutoff,
|
| 215 |
+
mappingReduced=self.mappingReduced,
|
| 216 |
+
activation_checkpoint_chunk_size=activation_checkpoint_chunk_size,
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
self.num_layers = num_layers
|
| 220 |
+
self.hidden_channels = hidden_channels
|
| 221 |
+
self.norm_type = norm_type
|
| 222 |
+
self.act_type = act_type
|
| 223 |
+
self.ff_type = ff_type
|
| 224 |
+
|
| 225 |
+
# Initialize the blocks for each layer
|
| 226 |
+
self.blocks = nn.ModuleList()
|
| 227 |
+
for _ in range(self.num_layers):
|
| 228 |
+
block = eSCNMD_Block(
|
| 229 |
+
self.sphere_channels,
|
| 230 |
+
self.hidden_channels,
|
| 231 |
+
self.lmax,
|
| 232 |
+
self.mmax,
|
| 233 |
+
self.mappingReduced,
|
| 234 |
+
self.SO3_grid,
|
| 235 |
+
self.edge_channels_list,
|
| 236 |
+
self.cutoff,
|
| 237 |
+
self.norm_type,
|
| 238 |
+
self.act_type,
|
| 239 |
+
self.ff_type,
|
| 240 |
+
activation_checkpoint_chunk_size=activation_checkpoint_chunk_size,
|
| 241 |
+
)
|
| 242 |
+
self.blocks.append(block)
|
| 243 |
+
|
| 244 |
+
self.norm = get_normalization_layer(
|
| 245 |
+
self.norm_type,
|
| 246 |
+
lmax=self.lmax,
|
| 247 |
+
num_channels=self.sphere_channels,
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
self.rot_mat_wigner_cuda = None # lazily initialize this
|
| 251 |
+
coefficient_index = self.SO3_grid["lmax_lmax"].mapping.coefficient_idx(
|
| 252 |
+
self.lmax, self.mmax
|
| 253 |
+
)
|
| 254 |
+
self.register_buffer("coefficient_index", coefficient_index, persistent=False)
|
| 255 |
+
|
| 256 |
+
def _get_rotmat_and_wigner(
|
| 257 |
+
self, edge_distance_vecs: torch.Tensor, use_cuda_graph: bool
|
| 258 |
+
):
|
| 259 |
+
Jd_buffers = [
|
| 260 |
+
getattr(self, f"Jd_{l}").type(edge_distance_vecs.dtype)
|
| 261 |
+
for l in range(self.lmax + 1)
|
| 262 |
+
]
|
| 263 |
+
|
| 264 |
+
if use_cuda_graph:
|
| 265 |
+
if self.rot_mat_wigner_cuda is None:
|
| 266 |
+
self.rot_mat_wigner_cuda = RotMatWignerCudaGraph()
|
| 267 |
+
with record_function("obtain rotmat wigner cudagraph"):
|
| 268 |
+
edge_rot_mat, wigner, wigner_inv = (
|
| 269 |
+
self.rot_mat_wigner_cuda.get_rotmat_and_wigner(
|
| 270 |
+
edge_distance_vecs, Jd_buffers
|
| 271 |
+
)
|
| 272 |
+
)
|
| 273 |
+
else:
|
| 274 |
+
with record_function("obtain rotmat wigner original"):
|
| 275 |
+
edge_rot_mat = init_edge_rot_mat(
|
| 276 |
+
edge_distance_vecs, rot_clip=(not self.direct_forces)
|
| 277 |
+
)
|
| 278 |
+
wigner = rotation_to_wigner(
|
| 279 |
+
edge_rot_mat,
|
| 280 |
+
0,
|
| 281 |
+
self.lmax,
|
| 282 |
+
Jd_buffers,
|
| 283 |
+
rot_clip=(not self.direct_forces),
|
| 284 |
+
)
|
| 285 |
+
wigner_inv = torch.transpose(wigner, 1, 2).contiguous()
|
| 286 |
+
|
| 287 |
+
# select subset of coefficients we are using
|
| 288 |
+
if self.mmax != self.lmax:
|
| 289 |
+
wigner = wigner.index_select(1, self.coefficient_index)
|
| 290 |
+
wigner_inv = wigner_inv.index_select(2, self.coefficient_index)
|
| 291 |
+
|
| 292 |
+
wigner_and_M_mapping = torch.einsum(
|
| 293 |
+
"mk,nkj->nmj", self.mappingReduced.to_m, wigner
|
| 294 |
+
)
|
| 295 |
+
wigner_and_M_mapping_inv = torch.einsum(
|
| 296 |
+
"njk,mk->njm", wigner_inv, self.mappingReduced.to_m
|
| 297 |
+
)
|
| 298 |
+
return edge_rot_mat, wigner_and_M_mapping, wigner_and_M_mapping_inv
|
| 299 |
+
|
| 300 |
+
def _get_displacement_and_cell(self, data_dict):
|
| 301 |
+
###############################################################
|
| 302 |
+
# gradient-based forces/stress
|
| 303 |
+
###############################################################
|
| 304 |
+
displacement = None
|
| 305 |
+
orig_cell = None
|
| 306 |
+
if self.regress_stress and not self.direct_forces:
|
| 307 |
+
displacement = torch.zeros(
|
| 308 |
+
(3, 3),
|
| 309 |
+
dtype=data_dict["pos"].dtype,
|
| 310 |
+
device=data_dict["pos"].device,
|
| 311 |
+
)
|
| 312 |
+
num_batch = len(data_dict["natoms"])
|
| 313 |
+
displacement = displacement.view(-1, 3, 3).expand(num_batch, 3, 3)
|
| 314 |
+
displacement.requires_grad = True
|
| 315 |
+
symmetric_displacement = 0.5 * (
|
| 316 |
+
displacement + displacement.transpose(-1, -2)
|
| 317 |
+
)
|
| 318 |
+
if data_dict["pos"].requires_grad is False:
|
| 319 |
+
data_dict["pos"].requires_grad = True
|
| 320 |
+
data_dict["pos_original"] = data_dict["pos"]
|
| 321 |
+
data_dict["pos"] = data_dict["pos"] + torch.bmm(
|
| 322 |
+
data_dict["pos"].unsqueeze(-2),
|
| 323 |
+
torch.index_select(symmetric_displacement, 0, data_dict["batch"]),
|
| 324 |
+
).squeeze(-2)
|
| 325 |
+
|
| 326 |
+
orig_cell = data_dict["cell"]
|
| 327 |
+
data_dict["cell"] = data_dict["cell"] + torch.bmm(
|
| 328 |
+
data_dict["cell"], symmetric_displacement
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
if (
|
| 332 |
+
not self.regress_stress
|
| 333 |
+
and self.regress_forces
|
| 334 |
+
and not self.direct_forces
|
| 335 |
+
and data_dict["pos"].requires_grad is False
|
| 336 |
+
):
|
| 337 |
+
data_dict["pos"].requires_grad = True
|
| 338 |
+
return displacement, orig_cell
|
| 339 |
+
|
| 340 |
+
def csd_embedding(self, charge, spin, dataset):
|
| 341 |
+
with record_function("charge spin dataset embeddings"):
|
| 342 |
+
# Add charge, spin, and dataset embeddings
|
| 343 |
+
chg_emb = self.charge_embedding(charge)
|
| 344 |
+
spin_emb = self.spin_embedding(spin)
|
| 345 |
+
if self.use_dataset_embedding:
|
| 346 |
+
assert dataset is not None
|
| 347 |
+
dataset_emb = self.dataset_embedding(dataset)
|
| 348 |
+
return torch.nn.SiLU()(
|
| 349 |
+
self.mix_csd(torch.cat((chg_emb, spin_emb, dataset_emb), dim=1))
|
| 350 |
+
)
|
| 351 |
+
return torch.nn.SiLU()(self.mix_csd(torch.cat((chg_emb, spin_emb), dim=1)))
|
| 352 |
+
|
| 353 |
+
def _generate_graph(self, data_dict):
|
| 354 |
+
if self.otf_graph:
|
| 355 |
+
pbc = None
|
| 356 |
+
if self.always_use_pbc:
|
| 357 |
+
pbc = torch.ones(len(data_dict), 3, dtype=torch.bool)
|
| 358 |
+
else:
|
| 359 |
+
assert (
|
| 360 |
+
"pbc" in data_dict
|
| 361 |
+
), "Since always_use_pbc is False, pbc conditions must be supplied by the input data"
|
| 362 |
+
pbc = data_dict["pbc"]
|
| 363 |
+
assert (
|
| 364 |
+
pbc.all() or (~pbc).all()
|
| 365 |
+
), "We can only accept pbc that is all true or all false"
|
| 366 |
+
logging.debug(f"Using radius graph gen version {self.radius_pbc_version}")
|
| 367 |
+
graph_dict = generate_graph(
|
| 368 |
+
data_dict,
|
| 369 |
+
cutoff=self.cutoff,
|
| 370 |
+
max_neighbors=self.max_neighbors,
|
| 371 |
+
enforce_max_neighbors_strictly=self.enforce_max_neighbors_strictly,
|
| 372 |
+
radius_pbc_version=self.radius_pbc_version,
|
| 373 |
+
pbc=pbc,
|
| 374 |
+
)
|
| 375 |
+
else:
|
| 376 |
+
# this assume edge_index is provided
|
| 377 |
+
assert (
|
| 378 |
+
"edge_index" in data_dict
|
| 379 |
+
), "otf_graph is false, need to provide edge_index as input!"
|
| 380 |
+
cell_per_edge = data_dict["cell"].repeat_interleave(
|
| 381 |
+
data_dict["nedges"], dim=0
|
| 382 |
+
)
|
| 383 |
+
shifts = torch.einsum(
|
| 384 |
+
"ij,ijk->ik",
|
| 385 |
+
data_dict["cell_offsets"].to(cell_per_edge.dtype),
|
| 386 |
+
cell_per_edge,
|
| 387 |
+
)
|
| 388 |
+
edge_distance_vec = (
|
| 389 |
+
data_dict["pos"][data_dict["edge_index"][0]]
|
| 390 |
+
- data_dict["pos"][data_dict["edge_index"][1]]
|
| 391 |
+
+ shifts
|
| 392 |
+
) # [n_edges, 3]
|
| 393 |
+
# pylint: disable=E1102
|
| 394 |
+
edge_distance = torch.linalg.norm(
|
| 395 |
+
edge_distance_vec, dim=-1, keepdim=False
|
| 396 |
+
) # [n_edges, 1]
|
| 397 |
+
|
| 398 |
+
graph_dict = {
|
| 399 |
+
"edge_index": data_dict["edge_index"],
|
| 400 |
+
"edge_distance": edge_distance,
|
| 401 |
+
"edge_distance_vec": edge_distance_vec,
|
| 402 |
+
"node_offset": 0,
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
if gp_utils.initialized():
|
| 406 |
+
graph_dict = self._init_gp_partitions(
|
| 407 |
+
graph_dict, data_dict["atomic_numbers_full"]
|
| 408 |
+
)
|
| 409 |
+
# create partial atomic numbers and batch tensors for GP
|
| 410 |
+
node_partition = graph_dict["node_partition"]
|
| 411 |
+
data_dict["atomic_numbers"] = data_dict["atomic_numbers_full"][
|
| 412 |
+
node_partition
|
| 413 |
+
]
|
| 414 |
+
data_dict["batch"] = data_dict["batch_full"][node_partition]
|
| 415 |
+
else:
|
| 416 |
+
graph_dict["node_offset"] = 0
|
| 417 |
+
graph_dict["edge_distance_vec_full"] = graph_dict["edge_distance_vec"]
|
| 418 |
+
graph_dict["edge_distance_full"] = graph_dict["edge_distance"]
|
| 419 |
+
graph_dict["edge_index_full"] = graph_dict["edge_index"]
|
| 420 |
+
|
| 421 |
+
return graph_dict
|
| 422 |
+
|
| 423 |
+
@conditional_grad(torch.enable_grad())
|
| 424 |
+
def forward(self, data_dict) -> dict[str, torch.Tensor]:
|
| 425 |
+
data_dict["atomic_numbers"] = data_dict["atomic_numbers"].long()
|
| 426 |
+
data_dict["atomic_numbers_full"] = data_dict["atomic_numbers"]
|
| 427 |
+
data_dict["batch_full"] = data_dict["batch"]
|
| 428 |
+
|
| 429 |
+
csd_mixed_emb = self.csd_embedding(
|
| 430 |
+
charge=data_dict["charge"],
|
| 431 |
+
spin=data_dict["spin"],
|
| 432 |
+
dataset=data_dict.get("dataset", None),
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
self.set_MOLE_coefficients(
|
| 436 |
+
atomic_numbers_full=data_dict["atomic_numbers_full"],
|
| 437 |
+
batch_full=data_dict["batch_full"],
|
| 438 |
+
csd_mixed_emb=csd_mixed_emb,
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
with record_function("get_displacement_and_cell"):
|
| 442 |
+
displacement, orig_cell = self._get_displacement_and_cell(data_dict)
|
| 443 |
+
|
| 444 |
+
with record_function("generate_graph"):
|
| 445 |
+
graph_dict = self._generate_graph(data_dict)
|
| 446 |
+
|
| 447 |
+
if graph_dict["edge_index"].numel() == 0:
|
| 448 |
+
raise ValueError(
|
| 449 |
+
f"No edges found in input system, this means either you have a single atom in the system or the atoms are farther apart than the radius cutoff of the model of {self.cutoff} Angstroms. We don't know how to handle this case. Check the positions of system: {data_dict['pos']}"
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
with record_function("obtain wigner"):
|
| 453 |
+
(edge_rot_mat, wigner_and_M_mapping_full, wigner_and_M_mapping_inv_full) = (
|
| 454 |
+
self._get_rotmat_and_wigner(
|
| 455 |
+
graph_dict["edge_distance_vec_full"],
|
| 456 |
+
use_cuda_graph=self.use_cuda_graph_wigner
|
| 457 |
+
and "cuda" in get_device_for_local_rank()
|
| 458 |
+
and not self.training,
|
| 459 |
+
)
|
| 460 |
+
)
|
| 461 |
+
# As a sanity check this should all be 0, dist, 0 (dist = scalar distance)
|
| 462 |
+
# rotated_ones = torch.bmm(edge_rot_mat, graph_dict["edge_distance_vec"].unsqueeze(-1)).squeeze(-1)
|
| 463 |
+
if gp_utils.initialized():
|
| 464 |
+
wigner_and_M_mapping = wigner_and_M_mapping_full[
|
| 465 |
+
graph_dict["edge_partition"]
|
| 466 |
+
]
|
| 467 |
+
wigner_and_M_mapping_inv = wigner_and_M_mapping_inv_full[
|
| 468 |
+
graph_dict["edge_partition"]
|
| 469 |
+
]
|
| 470 |
+
else:
|
| 471 |
+
wigner_and_M_mapping = wigner_and_M_mapping_full
|
| 472 |
+
wigner_and_M_mapping_inv = wigner_and_M_mapping_inv_full
|
| 473 |
+
|
| 474 |
+
###############################################################
|
| 475 |
+
# Initialize node embeddings
|
| 476 |
+
###############################################################
|
| 477 |
+
|
| 478 |
+
# Init per node representations using an atomic number based embedding
|
| 479 |
+
with record_function("atom embedding"):
|
| 480 |
+
x_message = torch.zeros(
|
| 481 |
+
data_dict["atomic_numbers"].shape[0],
|
| 482 |
+
self.sph_feature_size,
|
| 483 |
+
self.sphere_channels,
|
| 484 |
+
device=data_dict["pos"].device,
|
| 485 |
+
dtype=data_dict["pos"].dtype,
|
| 486 |
+
)
|
| 487 |
+
x_message[:, 0, :] = self.sphere_embedding(data_dict["atomic_numbers"])
|
| 488 |
+
|
| 489 |
+
sys_node_embedding = csd_mixed_emb[data_dict["batch"]]
|
| 490 |
+
x_message[:, 0, :] = x_message[:, 0, :] + sys_node_embedding
|
| 491 |
+
|
| 492 |
+
###
|
| 493 |
+
# Hook to allow MOLE
|
| 494 |
+
###
|
| 495 |
+
self.set_MOLE_sizes(
|
| 496 |
+
nsystems=csd_mixed_emb.shape[0],
|
| 497 |
+
batch_full=data_dict["batch_full"],
|
| 498 |
+
edge_index=graph_dict["edge_index"],
|
| 499 |
+
)
|
| 500 |
+
self.log_MOLE_stats()
|
| 501 |
+
|
| 502 |
+
# edge degree embedding
|
| 503 |
+
with record_function("edge embedding"):
|
| 504 |
+
edge_distance_embedding = self.distance_expansion(
|
| 505 |
+
graph_dict["edge_distance"]
|
| 506 |
+
)
|
| 507 |
+
source_embedding = self.source_embedding(
|
| 508 |
+
data_dict["atomic_numbers_full"][graph_dict["edge_index"][0]]
|
| 509 |
+
)
|
| 510 |
+
target_embedding = self.target_embedding(
|
| 511 |
+
data_dict["atomic_numbers_full"][graph_dict["edge_index"][1]]
|
| 512 |
+
)
|
| 513 |
+
x_edge = torch.cat(
|
| 514 |
+
(edge_distance_embedding, source_embedding, target_embedding), dim=1
|
| 515 |
+
)
|
| 516 |
+
x_message = self.edge_degree_embedding(
|
| 517 |
+
x_message,
|
| 518 |
+
x_edge,
|
| 519 |
+
graph_dict["edge_distance"],
|
| 520 |
+
graph_dict["edge_index"],
|
| 521 |
+
wigner_and_M_mapping_inv,
|
| 522 |
+
graph_dict["node_offset"],
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
###############################################################
|
| 526 |
+
# Update spherical node embeddings
|
| 527 |
+
###############################################################
|
| 528 |
+
for i in range(self.num_layers):
|
| 529 |
+
with record_function(f"message passing {i}"):
|
| 530 |
+
x_message = self.blocks[i](
|
| 531 |
+
x_message,
|
| 532 |
+
x_edge,
|
| 533 |
+
graph_dict["edge_distance"],
|
| 534 |
+
graph_dict["edge_index"],
|
| 535 |
+
wigner_and_M_mapping,
|
| 536 |
+
wigner_and_M_mapping_inv,
|
| 537 |
+
sys_node_embedding=sys_node_embedding,
|
| 538 |
+
node_offset=graph_dict["node_offset"],
|
| 539 |
+
)
|
| 540 |
+
|
| 541 |
+
# Final layer norm
|
| 542 |
+
x_message = self.norm(x_message)
|
| 543 |
+
out = {
|
| 544 |
+
"node_embedding": x_message,
|
| 545 |
+
"displacement": displacement,
|
| 546 |
+
"orig_cell": orig_cell,
|
| 547 |
+
"batch": data_dict["batch"],
|
| 548 |
+
}
|
| 549 |
+
return out
|
| 550 |
+
|
| 551 |
+
def _init_gp_partitions(self, graph_dict, atomic_numbers_full):
|
| 552 |
+
"""Graph Parallel
|
| 553 |
+
This creates the required partial tensors for each rank given the full tensors.
|
| 554 |
+
The tensors are split on the dimension along the node index using node_partition.
|
| 555 |
+
"""
|
| 556 |
+
edge_index = graph_dict["edge_index"]
|
| 557 |
+
edge_distance = graph_dict["edge_distance"]
|
| 558 |
+
edge_distance_vec_full = graph_dict["edge_distance_vec"]
|
| 559 |
+
|
| 560 |
+
node_partition = torch.tensor_split(
|
| 561 |
+
torch.arange(len(atomic_numbers_full)).to(atomic_numbers_full.device),
|
| 562 |
+
gp_utils.get_gp_world_size(),
|
| 563 |
+
)[gp_utils.get_gp_rank()]
|
| 564 |
+
|
| 565 |
+
assert (
|
| 566 |
+
node_partition.numel() > 0
|
| 567 |
+
), "Looks like there is no atoms in this graph paralell partition. Cannot proceed"
|
| 568 |
+
edge_partition = torch.where(
|
| 569 |
+
torch.logical_and(
|
| 570 |
+
edge_index[1] >= node_partition.min(),
|
| 571 |
+
edge_index[1] <= node_partition.max(), # TODO: 0 or 1?
|
| 572 |
+
)
|
| 573 |
+
)[0]
|
| 574 |
+
|
| 575 |
+
# full versions of data
|
| 576 |
+
graph_dict["edge_distance_vec_full"] = edge_distance_vec_full
|
| 577 |
+
graph_dict["edge_distance_full"] = edge_distance
|
| 578 |
+
graph_dict["edge_index_full"] = edge_index
|
| 579 |
+
graph_dict["edge_partition"] = edge_partition
|
| 580 |
+
graph_dict["node_partition"] = node_partition
|
| 581 |
+
|
| 582 |
+
# gp versions of data
|
| 583 |
+
graph_dict["edge_index"] = edge_index[:, edge_partition]
|
| 584 |
+
graph_dict["edge_distance"] = edge_distance[edge_partition]
|
| 585 |
+
graph_dict["edge_distance_vec"] = edge_distance_vec_full[edge_partition]
|
| 586 |
+
graph_dict["node_offset"] = node_partition.min().item()
|
| 587 |
+
|
| 588 |
+
return graph_dict
|
| 589 |
+
|
| 590 |
+
@property
|
| 591 |
+
def num_params(self):
|
| 592 |
+
return sum(p.numel() for p in self.parameters())
|
| 593 |
+
|
| 594 |
+
@torch.jit.ignore
|
| 595 |
+
def no_weight_decay(self) -> set:
|
| 596 |
+
no_wd_list = []
|
| 597 |
+
named_parameters_list = [name for name, _ in self.named_parameters()]
|
| 598 |
+
for module_name, module in self.named_modules():
|
| 599 |
+
if isinstance(
|
| 600 |
+
module,
|
| 601 |
+
(
|
| 602 |
+
torch.nn.Linear,
|
| 603 |
+
SO3_Linear,
|
| 604 |
+
torch.nn.LayerNorm,
|
| 605 |
+
EquivariantLayerNormArray,
|
| 606 |
+
EquivariantLayerNormArraySphericalHarmonics,
|
| 607 |
+
EquivariantRMSNormArraySphericalHarmonics,
|
| 608 |
+
EquivariantRMSNormArraySphericalHarmonicsV2,
|
| 609 |
+
),
|
| 610 |
+
):
|
| 611 |
+
for parameter_name, _ in module.named_parameters():
|
| 612 |
+
if (
|
| 613 |
+
isinstance(module, (torch.nn.Linear, SO3_Linear))
|
| 614 |
+
and "weight" in parameter_name
|
| 615 |
+
):
|
| 616 |
+
continue
|
| 617 |
+
global_parameter_name = module_name + "." + parameter_name
|
| 618 |
+
assert global_parameter_name in named_parameters_list
|
| 619 |
+
no_wd_list.append(global_parameter_name)
|
| 620 |
+
|
| 621 |
+
return set(no_wd_list)
|
model/uma_escn_moe.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
import warnings
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
from matplotlib import pyplot as plt
|
| 12 |
+
|
| 13 |
+
from onescience.modules.head.uma_head import (
|
| 14 |
+
DatasetSpecificMoEWrapper,
|
| 15 |
+
DatasetSpecificSingleHeadWrapper,
|
| 16 |
+
)
|
| 17 |
+
from onescience.utils.uma.common.registry import registry
|
| 18 |
+
from model.uma_escn_md import eSCNMDBackbone
|
| 19 |
+
from onescience.modules.func_utils.uma_mole_utils import (
|
| 20 |
+
MOLEInterface,
|
| 21 |
+
convert_model_to_MOLE_model,
|
| 22 |
+
model_search_and_replace,
|
| 23 |
+
recursive_replace_so2_MOLE,
|
| 24 |
+
replace_MOLE_with_linear,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
# This will catch the warning despite its C++ origin
|
| 28 |
+
# torch.Tensor.index_reduce is in beta
|
| 29 |
+
warnings.filterwarnings(
|
| 30 |
+
"ignore",
|
| 31 |
+
message="index_reduce\\(\\) is in beta",
|
| 32 |
+
category=UserWarning,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@registry.register_model("escnmd_moe_backbone")
|
| 37 |
+
class eSCNMDMoeBackbone(eSCNMDBackbone, MOLEInterface):
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
num_experts: int = 8,
|
| 41 |
+
moe_dropout: float = 0.0,
|
| 42 |
+
use_global_embedding: bool = False, # obsolete
|
| 43 |
+
use_composition_embedding: bool = False,
|
| 44 |
+
moe_expert_coefficient_norm: str = "softmax",
|
| 45 |
+
act=torch.nn.SiLU,
|
| 46 |
+
layers_moe=None,
|
| 47 |
+
moe_layer_type: str = "pytorch",
|
| 48 |
+
moe_single: bool = False,
|
| 49 |
+
moe_type: str = "so2",
|
| 50 |
+
model_version: float = 1.0,
|
| 51 |
+
**kwargs,
|
| 52 |
+
):
|
| 53 |
+
super().__init__(**kwargs)
|
| 54 |
+
self.parent_kwargs = kwargs
|
| 55 |
+
self.num_experts = num_experts
|
| 56 |
+
self.model_version = model_version
|
| 57 |
+
if num_experts > 0:
|
| 58 |
+
convert_model_to_MOLE_model(
|
| 59 |
+
model=self,
|
| 60 |
+
num_experts=num_experts,
|
| 61 |
+
mole_dropout=moe_dropout,
|
| 62 |
+
mole_expert_coefficient_norm=moe_expert_coefficient_norm,
|
| 63 |
+
act=act,
|
| 64 |
+
layers_mole=layers_moe,
|
| 65 |
+
use_composition_embedding=use_composition_embedding,
|
| 66 |
+
mole_layer_type=moe_layer_type,
|
| 67 |
+
mole_single=moe_single,
|
| 68 |
+
mole_type=moe_type,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
def merge_MOLE_model(self, data):
|
| 72 |
+
if self.num_experts == 0:
|
| 73 |
+
return self
|
| 74 |
+
data["atomic_numbers"] = data["atomic_numbers"].long()
|
| 75 |
+
csd_mixed_emb = self.csd_embedding(
|
| 76 |
+
charge=data["charge"],
|
| 77 |
+
spin=data["spin"],
|
| 78 |
+
dataset=data["dataset"],
|
| 79 |
+
)
|
| 80 |
+
self.set_MOLE_coefficients(
|
| 81 |
+
atomic_numbers_full=data["atomic_numbers"],
|
| 82 |
+
batch_full=data["batch"],
|
| 83 |
+
csd_mixed_emb=csd_mixed_emb,
|
| 84 |
+
)
|
| 85 |
+
if self.mole_type != "so2":
|
| 86 |
+
raise ValueError("Only mole_type=so2 supported for merging")
|
| 87 |
+
|
| 88 |
+
model_search_and_replace(
|
| 89 |
+
self, recursive_replace_so2_MOLE, replace_MOLE_with_linear
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
# drop moe parameters from merged model
|
| 93 |
+
self.routing_mlp = None
|
| 94 |
+
self.composition_embedding = None
|
| 95 |
+
self.num_experts = 0
|
| 96 |
+
|
| 97 |
+
# create a new non moe model and load weights into there
|
| 98 |
+
new_model = eSCNMDBackbone(**self.parent_kwargs)
|
| 99 |
+
new_model.load_state_dict(self.state_dict())
|
| 100 |
+
new_model.eval()
|
| 101 |
+
return new_model
|
| 102 |
+
|
| 103 |
+
def set_MOLE_coefficients(self, atomic_numbers_full, batch_full, csd_mixed_emb):
|
| 104 |
+
if self.num_experts == 0:
|
| 105 |
+
return
|
| 106 |
+
with torch.autocast(device_type=atomic_numbers_full.device.type, enabled=False):
|
| 107 |
+
embeddings = []
|
| 108 |
+
if self.use_composition_embedding:
|
| 109 |
+
composition_by_atom = self.composition_embedding(atomic_numbers_full)
|
| 110 |
+
composition = composition_by_atom.new_zeros(
|
| 111 |
+
csd_mixed_emb.shape[0],
|
| 112 |
+
self.sphere_channels,
|
| 113 |
+
).index_reduce_(
|
| 114 |
+
0,
|
| 115 |
+
batch_full,
|
| 116 |
+
composition_by_atom,
|
| 117 |
+
reduce="mean",
|
| 118 |
+
include_self=np.isclose(self.model_version, 1.0).item(),
|
| 119 |
+
)
|
| 120 |
+
embeddings.append(composition.unsqueeze(0))
|
| 121 |
+
embeddings.append(csd_mixed_emb[None])
|
| 122 |
+
|
| 123 |
+
expert_mixing_coefficients_before_norm = self.routing_mlp(
|
| 124 |
+
torch.vstack(embeddings)
|
| 125 |
+
.transpose(0, 1)
|
| 126 |
+
.reshape(csd_mixed_emb.shape[0], -1)
|
| 127 |
+
)
|
| 128 |
+
self.global_mole_tensors.expert_mixing_coefficients = (
|
| 129 |
+
self.mole_expert_coefficient_norm(
|
| 130 |
+
self.mole_dropout(expert_mixing_coefficients_before_norm)
|
| 131 |
+
)
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
def set_MOLE_sizes(self, nsystems, batch_full, edge_index):
|
| 135 |
+
if self.num_experts == 0:
|
| 136 |
+
return
|
| 137 |
+
with torch.autocast(device_type=batch_full.device.type, enabled=False):
|
| 138 |
+
# Generate edge mix_size routing each edge in this instance (GP or not)
|
| 139 |
+
# using its local edge and batch routing
|
| 140 |
+
|
| 141 |
+
# Local edge_index is 2xN where [1,:] is the target node, the target node does not
|
| 142 |
+
# have the gp offset applied, which means we need to lookup in the full batch_full
|
| 143 |
+
# _, mix_size = torch.unique(data.batch_full[edge_index[1]], return_counts=True)
|
| 144 |
+
mole_sizes = torch.zeros(
|
| 145 |
+
nsystems, # data.natoms.shape[0],
|
| 146 |
+
dtype=torch.int,
|
| 147 |
+
device=batch_full[edge_index[1]].device,
|
| 148 |
+
).scatter_(0, batch_full[edge_index[1]], 1, reduce="add")
|
| 149 |
+
|
| 150 |
+
self.global_mole_tensors.mole_sizes = mole_sizes.cpu()
|
| 151 |
+
|
| 152 |
+
def log_MOLE_stats(self):
|
| 153 |
+
if not self.training or self.num_experts == 0:
|
| 154 |
+
return
|
| 155 |
+
if not hasattr(self, "fig"):
|
| 156 |
+
self.fig, self.axs = plt.subplots(2, 1)
|
| 157 |
+
with torch.no_grad():
|
| 158 |
+
if self.counter % 500 == 0:
|
| 159 |
+
logging.info(
|
| 160 |
+
f"{self.counter }: Expert variance: "
|
| 161 |
+
+ ",".join(
|
| 162 |
+
[
|
| 163 |
+
f"{x:.2e}"
|
| 164 |
+
for x in self.global_mole_tensors.expert_mixing_coefficients.var(
|
| 165 |
+
axis=0
|
| 166 |
+
).tolist()
|
| 167 |
+
]
|
| 168 |
+
)
|
| 169 |
+
)
|
| 170 |
+
logging.info(
|
| 171 |
+
f"{self.counter }: Expert mean: "
|
| 172 |
+
+ ",".join(
|
| 173 |
+
[
|
| 174 |
+
f"{x:.2e}"
|
| 175 |
+
for x in self.global_mole_tensors.expert_mixing_coefficients.mean(
|
| 176 |
+
axis=0
|
| 177 |
+
).tolist()
|
| 178 |
+
]
|
| 179 |
+
)
|
| 180 |
+
)
|
| 181 |
+
self.fig.tight_layout()
|
| 182 |
+
self.plot_ready = True
|
| 183 |
+
|
| 184 |
+
self.counter += 1
|
models-json/pretrained_models.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"uma-s-1": {
|
| 3 |
+
"filename": "uma-s-1.pt",
|
| 4 |
+
"repo_id": "facebook/UMA",
|
| 5 |
+
"subfolder": "checkpoints",
|
| 6 |
+
"atom_refs": {
|
| 7 |
+
"subfolder": "references",
|
| 8 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 9 |
+
}
|
| 10 |
+
},
|
| 11 |
+
"uma-s-1p1": {
|
| 12 |
+
"filename": "uma-s-1p1.pt",
|
| 13 |
+
"repo_id": "facebook/UMA",
|
| 14 |
+
"subfolder": "checkpoints",
|
| 15 |
+
"atom_refs": {
|
| 16 |
+
"subfolder": "references",
|
| 17 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 18 |
+
}
|
| 19 |
+
},
|
| 20 |
+
"uma-m-1p1": {
|
| 21 |
+
"filename": "uma-m-1p1.pt",
|
| 22 |
+
"repo_id": "facebook/UMA",
|
| 23 |
+
"subfolder": "checkpoints",
|
| 24 |
+
"atom_refs": {
|
| 25 |
+
"subfolder": "references",
|
| 26 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"esen-md-direct-all-omol": {
|
| 30 |
+
"filename": "esen_md_direct_all.pt",
|
| 31 |
+
"repo_id": "facebook/OMol25",
|
| 32 |
+
"subfolder": "checkpoints",
|
| 33 |
+
"atom_refs": {
|
| 34 |
+
"subfolder": "references",
|
| 35 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
"esen-sm-conserving-all-omol": {
|
| 39 |
+
"filename": "esen_sm_conserving_all.pt",
|
| 40 |
+
"repo_id": "facebook/OMol25",
|
| 41 |
+
"subfolder": "checkpoints",
|
| 42 |
+
"atom_refs": {
|
| 43 |
+
"subfolder": "references",
|
| 44 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
"esen-sm-direct-all-omol": {
|
| 48 |
+
"filename": "esen_sm_direct_all.pt",
|
| 49 |
+
"repo_id": "facebook/OMol25",
|
| 50 |
+
"subfolder": "checkpoints",
|
| 51 |
+
"atom_refs": {
|
| 52 |
+
"subfolder": "references",
|
| 53 |
+
"filename": "iso_atom_elem_refs.yaml"
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
scripts/convert_model.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import argparse
|
| 3 |
+
import pickle
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import torch
|
| 6 |
+
from omegaconf import OmegaConf, DictConfig, ListConfig
|
| 7 |
+
|
| 8 |
+
ROOT = Path(__file__).resolve().parents[2]
|
| 9 |
+
BACKBONE_MOE = "model.uma_escn_moe"
|
| 10 |
+
BACKBONE_MD = "model.uma_escn_md"
|
| 11 |
+
BACKBONE_BASE = "model.base"
|
| 12 |
+
|
| 13 |
+
RULES = [
|
| 14 |
+
("onescience.models.UMA.models.base", BACKBONE_BASE),
|
| 15 |
+
("onescience.models.UMA.base", BACKBONE_BASE),
|
| 16 |
+
("onescience.models.UMA.models.uma.escn_md", BACKBONE_MD),
|
| 17 |
+
("onescience.models.UMA.models.uma.escn_moe", BACKBONE_MOE),
|
| 18 |
+
("onescience.models.UMA.uma.escn_md", BACKBONE_MD),
|
| 19 |
+
("onescience.models.UMA.uma.escn_moe", BACKBONE_MOE),
|
| 20 |
+
("onescience.models.UMA.uma_escn_md", BACKBONE_MD),
|
| 21 |
+
("onescience.models.UMA.uma_escn_moe", BACKBONE_MOE),
|
| 22 |
+
("onescience.utils.uma.models.uma.escn_md", BACKBONE_MD),
|
| 23 |
+
("onescience.utils.uma.models.uma.escn_moe", BACKBONE_MOE),
|
| 24 |
+
("fairchem.core.models.base", BACKBONE_BASE),
|
| 25 |
+
("fairchem.core.models.uma.escn_md", BACKBONE_MD),
|
| 26 |
+
("fairchem.core.models.uma.escn_moe", BACKBONE_MOE),
|
| 27 |
+
("onescience.models.UMA.units", "onescience.utils.uma.units"),
|
| 28 |
+
("onescience.models.UMA.modules.head", "onescience.modules.head.uma_head"),
|
| 29 |
+
("onescience.models.UMA.modules.loss", "onescience.modules.loss.uma_loss"),
|
| 30 |
+
("onescience.models.UMA.common", "onescience.utils.uma.common"),
|
| 31 |
+
("onescience.utils.uma.modules.head", "onescience.modules.head.uma_head"),
|
| 32 |
+
("onescience.utils.uma.modules.loss", "onescience.modules.loss.uma_loss"),
|
| 33 |
+
("fairchem.core.units", "onescience.utils.uma.units"),
|
| 34 |
+
("fairchem.core.modules.head", "onescience.modules.head.uma_head"),
|
| 35 |
+
("fairchem.core.modules.loss", "onescience.modules.loss.uma_loss"),
|
| 36 |
+
("fairchem.core.components", "onescience.utils.uma.components"),
|
| 37 |
+
("fairchem.core.common", "onescience.utils.uma.common"),
|
| 38 |
+
("fairchem.core", "onescience.utils.uma"),
|
| 39 |
+
# 兜底:修复之前被错误转换的中间状态
|
| 40 |
+
("onescience.modules.normalization", "onescience.utils.uma.normalization"),
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
LEGACY_PREFIX = (
|
| 44 |
+
"onescience.models.UMA.models.",
|
| 45 |
+
"onescience.models.UMA.uma.",
|
| 46 |
+
"onescience.utils.uma.models.",
|
| 47 |
+
"onescience.utils.uma.modules.head.",
|
| 48 |
+
"onescience.utils.uma.modules.loss.",
|
| 49 |
+
"fairchem.core.models.",
|
| 50 |
+
"fairchem.core.modules.head.",
|
| 51 |
+
"fairchem.core.modules.loss.",
|
| 52 |
+
"onescience.modules.normalization.",
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
def remap(s: str) -> str:
|
| 56 |
+
prev = None
|
| 57 |
+
cur = s
|
| 58 |
+
while cur != prev:
|
| 59 |
+
prev = cur
|
| 60 |
+
for old, new in RULES:
|
| 61 |
+
if cur == old or cur.startswith(old + "."):
|
| 62 |
+
cur = new + cur[len(old):]
|
| 63 |
+
break
|
| 64 |
+
# 兜底:修复之前错误转换出的中间状态
|
| 65 |
+
if cur.startswith("onescience.modules.loss.") and not cur.startswith("onescience.modules.loss.uma_loss."):
|
| 66 |
+
cur = "onescience.modules.loss.uma_loss." + cur[len("onescience.modules.loss."):]
|
| 67 |
+
if cur.startswith("onescience.modules.head.") and not cur.startswith("onescience.modules.head.uma_head."):
|
| 68 |
+
cur = "onescience.modules.head.uma_head." + cur[len("onescience.modules.head."):]
|
| 69 |
+
return cur
|
| 70 |
+
|
| 71 |
+
def to_py(x):
|
| 72 |
+
if isinstance(x, (DictConfig, ListConfig)):
|
| 73 |
+
return OmegaConf.to_container(x, resolve=False)
|
| 74 |
+
return x
|
| 75 |
+
|
| 76 |
+
def walk(x):
|
| 77 |
+
if isinstance(x, str):
|
| 78 |
+
return remap(x)
|
| 79 |
+
if isinstance(x, dict):
|
| 80 |
+
return {k: walk(v) for k, v in x.items()}
|
| 81 |
+
if isinstance(x, list):
|
| 82 |
+
return [walk(v) for v in x]
|
| 83 |
+
return x
|
| 84 |
+
|
| 85 |
+
def find_legacy(x, path="root"):
|
| 86 |
+
out = []
|
| 87 |
+
if isinstance(x, str):
|
| 88 |
+
if x.startswith(LEGACY_PREFIX):
|
| 89 |
+
out.append((path, x))
|
| 90 |
+
elif isinstance(x, dict):
|
| 91 |
+
for k, v in x.items():
|
| 92 |
+
out.extend(find_legacy(v, f"{path}.{k}"))
|
| 93 |
+
elif isinstance(x, list):
|
| 94 |
+
for i, v in enumerate(x):
|
| 95 |
+
out.extend(find_legacy(v, f"{path}[{i}]"))
|
| 96 |
+
return out
|
| 97 |
+
|
| 98 |
+
class CustomUnpickler(pickle.Unpickler):
|
| 99 |
+
def find_class(self, module_name, class_name):
|
| 100 |
+
mapped = remap(module_name)
|
| 101 |
+
try:
|
| 102 |
+
return super().find_class(mapped, class_name)
|
| 103 |
+
except ModuleNotFoundError:
|
| 104 |
+
return super().find_class(module_name, class_name)
|
| 105 |
+
|
| 106 |
+
class CustomPickleModule:
|
| 107 |
+
Unpickler = CustomUnpickler
|
| 108 |
+
|
| 109 |
+
def gf(obj, k):
|
| 110 |
+
return obj[k] if isinstance(obj, dict) else getattr(obj, k)
|
| 111 |
+
|
| 112 |
+
def sf(obj, k, v):
|
| 113 |
+
if isinstance(obj, dict):
|
| 114 |
+
obj[k] = v
|
| 115 |
+
else:
|
| 116 |
+
setattr(obj, k, v)
|
| 117 |
+
|
| 118 |
+
def main():
|
| 119 |
+
ap = argparse.ArgumentParser()
|
| 120 |
+
ap.add_argument("src")
|
| 121 |
+
ap.add_argument("--out", required=True)
|
| 122 |
+
args = ap.parse_args()
|
| 123 |
+
|
| 124 |
+
print(f"[*] loading: {args.src}")
|
| 125 |
+
try:
|
| 126 |
+
obj = torch.load(args.src, map_location="cpu", pickle_module=CustomPickleModule, weights_only=False)
|
| 127 |
+
except TypeError:
|
| 128 |
+
obj = torch.load(args.src, map_location="cpu", pickle_module=CustomPickleModule)
|
| 129 |
+
|
| 130 |
+
mc = OmegaConf.create(walk(to_py(gf(obj, "model_config"))))
|
| 131 |
+
tc = OmegaConf.create(walk(to_py(gf(obj, "tasks_config"))))
|
| 132 |
+
sf(obj, "model_config", mc)
|
| 133 |
+
sf(obj, "tasks_config", tc)
|
| 134 |
+
|
| 135 |
+
cfg_py = OmegaConf.to_container(mc, resolve=False)
|
| 136 |
+
bad = find_legacy(cfg_py)
|
| 137 |
+
if bad:
|
| 138 |
+
print("[FATAL] legacy paths still exist in model_config:")
|
| 139 |
+
for p, v in bad[:20]:
|
| 140 |
+
print(" ", p, "=", v)
|
| 141 |
+
raise SystemExit(2)
|
| 142 |
+
|
| 143 |
+
out = Path(args.out)
|
| 144 |
+
out.parent.mkdir(parents=True, exist_ok=True)
|
| 145 |
+
torch.save(obj, str(out))
|
| 146 |
+
print("[OK] saved:", out)
|
| 147 |
+
print("[OK] model _target_:", mc.get("_target_", "<none>"))
|
| 148 |
+
print("[OK] backbone model:", mc.get("backbone", {}).get("model"))
|
| 149 |
+
|
| 150 |
+
if __name__ == "__main__":
|
| 151 |
+
main()
|
scripts/create_finetune_dataset.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import glob
|
| 5 |
+
import logging
|
| 6 |
+
import multiprocessing as mp
|
| 7 |
+
import os
|
| 8 |
+
import random
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from ase.db import connect
|
| 13 |
+
from ase.io import read
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
|
| 16 |
+
from onescience.datapipes.materials.custom_stack.storage import AseDBDataset
|
| 17 |
+
logging.basicConfig(level=logging.INFO)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def compute_normalizer_and_linear_reference(train_path, num_workers):
|
| 21 |
+
"""
|
| 22 |
+
Given a path to an ASE database file, compute the normalizer value and linear
|
| 23 |
+
reference coefficients. These are used to normalize energies and forces during
|
| 24 |
+
training. For large datasets, compute this for only a subset of the data.
|
| 25 |
+
"""
|
| 26 |
+
global dataset
|
| 27 |
+
dataset = AseDBDataset({"src": str(train_path)})
|
| 28 |
+
|
| 29 |
+
sample_indices = random.sample(range(len(dataset)), min(100000, len(dataset)))
|
| 30 |
+
with mp.Pool(num_workers) as pool:
|
| 31 |
+
outputs = list(
|
| 32 |
+
tqdm(
|
| 33 |
+
pool.imap(extract_energy_and_forces, sample_indices),
|
| 34 |
+
total=len(sample_indices),
|
| 35 |
+
desc="Computing normalizer values.",
|
| 36 |
+
)
|
| 37 |
+
)
|
| 38 |
+
atomic_numbers = [x[0] for x in outputs]
|
| 39 |
+
energies = [x[1] for x in outputs]
|
| 40 |
+
forces = np.array([force for x in outputs for force in x[2]])
|
| 41 |
+
force_rms = np.sqrt(np.mean(np.square(forces)))
|
| 42 |
+
assert (
|
| 43 |
+
np.isfinite(force_rms).all()
|
| 44 |
+
), "We found non-finite values in the forces, please check your input data!"
|
| 45 |
+
coeff = compute_lin_ref(atomic_numbers, energies)
|
| 46 |
+
return force_rms, coeff
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def extract_energy_and_forces(idx):
|
| 50 |
+
"""
|
| 51 |
+
Extract energy and forces from an ASE atoms object at a given index in the dataset.
|
| 52 |
+
"""
|
| 53 |
+
atoms = dataset.get_atoms(idx)
|
| 54 |
+
atomic_numbers = atoms.get_atomic_numbers()
|
| 55 |
+
energy = atoms.get_potential_energy()
|
| 56 |
+
forces = atoms.get_forces()
|
| 57 |
+
n_atoms = len(atoms)
|
| 58 |
+
|
| 59 |
+
fixed_idx = np.zeros(n_atoms)
|
| 60 |
+
if hasattr(atoms, "constraints"):
|
| 61 |
+
from ase.constraints import FixAtoms
|
| 62 |
+
|
| 63 |
+
for constraint in atoms.constraints:
|
| 64 |
+
if isinstance(constraint, FixAtoms):
|
| 65 |
+
fixed_idx[constraint.index] = 1
|
| 66 |
+
|
| 67 |
+
mask = fixed_idx == 0
|
| 68 |
+
forces = forces[mask]
|
| 69 |
+
|
| 70 |
+
return atomic_numbers, energy, forces
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def compute_lin_ref(atomic_numbers, energies):
|
| 74 |
+
"""
|
| 75 |
+
Compute linear reference coefficients given atomic numbers and energies.
|
| 76 |
+
"""
|
| 77 |
+
features = [np.bincount(x, minlength=100).astype(int) for x in atomic_numbers]
|
| 78 |
+
|
| 79 |
+
X = np.vstack(features)
|
| 80 |
+
y = energies
|
| 81 |
+
|
| 82 |
+
coeff = np.linalg.lstsq(X, y, rcond=None)[0]
|
| 83 |
+
assert np.isfinite(
|
| 84 |
+
coeff
|
| 85 |
+
).all(), "Found some non-finite values while computing element references, please check/sanitize your inputs before proceeding"
|
| 86 |
+
return coeff.tolist()
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def write_ase_db(mp_arg):
|
| 90 |
+
"""
|
| 91 |
+
Write ASE atoms objects to an ASE database file. This function is designed to be
|
| 92 |
+
run in parallel using multiprocessing.
|
| 93 |
+
"""
|
| 94 |
+
db_file, file_list, worker_id = mp_arg
|
| 95 |
+
|
| 96 |
+
successful = []
|
| 97 |
+
failed = []
|
| 98 |
+
natoms = []
|
| 99 |
+
with connect(str(db_file)) as db:
|
| 100 |
+
for file in tqdm(file_list, position=worker_id):
|
| 101 |
+
atoms_list = read(file, ":")
|
| 102 |
+
for i, atoms in enumerate(atoms_list):
|
| 103 |
+
try:
|
| 104 |
+
assert (
|
| 105 |
+
atoms.calc is not None
|
| 106 |
+
), "No calculator attached to atoms object."
|
| 107 |
+
assert "energy" in atoms.calc.results, "Missing energy result"
|
| 108 |
+
assert "forces" in atoms.calc.results, "Missing forces result"
|
| 109 |
+
db.write(atoms, data=atoms.info)
|
| 110 |
+
natoms.append(len(atoms))
|
| 111 |
+
successful.append(f"{file},{i}")
|
| 112 |
+
except AssertionError as e:
|
| 113 |
+
failed.append(f"{file},{i}: {e!s}")
|
| 114 |
+
|
| 115 |
+
return db_file, natoms, successful, failed
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def launch_processing(data_dir, output_dir, num_workers):
|
| 119 |
+
"""
|
| 120 |
+
Driver script to launch processing of ASE atoms files into an ASE database.
|
| 121 |
+
"""
|
| 122 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 123 |
+
input_files = [
|
| 124 |
+
f
|
| 125 |
+
for f in glob.glob(os.path.join(data_dir, "**/*"), recursive=True)
|
| 126 |
+
if os.path.isfile(f)
|
| 127 |
+
]
|
| 128 |
+
chunked_files = np.array_split(input_files, num_workers)
|
| 129 |
+
db_files = [output_dir / f"data.{i:04d}.aselmdb" for i in range(num_workers)]
|
| 130 |
+
mp_args = [(db_files[i], chunked_files[i], i) for i in range(num_workers)]
|
| 131 |
+
with mp.Pool(num_workers) as pool:
|
| 132 |
+
outputs = pool.map(write_ase_db, mp_args)
|
| 133 |
+
|
| 134 |
+
# Log results
|
| 135 |
+
natoms = []
|
| 136 |
+
for output in outputs:
|
| 137 |
+
db_file, _natoms, successful, failed = output
|
| 138 |
+
natoms.extend(_natoms)
|
| 139 |
+
log_file = db_file.with_suffix(".log")
|
| 140 |
+
failed_file = db_file.with_suffix(".failed")
|
| 141 |
+
|
| 142 |
+
with open(log_file, "w") as log:
|
| 143 |
+
log.write("\n".join(successful))
|
| 144 |
+
with open(failed_file, "w") as failed_log:
|
| 145 |
+
failed_log.write("\n".join(failed))
|
| 146 |
+
np.savez_compressed(output_dir / "metadata.npz", natoms=natoms)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
parser = argparse.ArgumentParser()
|
| 151 |
+
parser.add_argument(
|
| 152 |
+
"--train-dir",
|
| 153 |
+
type=str,
|
| 154 |
+
required=True,
|
| 155 |
+
help="Directory of ASE atoms objects to convert for training.",
|
| 156 |
+
)
|
| 157 |
+
parser.add_argument(
|
| 158 |
+
"--val-dir",
|
| 159 |
+
type=str,
|
| 160 |
+
required=True,
|
| 161 |
+
help="Directory of ASE atoms objects to convert for validation.",
|
| 162 |
+
)
|
| 163 |
+
parser.add_argument(
|
| 164 |
+
"--output-dir",
|
| 165 |
+
type=Path,
|
| 166 |
+
required=True,
|
| 167 |
+
help="Output directory to save required finetuning artifacts.",
|
| 168 |
+
)
|
| 169 |
+
parser.add_argument(
|
| 170 |
+
"--num-workers",
|
| 171 |
+
type=int,
|
| 172 |
+
default=8,
|
| 173 |
+
help="Number of parallel workers for processing files.",
|
| 174 |
+
)
|
| 175 |
+
args = parser.parse_args()
|
| 176 |
+
|
| 177 |
+
# Launch processing for training data
|
| 178 |
+
train_path = args.output_dir / "train"
|
| 179 |
+
launch_processing(args.train_dir, train_path, args.num_workers)
|
| 180 |
+
force_rms, linref_coeff = compute_normalizer_and_linear_reference(
|
| 181 |
+
train_path, args.num_workers
|
| 182 |
+
)
|
| 183 |
+
val_path = args.output_dir / "val"
|
| 184 |
+
launch_processing(args.val_dir, val_path, args.num_workers)
|
| 185 |
+
|
| 186 |
+
logging.info(f"Generated dataset at {args.output_dir}")
|
scripts/create_uma_finetune_dataset.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import yaml
|
| 9 |
+
|
| 10 |
+
from onescience.utils.uma.scripts.create_finetune_dataset import (
|
| 11 |
+
compute_normalizer_and_linear_reference,
|
| 12 |
+
launch_processing,
|
| 13 |
+
)
|
| 14 |
+
from onescience.utils.uma.units.mlip_unit.api.inference import UMATask
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
logging.basicConfig(level=logging.INFO)
|
| 18 |
+
|
| 19 |
+
TEMPLATE_DIR = Path("configs")
|
| 20 |
+
DATA_YAML_DIR = Path("data")
|
| 21 |
+
REGRESSION_LABEL_TO_TASK_YAML = {
|
| 22 |
+
"e": DATA_YAML_DIR / Path("uma_conserving_data_task_energy.yaml"),
|
| 23 |
+
"ef": DATA_YAML_DIR / Path("uma_conserving_data_task_energy_force.yaml"),
|
| 24 |
+
"efs": DATA_YAML_DIR / Path("uma_conserving_data_task_energy_force_stress.yaml"),
|
| 25 |
+
}
|
| 26 |
+
UMA_SM_FINETUNE_YAML = Path("uma_sm_finetune_template.yaml")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def create_yaml(
|
| 30 |
+
train_path: str,
|
| 31 |
+
val_path: str,
|
| 32 |
+
force_rms: float,
|
| 33 |
+
linref_coeff: list,
|
| 34 |
+
output_dir: str,
|
| 35 |
+
dataset_name: str,
|
| 36 |
+
regression_tasks: str,
|
| 37 |
+
# base_model_name: str,
|
| 38 |
+
):
|
| 39 |
+
data_task_yaml = TEMPLATE_DIR / REGRESSION_LABEL_TO_TASK_YAML[regression_tasks]
|
| 40 |
+
with open(data_task_yaml) as file:
|
| 41 |
+
template = yaml.safe_load(file)
|
| 42 |
+
template["dataset_name"] = dataset_name
|
| 43 |
+
template["normalizer_rmsd"] = force_rms
|
| 44 |
+
template["elem_refs"] = linref_coeff
|
| 45 |
+
template["train_dataset"]["splits"]["train"]["src"] = train_path
|
| 46 |
+
template["val_dataset"]["splits"]["val"]["src"] = val_path
|
| 47 |
+
# add extra large vaccum box for molecules
|
| 48 |
+
# if dataset_name == str(UMATask.OMOL):
|
| 49 |
+
# template["train_dataset"]["a2g_args"]["molecule_cell_size"] = 1000.0
|
| 50 |
+
os.makedirs(output_dir / DATA_YAML_DIR, exist_ok=True)
|
| 51 |
+
with open(
|
| 52 |
+
output_dir / REGRESSION_LABEL_TO_TASK_YAML[regression_tasks], "w"
|
| 53 |
+
) as yaml_file:
|
| 54 |
+
yaml.dump(template, yaml_file, default_flow_style=False, sort_keys=False)
|
| 55 |
+
|
| 56 |
+
uma_finetune_yaml = TEMPLATE_DIR / UMA_SM_FINETUNE_YAML
|
| 57 |
+
with open(uma_finetune_yaml) as file:
|
| 58 |
+
template_ft = yaml.safe_load(file)
|
| 59 |
+
#template_ft["base_model_name"] = base_model_name
|
| 60 |
+
template_ft["defaults"][0]["data"] = REGRESSION_LABEL_TO_TASK_YAML[
|
| 61 |
+
regression_tasks
|
| 62 |
+
].stem
|
| 63 |
+
with open(output_dir / UMA_SM_FINETUNE_YAML, "w") as yaml_file:
|
| 64 |
+
yaml.dump(template_ft, yaml_file, default_flow_style=False, sort_keys=False)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
parser = argparse.ArgumentParser()
|
| 69 |
+
parser.add_argument(
|
| 70 |
+
"--train-dir",
|
| 71 |
+
type=str,
|
| 72 |
+
required=True,
|
| 73 |
+
help="Directory of ASE atoms objects to convert for training.",
|
| 74 |
+
)
|
| 75 |
+
parser.add_argument(
|
| 76 |
+
"--val-dir",
|
| 77 |
+
type=str,
|
| 78 |
+
required=True,
|
| 79 |
+
help="Directory of ASE atoms objects to convert for validation.",
|
| 80 |
+
)
|
| 81 |
+
parser.add_argument(
|
| 82 |
+
"--uma-task",
|
| 83 |
+
type=str,
|
| 84 |
+
required=True,
|
| 85 |
+
choices=[t.value for t in UMATask],
|
| 86 |
+
help="choose a uma task to finetune",
|
| 87 |
+
)
|
| 88 |
+
parser.add_argument(
|
| 89 |
+
"--regression-tasks",
|
| 90 |
+
type=str,
|
| 91 |
+
choices=["e", "ef", "efs"],
|
| 92 |
+
required=True,
|
| 93 |
+
help="Choose to finetune based on regression task set (you must have the corresponding labels in your dataset), can be energy (e), energy+force (ef) or energy+force+stress(efs)",
|
| 94 |
+
)
|
| 95 |
+
#parser.add_argument(
|
| 96 |
+
# "--base-model",
|
| 97 |
+
# type=str,
|
| 98 |
+
# default="uma-s-1",
|
| 99 |
+
# help="Name of base uma model",
|
| 100 |
+
#)
|
| 101 |
+
parser.add_argument(
|
| 102 |
+
"--output-dir",
|
| 103 |
+
type=Path,
|
| 104 |
+
required=True,
|
| 105 |
+
help="Output directory to save required finetuning artifacts.",
|
| 106 |
+
)
|
| 107 |
+
parser.add_argument(
|
| 108 |
+
"--num-workers",
|
| 109 |
+
type=int,
|
| 110 |
+
default=8,
|
| 111 |
+
help="Number of parallel workers for processing files.",
|
| 112 |
+
)
|
| 113 |
+
args = parser.parse_args()
|
| 114 |
+
assert not Path(
|
| 115 |
+
args.output_dir
|
| 116 |
+
).exists(), f"{args.output_dir} can't already exist, please choose a different dir"
|
| 117 |
+
|
| 118 |
+
# Launch processing for training data
|
| 119 |
+
train_path = args.output_dir / "train"
|
| 120 |
+
launch_processing(args.train_dir, train_path, args.num_workers)
|
| 121 |
+
force_rms, linref_coeff = compute_normalizer_and_linear_reference(
|
| 122 |
+
train_path, args.num_workers
|
| 123 |
+
)
|
| 124 |
+
val_path = args.output_dir / "val"
|
| 125 |
+
launch_processing(args.val_dir, val_path, args.num_workers)
|
| 126 |
+
|
| 127 |
+
create_yaml(
|
| 128 |
+
train_path=str(train_path),
|
| 129 |
+
val_path=str(val_path),
|
| 130 |
+
force_rms=float(force_rms),
|
| 131 |
+
linref_coeff=linref_coeff,
|
| 132 |
+
output_dir=args.output_dir,
|
| 133 |
+
dataset_name=args.uma_task,
|
| 134 |
+
regression_tasks=args.regression_tasks,
|
| 135 |
+
# base_model_name=args.base_model,
|
| 136 |
+
)
|
| 137 |
+
logging.info(f"Generated dataset and data config yaml in {args.output_dir}")
|
| 138 |
+
logging.info(
|
| 139 |
+
f"To run finetuning, run fairchem -c {args.output_dir}/{UMA_SM_FINETUNE_YAML}"
|
| 140 |
+
)
|
scripts/update_demo_config.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""用 create_uma_finetune_dataset.py 生成的 data yaml 更新 demo 配置文件。"""
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import yaml
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def load_yaml(path: Path):
|
| 11 |
+
with open(path) as f:
|
| 12 |
+
return yaml.safe_load(f)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def save_yaml(path: Path, data):
|
| 16 |
+
with open(path, "w") as f:
|
| 17 |
+
yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def update_demo_config(demo_config_path: Path, generated_data_path: Path):
|
| 21 |
+
demo_cfg = load_yaml(demo_config_path)
|
| 22 |
+
gen_data = load_yaml(generated_data_path)
|
| 23 |
+
|
| 24 |
+
# 生成的 yaml 直接就是 data 部分的内容(没有顶层 data 键)
|
| 25 |
+
demo_cfg["data"] = gen_data
|
| 26 |
+
|
| 27 |
+
# 保持 train/val 路径使用 ONESCIENCE_DATASETS_DIR 变量(数据放在 UMA/data/ 下)
|
| 28 |
+
demo_cfg["data"]["train_dataset"]["splits"]["train"][
|
| 29 |
+
"src"
|
| 30 |
+
] = "${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/train"
|
| 31 |
+
demo_cfg["data"]["val_dataset"]["splits"]["val"][
|
| 32 |
+
"src"
|
| 33 |
+
] = "${ONESCIENCE_DATASETS_DIR}/data/oc20_finetune/val"
|
| 34 |
+
|
| 35 |
+
save_yaml(demo_config_path, demo_cfg)
|
| 36 |
+
print(f"[OK] 已更新 {demo_config_path}")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
parser = argparse.ArgumentParser()
|
| 41 |
+
parser.add_argument("--demo-config", type=Path, required=True)
|
| 42 |
+
parser.add_argument(
|
| 43 |
+
"--generated-data",
|
| 44 |
+
type=Path,
|
| 45 |
+
default=Path("data/oc20_finetune/data/uma_conserving_data_task_energy_force.yaml"),
|
| 46 |
+
)
|
| 47 |
+
args = parser.parse_args()
|
| 48 |
+
update_demo_config(args.demo_config, args.generated_data)
|
train.py
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
import argparse
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import random
|
| 6 |
+
import tempfile
|
| 7 |
+
import uuid
|
| 8 |
+
from dataclasses import dataclass, field
|
| 9 |
+
from enum import Enum
|
| 10 |
+
from typing import TYPE_CHECKING, Optional
|
| 11 |
+
|
| 12 |
+
# import clusterscope
|
| 13 |
+
try:
|
| 14 |
+
import clusterscope
|
| 15 |
+
except Exception:
|
| 16 |
+
clusterscope = None
|
| 17 |
+
import hydra
|
| 18 |
+
import numpy as np
|
| 19 |
+
import torch
|
| 20 |
+
from omegaconf import OmegaConf
|
| 21 |
+
from omegaconf.errors import InterpolationKeyError
|
| 22 |
+
|
| 23 |
+
from onescience.utils.uma.common import gp_utils
|
| 24 |
+
|
| 25 |
+
if TYPE_CHECKING:
|
| 26 |
+
from omegaconf import DictConfig
|
| 27 |
+
|
| 28 |
+
from onescience.utils.uma.components.reducer import Reducer
|
| 29 |
+
from onescience.utils.uma.components.runner import Runner
|
| 30 |
+
|
| 31 |
+
from submitit import AutoExecutor
|
| 32 |
+
from submitit.core.utils import JobPaths, cloudpickle_dump
|
| 33 |
+
from submitit.helpers import Checkpointable, DelayedSubmission
|
| 34 |
+
from submitit.slurm.slurm import SlurmJobEnvironment
|
| 35 |
+
|
| 36 |
+
from onescience.distributed.manager import DistributedManager
|
| 37 |
+
from onescience.utils.uma.common import distutils
|
| 38 |
+
from onescience.monitoring.uma.logger import WandBSingletonLogger
|
| 39 |
+
from onescience.monitoring.uma.runtime_logging import setup_logging
|
| 40 |
+
from onescience.utils.uma.common.utils import (
|
| 41 |
+
get_commit_hash,
|
| 42 |
+
get_timestamp_uid,
|
| 43 |
+
setup_env_vars,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
# this effects the cli only since the actual job will be run in subprocesses or remoe
|
| 47 |
+
logging.basicConfig(level=logging.INFO)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
ALLOWED_TOP_LEVEL_KEYS = {"job", "runner", "reducer"}
|
| 51 |
+
|
| 52 |
+
LOG_DIR_NAME = "logs"
|
| 53 |
+
CHECKPOINT_DIR_NAME = "checkpoints"
|
| 54 |
+
RESULTS_DIR = "results"
|
| 55 |
+
CONFIG_FILE_NAME = "canonical_config.yaml"
|
| 56 |
+
PREEMPTION_STATE_DIR_NAME = "preemption_state"
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class SchedulerType(str, Enum):
|
| 60 |
+
LOCAL = "local"
|
| 61 |
+
SLURM = "slurm"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class DeviceType(str, Enum):
|
| 65 |
+
CPU = "cpu"
|
| 66 |
+
CUDA = "cuda"
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class RunType(str, Enum):
|
| 70 |
+
RUN = "run"
|
| 71 |
+
REDUCE = "reduce"
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class DistributedInitMethod(str, Enum):
|
| 75 |
+
TCP = "tcp"
|
| 76 |
+
FILE = "file"
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@dataclass
|
| 80 |
+
class SlurmConfig:
|
| 81 |
+
mem_gb: int = 80
|
| 82 |
+
timeout_hr: int = 168
|
| 83 |
+
cpus_per_task: int = 8
|
| 84 |
+
partition: Optional[str] = (
|
| 85 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 86 |
+
)
|
| 87 |
+
qos: Optional[str] = None # omegaconf in python 3.9 does not backport annotations
|
| 88 |
+
account: Optional[str] = (
|
| 89 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 90 |
+
)
|
| 91 |
+
additional_parameters: Optional[dict] = None # 字典格式,用于存储环境变量和配置
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
@dataclass
|
| 95 |
+
class SchedulerConfig:
|
| 96 |
+
mode: SchedulerType = SchedulerType.LOCAL
|
| 97 |
+
distributed_init_method: DistributedInitMethod = DistributedInitMethod.TCP
|
| 98 |
+
ranks_per_node: int = 1
|
| 99 |
+
num_nodes: int = 1
|
| 100 |
+
num_array_jobs: int = 1
|
| 101 |
+
|
| 102 |
+
# 新增:仅 LOCAL+elastic 多节点时使用(也可不填,见下文)
|
| 103 |
+
rdzv_backend: str = "c10d"
|
| 104 |
+
rdzv_endpoint: Optional[str] = None
|
| 105 |
+
run_id: str = field(default_factory=lambda: f"run_{uuid.uuid4().hex[:8]}")
|
| 106 |
+
|
| 107 |
+
slurm: SlurmConfig = field(default_factory=lambda: SlurmConfig)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@dataclass
|
| 111 |
+
class SlurmEnv:
|
| 112 |
+
# reflects the job_id given by submitit (slurm id with array job id and array task id if they exist)
|
| 113 |
+
job_id: Optional[str] = (
|
| 114 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 115 |
+
)
|
| 116 |
+
# reflects SLURM_JOB_ID only
|
| 117 |
+
raw_job_id: Optional[str] = (
|
| 118 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 119 |
+
)
|
| 120 |
+
# SLURM_ARRAY_JOB_ID
|
| 121 |
+
array_job_id: Optional[str] = (
|
| 122 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 123 |
+
)
|
| 124 |
+
# SLURM_ARRAY_TASK_ID
|
| 125 |
+
array_task_id: Optional[str] = (
|
| 126 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 127 |
+
)
|
| 128 |
+
# reflects SLURM_RESTART_COUNT env variable
|
| 129 |
+
restart_count: Optional[str] = (
|
| 130 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@dataclass
|
| 135 |
+
class Metadata:
|
| 136 |
+
# read-only metadata about the job, not user inputs
|
| 137 |
+
commit: str
|
| 138 |
+
log_dir: str
|
| 139 |
+
checkpoint_dir: str
|
| 140 |
+
results_dir: str
|
| 141 |
+
config_path: str
|
| 142 |
+
preemption_checkpoint_dir: str
|
| 143 |
+
cluster_name: str
|
| 144 |
+
array_job_num: int = 0
|
| 145 |
+
slurm_env: SlurmEnv = field(default_factory=lambda: SlurmEnv())
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
@dataclass
|
| 149 |
+
class JobConfig:
|
| 150 |
+
run_name: str = field(
|
| 151 |
+
default_factory=lambda: get_timestamp_uid() + uuid.uuid4().hex.upper()[0:4]
|
| 152 |
+
)
|
| 153 |
+
timestamp_id: str = field(default_factory=lambda: get_timestamp_uid())
|
| 154 |
+
run_dir: str = field(default_factory=lambda: tempfile.TemporaryDirectory().name)
|
| 155 |
+
device_type: DeviceType = DeviceType.CUDA
|
| 156 |
+
debug: bool = False
|
| 157 |
+
scheduler: SchedulerConfig = field(default_factory=lambda: SchedulerConfig)
|
| 158 |
+
logger: Optional[dict] = (
|
| 159 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 160 |
+
)
|
| 161 |
+
seed: int = 0
|
| 162 |
+
deterministic: bool = False
|
| 163 |
+
runner_state_path: Optional[str] = (
|
| 164 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 165 |
+
)
|
| 166 |
+
# read-only metadata about the job, not user inputs
|
| 167 |
+
metadata: Optional[Metadata] = (
|
| 168 |
+
None # omegaconf in python 3.9 does not backport annotations
|
| 169 |
+
)
|
| 170 |
+
graph_parallel_group_size: Optional[int] = None
|
| 171 |
+
|
| 172 |
+
def __post_init__(self) -> None:
|
| 173 |
+
self.run_dir = os.path.abspath(self.run_dir)
|
| 174 |
+
try:
|
| 175 |
+
try:
|
| 176 |
+
cluster = clusterscope.cluster()
|
| 177 |
+
except Exception as e:
|
| 178 |
+
# DCU 或受限环境下,clusterscope 可能调用 nvidia-smi 失败
|
| 179 |
+
cluster = os.environ.get("ONESCIENCE_CLUSTER", "sghpc")
|
| 180 |
+
if os.environ.get("LOCAL_RANK", "0") == "0":
|
| 181 |
+
print(
|
| 182 |
+
f"[WARN] clusterscope.cluster() failed "
|
| 183 |
+
f"({type(e).__name__}: {e}); fallback to '{cluster}'"
|
| 184 |
+
)
|
| 185 |
+
except RuntimeError:
|
| 186 |
+
cluster = ""
|
| 187 |
+
self.metadata = Metadata(
|
| 188 |
+
commit=get_commit_hash(),
|
| 189 |
+
log_dir=os.path.join(self.run_dir, self.timestamp_id, LOG_DIR_NAME),
|
| 190 |
+
checkpoint_dir=os.path.join(
|
| 191 |
+
self.run_dir, self.timestamp_id, CHECKPOINT_DIR_NAME
|
| 192 |
+
),
|
| 193 |
+
results_dir=os.path.join(self.run_dir, self.timestamp_id, RESULTS_DIR),
|
| 194 |
+
config_path=os.path.join(self.run_dir, self.timestamp_id, CONFIG_FILE_NAME),
|
| 195 |
+
preemption_checkpoint_dir=os.path.join(
|
| 196 |
+
self.run_dir,
|
| 197 |
+
self.timestamp_id,
|
| 198 |
+
CHECKPOINT_DIR_NAME,
|
| 199 |
+
PREEMPTION_STATE_DIR_NAME,
|
| 200 |
+
),
|
| 201 |
+
cluster_name=cluster,
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def _set_seeds(seed: int) -> None:
|
| 206 |
+
random.seed(seed)
|
| 207 |
+
np.random.seed(seed)
|
| 208 |
+
torch.manual_seed(seed)
|
| 209 |
+
torch.cuda.manual_seed_all(seed)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def _set_deterministic_mode() -> None:
|
| 213 |
+
# this is required for full cuda deterministic mode
|
| 214 |
+
logging.info("Setting deterministic mode!")
|
| 215 |
+
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
|
| 216 |
+
torch.backends.cudnn.deterministic = True
|
| 217 |
+
torch.backends.cudnn.benchmark = False
|
| 218 |
+
torch.use_deterministic_algorithms(True)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def _get_slurm_env() -> SlurmEnv:
|
| 222 |
+
slurm_job_env = SlurmJobEnvironment()
|
| 223 |
+
try:
|
| 224 |
+
slurm_env = SlurmEnv(
|
| 225 |
+
job_id=slurm_job_env.job_id,
|
| 226 |
+
raw_job_id=slurm_job_env.raw_job_id,
|
| 227 |
+
array_job_id=slurm_job_env.array_job_id,
|
| 228 |
+
array_task_id=slurm_job_env.array_task_id,
|
| 229 |
+
restart_count=os.environ.get("SLURM_RESTART_COUNT"),
|
| 230 |
+
)
|
| 231 |
+
except KeyError:
|
| 232 |
+
# slurm environment variables are undefined, running locally
|
| 233 |
+
slurm_env = SlurmEnv()
|
| 234 |
+
|
| 235 |
+
return slurm_env
|
| 236 |
+
def remove_runner_state_from_submission(log_folder: str, job_id: str) -> None:
|
| 237 |
+
# (HACK) Decouple the job from the runner state by manually modifying it
|
| 238 |
+
# this ensures the saved runner state is not re-submitted in the event of a node failure
|
| 239 |
+
# ie: if the job was started at state t=T, a requeue during node failure would resubmit the job
|
| 240 |
+
# starting at state t=T again without calling the checkpoint callback, losing all progress in between.
|
| 241 |
+
job_path = JobPaths(folder=log_folder, job_id=job_id)
|
| 242 |
+
if os.path.isfile(job_path.submitted_pickle):
|
| 243 |
+
submission_obj = DelayedSubmission.load(job_path.submitted_pickle)
|
| 244 |
+
submission_obj.args[0].job.runner_state_path = None
|
| 245 |
+
cloudpickle_dump(submission_obj, job_path.submitted_pickle)
|
| 246 |
+
class Submitit(Checkpointable):
|
| 247 |
+
def __init__(self) -> None:
|
| 248 |
+
self.config = None
|
| 249 |
+
self.runner = None
|
| 250 |
+
self.reducer = None
|
| 251 |
+
|
| 252 |
+
def __call__(
|
| 253 |
+
self, dict_config: DictConfig, run_type: RunType = RunType.RUN
|
| 254 |
+
) -> None:
|
| 255 |
+
self.config = dict_config
|
| 256 |
+
self.run_type = run_type
|
| 257 |
+
# modify the config metadata to add slurm info if they exist
|
| 258 |
+
self.config.job.metadata.slurm_env = _get_slurm_env()
|
| 259 |
+
|
| 260 |
+
setup_env_vars()
|
| 261 |
+
setup_logging()
|
| 262 |
+
dist_config = map_job_config_to_dist_config(self.config.job)
|
| 263 |
+
logging.info("Setting up distributed backend...")
|
| 264 |
+
distutils.setup(dist_config)
|
| 265 |
+
distutils.synchronize()
|
| 266 |
+
if (
|
| 267 |
+
distutils.is_master()
|
| 268 |
+
and self.config.job.scheduler.mode == SchedulerType.SLURM
|
| 269 |
+
):
|
| 270 |
+
# this pickle file is shared across all processes so can only modify this on the main rank
|
| 271 |
+
remove_runner_state_from_submission(
|
| 272 |
+
dict_config.job.metadata.log_dir,
|
| 273 |
+
self.config.job.metadata.slurm_env.job_id,
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
if self.config.job.graph_parallel_group_size is not None:
|
| 277 |
+
logging.info("Setting up graph parallel...")
|
| 278 |
+
gp_utils.setup_graph_parallel_groups(
|
| 279 |
+
self.config.job.graph_parallel_group_size,
|
| 280 |
+
dist_config["distributed_backend"],
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
self._init_logger()
|
| 284 |
+
print(f"rank:{distutils.get_rank()}")
|
| 285 |
+
|
| 286 |
+
_set_seeds(self.config.job.seed)
|
| 287 |
+
if self.config.job.deterministic:
|
| 288 |
+
_set_deterministic_mode()
|
| 289 |
+
|
| 290 |
+
if run_type == RunType.RUN:
|
| 291 |
+
logging.info("Calling runner.run() ...")
|
| 292 |
+
self.runner: Runner = hydra.utils.instantiate(self.config.runner)
|
| 293 |
+
self.runner.job_config = self.config.job
|
| 294 |
+
# must call resume state AFTER the runner has been initialized
|
| 295 |
+
self.runner.load_state(self.config.job.runner_state_path)
|
| 296 |
+
self.runner.run()
|
| 297 |
+
elif run_type == RunType.REDUCE:
|
| 298 |
+
logging.info("Calling reducer.reduce() ...")
|
| 299 |
+
self.reducer: Reducer = hydra.utils.instantiate(self.config.reducer)
|
| 300 |
+
self.reducer.job_config = self.config.job
|
| 301 |
+
self.reducer.runner_config = self.config.runner
|
| 302 |
+
# must call resume state AFTER the runner has been initialized
|
| 303 |
+
self.reducer.load_state(self.config.job.runner_state_path)
|
| 304 |
+
self.reducer.reduce()
|
| 305 |
+
else:
|
| 306 |
+
raise ValueError(f"run type {run_type} is not recognized!")
|
| 307 |
+
|
| 308 |
+
distutils.cleanup()
|
| 309 |
+
|
| 310 |
+
def _init_logger(self) -> None:
|
| 311 |
+
if (
|
| 312 |
+
self.config.job.logger
|
| 313 |
+
and distutils.is_master()
|
| 314 |
+
and not self.config.job.debug
|
| 315 |
+
and self.config.job.metadata.array_job_num == 0
|
| 316 |
+
):
|
| 317 |
+
# get a partial function from the config and instantiate wandb with it
|
| 318 |
+
# currently code assumes that we only use the WandBSingletonLogger
|
| 319 |
+
logger_initializer = hydra.utils.instantiate(self.config.job.logger)
|
| 320 |
+
simple_config = OmegaConf.to_container(
|
| 321 |
+
self.config, resolve=True, throw_on_missing=True
|
| 322 |
+
)
|
| 323 |
+
logger_initializer(
|
| 324 |
+
config=simple_config,
|
| 325 |
+
run_id=self.config.job.timestamp_id,
|
| 326 |
+
run_name=self.config.job.run_name,
|
| 327 |
+
log_dir=self.config.job.metadata.log_dir,
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
def checkpoint(self, *args, **kwargs) -> DelayedSubmission:
|
| 331 |
+
logging.error("Submitit checkpointing callback is triggered")
|
| 332 |
+
save_path = self.config.job.metadata.preemption_checkpoint_dir
|
| 333 |
+
cfg_copy = self.config.copy()
|
| 334 |
+
# only assign if the save was successful
|
| 335 |
+
cfg_copy.job.runner_state_path = None
|
| 336 |
+
|
| 337 |
+
if (
|
| 338 |
+
self.run_type == RunType.RUN
|
| 339 |
+
and self.runner.save_state(save_path, is_preemption=True)
|
| 340 |
+
) or (
|
| 341 |
+
self.run_type == RunType.REDUCE
|
| 342 |
+
and self.reducer.save_state(save_path, is_preemption=True)
|
| 343 |
+
):
|
| 344 |
+
cfg_copy.job.runner_state_path = save_path
|
| 345 |
+
|
| 346 |
+
if WandBSingletonLogger.initialized():
|
| 347 |
+
WandBSingletonLogger.get_instance().mark_preempting()
|
| 348 |
+
logging.info(
|
| 349 |
+
f"Submitit checkpointing callback is completed, resuming with use the following state: {save_path}"
|
| 350 |
+
)
|
| 351 |
+
return DelayedSubmission(Submitit(), cfg_copy)
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def map_job_config_to_dist_config(job_cfg: JobConfig) -> dict:
|
| 355 |
+
scheduler_config = job_cfg.scheduler
|
| 356 |
+
return {
|
| 357 |
+
"world_size": scheduler_config.num_nodes * scheduler_config.ranks_per_node,
|
| 358 |
+
"distributed_backend": (
|
| 359 |
+
"gloo" if job_cfg.device_type == DeviceType.CPU else "nccl"
|
| 360 |
+
),
|
| 361 |
+
"submit": scheduler_config.mode == SchedulerType.SLURM,
|
| 362 |
+
"cpu": job_cfg.device_type == DeviceType.CPU,
|
| 363 |
+
"init_method": scheduler_config.distributed_init_method,
|
| 364 |
+
# for distributed shared file initialization
|
| 365 |
+
"shared_file_dir": os.path.join(job_cfg.run_dir, job_cfg.timestamp_id),
|
| 366 |
+
"array_job_num": job_cfg.metadata.array_job_num,
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
def get_canonical_config(config: DictConfig) -> DictConfig:
|
| 371 |
+
# manually initialize metadata, because OmegaConf currently doesn't call __post_init__ on dataclasses
|
| 372 |
+
job = OmegaConf.to_object(config.job)
|
| 373 |
+
job.__post_init__()
|
| 374 |
+
config.job = job
|
| 375 |
+
# check that each key other than the allowed top level keys are used in config
|
| 376 |
+
# find all top level keys are not in the allowed set
|
| 377 |
+
all_keys = set(config.keys()).difference(ALLOWED_TOP_LEVEL_KEYS)
|
| 378 |
+
used_keys = set()
|
| 379 |
+
for key in all_keys:
|
| 380 |
+
# make a copy of all keys except the key in question
|
| 381 |
+
copy_cfg = OmegaConf.create({k: v for k, v in config.items() if k != key})
|
| 382 |
+
try:
|
| 383 |
+
OmegaConf.resolve(copy_cfg)
|
| 384 |
+
except InterpolationKeyError:
|
| 385 |
+
# if this error is thrown, this means the key was actually required
|
| 386 |
+
used_keys.add(key)
|
| 387 |
+
|
| 388 |
+
unused_keys = all_keys.difference(used_keys)
|
| 389 |
+
if unused_keys != set():
|
| 390 |
+
raise ValueError(
|
| 391 |
+
f"Found unused keys in the config: {unused_keys}, please remove them!, only keys other than {ALLOWED_TOP_LEVEL_KEYS} or ones that are used as variables are allowed."
|
| 392 |
+
)
|
| 393 |
+
|
| 394 |
+
# resolve the config to fully replace the variables and delete all top level keys except for the ALLOWED_TOP_LEVEL_KEYS
|
| 395 |
+
for _k in ("ONESCIENCE_DATASETS_DIR", "ONESCIENCE_MODELS_DIR"):
|
| 396 |
+
if OmegaConf.select(config, _k, default=None) is None:
|
| 397 |
+
_v = os.environ.get(_k)
|
| 398 |
+
if _v:
|
| 399 |
+
OmegaConf.update(config, _k, _v, force_add=True)
|
| 400 |
+
OmegaConf.resolve(config)
|
| 401 |
+
return OmegaConf.create(
|
| 402 |
+
{k: v for k, v in config.items() if k in ALLOWED_TOP_LEVEL_KEYS}
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
def get_hydra_config_from_yaml(
|
| 407 |
+
config_yml: str, overrides_args: list[str]
|
| 408 |
+
) -> DictConfig:
|
| 409 |
+
# Load the configuration from the file
|
| 410 |
+
os.environ["HYDRA_FULL_ERROR"] = "1"
|
| 411 |
+
config_directory = os.path.dirname(os.path.abspath(config_yml))
|
| 412 |
+
config_name = os.path.basename(config_yml)
|
| 413 |
+
hydra.initialize_config_dir(config_directory, version_base="1.1")
|
| 414 |
+
cfg = hydra.compose(config_name=config_name, overrides=overrides_args)
|
| 415 |
+
# merge default structured config with initialized job object
|
| 416 |
+
cfg = OmegaConf.merge({"job": OmegaConf.structured(JobConfig)}, cfg)
|
| 417 |
+
# canonicalize config (remove top level keys that just used replacing variables)
|
| 418 |
+
return get_canonical_config(cfg)
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def _runner_wrapper(config: DictConfig, run_type: RunType = RunType.RUN):
|
| 422 |
+
# This is needed when using elastic_launch for local runs since it looks for
|
| 423 |
+
# the __name__ attribute of the function, Submitit.__call__ does not have one
|
| 424 |
+
Submitit()(config, run_type)
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def _run_under_torchrun(cfg: DictConfig) -> None:
|
| 428 |
+
"""在 torchrun / srun+torchrun 启动的 worker 进程里运行训练。
|
| 429 |
+
|
| 430 |
+
调用方已经是 rank N 之一(LOCAL_RANK 已由 torchrun 注入),因此这里
|
| 431 |
+
绝对不能再走 submitit 或 elastic_launch,而是直接:
|
| 432 |
+
1) setup_env_vars / setup_logging
|
| 433 |
+
2) DistributedManager.initialize() (读取 RANK/LOCAL_RANK/WORLD_SIZE 等 env 变量)
|
| 434 |
+
3) gp_utils (可选)
|
| 435 |
+
4) _set_seeds + deterministic
|
| 436 |
+
5) WandB 日志 (rank0)
|
| 437 |
+
6) hydra.utils.instantiate(cfg.runner) + runner.run()
|
| 438 |
+
7) DistributedManager.cleanup()
|
| 439 |
+
"""
|
| 440 |
+
setup_env_vars()
|
| 441 |
+
setup_logging()
|
| 442 |
+
|
| 443 |
+
world_size = int(os.environ.get("WORLD_SIZE", "1"))
|
| 444 |
+
logging.info(
|
| 445 |
+
f"Init distributed from ENV: RANK={os.environ.get('RANK')}, "
|
| 446 |
+
f"LOCAL_RANK={os.environ.get('LOCAL_RANK')}, WORLD_SIZE={world_size}, "
|
| 447 |
+
f"MASTER={os.environ.get('MASTER_ADDR')}:{os.environ.get('MASTER_PORT')}"
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
DistributedManager.initialize()
|
| 451 |
+
|
| 452 |
+
if cfg.job.graph_parallel_group_size is not None:
|
| 453 |
+
backend = "gloo" if cfg.job.device_type == DeviceType.CPU else "nccl"
|
| 454 |
+
gp_utils.setup_graph_parallel_groups(
|
| 455 |
+
cfg.job.graph_parallel_group_size, backend
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
_set_seeds(cfg.job.seed)
|
| 459 |
+
if cfg.job.deterministic:
|
| 460 |
+
_set_deterministic_mode()
|
| 461 |
+
|
| 462 |
+
dm = DistributedManager()
|
| 463 |
+
if cfg.job.logger and dm.rank == 0 and not cfg.job.debug:
|
| 464 |
+
logger_initializer = hydra.utils.instantiate(cfg.job.logger)
|
| 465 |
+
simple_config = OmegaConf.to_container(
|
| 466 |
+
cfg, resolve=True, throw_on_missing=True
|
| 467 |
+
)
|
| 468 |
+
logger_initializer(
|
| 469 |
+
config=simple_config,
|
| 470 |
+
run_id=cfg.job.timestamp_id,
|
| 471 |
+
run_name=cfg.job.run_name,
|
| 472 |
+
log_dir=cfg.job.metadata.log_dir,
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
runner = hydra.utils.instantiate(cfg.runner)
|
| 476 |
+
runner.job_config = cfg.job
|
| 477 |
+
runner.load_state(cfg.job.runner_state_path)
|
| 478 |
+
runner.run()
|
| 479 |
+
|
| 480 |
+
DistributedManager.cleanup()
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def main(
|
| 484 |
+
args: argparse.Namespace | None = None, override_args: list[str] | None = None
|
| 485 |
+
):
|
| 486 |
+
if args is None:
|
| 487 |
+
parser = argparse.ArgumentParser()
|
| 488 |
+
parser.add_argument("-c", "--config", type=str, required=True)
|
| 489 |
+
args, override_args = parser.parse_known_args()
|
| 490 |
+
|
| 491 |
+
cfg = get_hydra_config_from_yaml(args.config, override_args)
|
| 492 |
+
|
| 493 |
+
log_dir = cfg.job.metadata.log_dir
|
| 494 |
+
os.makedirs(cfg.job.run_dir, exist_ok=True)
|
| 495 |
+
os.makedirs(log_dir, exist_ok=True)
|
| 496 |
+
|
| 497 |
+
OmegaConf.save(cfg, cfg.job.metadata.config_path)
|
| 498 |
+
logging.info(f"saved canonical config to {cfg.job.metadata.config_path}")
|
| 499 |
+
|
| 500 |
+
scheduler_cfg = cfg.job.scheduler
|
| 501 |
+
|
| 502 |
+
# ------------------------------------------------------------------
|
| 503 |
+
# torchrun / srun+torchrun worker 分支
|
| 504 |
+
# LOCAL_RANK 由 torchrun 注入, 说明我们已经在 rank N 的子进程中,
|
| 505 |
+
# 不应再走 submitit / elastic_launch (否则会递归 spawn N*N 个进程)。
|
| 506 |
+
# demo/run.sh 会把 LOCAL 调度与 torchrun 启动器组合使用, 进入这里。
|
| 507 |
+
# ------------------------------------------------------------------
|
| 508 |
+
if (
|
| 509 |
+
os.environ.get("LOCAL_RANK") is not None
|
| 510 |
+
and scheduler_cfg.mode == SchedulerType.LOCAL
|
| 511 |
+
):
|
| 512 |
+
_run_under_torchrun(cfg)
|
| 513 |
+
return
|
| 514 |
+
|
| 515 |
+
logging.info(f"Running fairchemv2 cli with {cfg}")
|
| 516 |
+
if scheduler_cfg.mode == SchedulerType.SLURM: # Run on cluster
|
| 517 |
+
assert (
|
| 518 |
+
os.getenv("SLURM_SUBMIT_HOST") is None
|
| 519 |
+
), "SLURM DID NOT SUBMIT JOB!! Please do not submit jobs from an active slurm job (srun or otherwise)"
|
| 520 |
+
executor = AutoExecutor(folder=log_dir, slurm_max_num_timeout=3)
|
| 521 |
+
executor.update_parameters(
|
| 522 |
+
name=cfg.job.run_name,
|
| 523 |
+
mem_gb=scheduler_cfg.slurm.mem_gb,
|
| 524 |
+
timeout_min=scheduler_cfg.slurm.timeout_hr * 60,
|
| 525 |
+
slurm_partition=scheduler_cfg.slurm.partition,
|
| 526 |
+
# gpus_per_node=scheduler_cfg.ranks_per_node,
|
| 527 |
+
gpus_per_node=None, # 不设置 gpus-per-node
|
| 528 |
+
cpus_per_task=scheduler_cfg.slurm.cpus_per_task,
|
| 529 |
+
tasks_per_node=scheduler_cfg.ranks_per_node,
|
| 530 |
+
nodes=scheduler_cfg.num_nodes,
|
| 531 |
+
slurm_qos=scheduler_cfg.slurm.qos,
|
| 532 |
+
slurm_account=scheduler_cfg.slurm.account,
|
| 533 |
+
# 新增:把 YAML 里的 additional_parameters 传给 sbatch
|
| 534 |
+
slurm_additional_parameters=getattr(
|
| 535 |
+
scheduler_cfg.slurm, "additional_parameters", None
|
| 536 |
+
),
|
| 537 |
+
)
|
| 538 |
+
if scheduler_cfg.num_array_jobs == 1:
|
| 539 |
+
job = executor.submit(Submitit(), cfg)
|
| 540 |
+
logging.info(
|
| 541 |
+
f"Submitted job id: {cfg.job.timestamp_id}, slurm id: {job.job_id}, logs: {cfg.job.metadata.log_dir}"
|
| 542 |
+
)
|
| 543 |
+
jobs = [job]
|
| 544 |
+
elif scheduler_cfg.num_array_jobs > 1:
|
| 545 |
+
executor.update_parameters(
|
| 546 |
+
slurm_array_parallelism=scheduler_cfg.num_array_jobs,
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
jobs = []
|
| 550 |
+
with executor.batch():
|
| 551 |
+
for job_number in range(scheduler_cfg.num_array_jobs):
|
| 552 |
+
_cfg = cfg.copy()
|
| 553 |
+
_cfg.job.metadata.array_job_num = job_number
|
| 554 |
+
job = executor.submit(Submitit(), _cfg)
|
| 555 |
+
jobs.append(job)
|
| 556 |
+
logging.info(f"Submitted {len(jobs)} jobs: {jobs[0].job_id.split('_')[0]}")
|
| 557 |
+
|
| 558 |
+
if "reducer" in cfg:
|
| 559 |
+
job_id = jobs[0].job_id.split("_")[0]
|
| 560 |
+
executor.update_parameters(
|
| 561 |
+
name=f"{cfg.job.run_name}_reduce",
|
| 562 |
+
# set a single node, or do we want the same config as the Runner or a separate JobConfig
|
| 563 |
+
nodes=1,
|
| 564 |
+
slurm_dependency=f"afterok:{job_id}",
|
| 565 |
+
slurm_additional_parameters={
|
| 566 |
+
"kill-on-invalid-dep": "yes"
|
| 567 |
+
}, # kill the reducer if run fails
|
| 568 |
+
)
|
| 569 |
+
executor.submit(Submitit(), cfg, RunType.REDUCE)
|
| 570 |
+
else:
|
| 571 |
+
from torch.distributed.launcher.api import LaunchConfig, elastic_launch
|
| 572 |
+
|
| 573 |
+
if scheduler_cfg.num_nodes > 1:
|
| 574 |
+
cfg.job.scheduler.num_nodes = 1
|
| 575 |
+
logging.warning(
|
| 576 |
+
f"You cannot use more than one node (scheduler_cfg.num_nodes={scheduler_cfg.num_nodes}) in LOCAL mode, over-riding to 1 node"
|
| 577 |
+
)
|
| 578 |
+
if scheduler_cfg.ranks_per_node > 1:
|
| 579 |
+
logging.info(
|
| 580 |
+
f"Running in local mode with {scheduler_cfg.ranks_per_node} ranks using device_type:{cfg.job.device_type}"
|
| 581 |
+
)
|
| 582 |
+
launch_config = LaunchConfig(
|
| 583 |
+
min_nodes=1,
|
| 584 |
+
max_nodes=1,
|
| 585 |
+
nproc_per_node=scheduler_cfg.ranks_per_node,
|
| 586 |
+
rdzv_backend="c10d",
|
| 587 |
+
max_restarts=0,
|
| 588 |
+
)
|
| 589 |
+
elastic_launch(launch_config, _runner_wrapper)(cfg)
|
| 590 |
+
if "reducer" in cfg:
|
| 591 |
+
elastic_launch(launch_config, _runner_wrapper)(cfg, RunType.REDUCE)
|
| 592 |
+
else:
|
| 593 |
+
logging.info("Running in local mode without elastic launch")
|
| 594 |
+
distutils.setup_env_local()
|
| 595 |
+
Submitit()(cfg)
|
| 596 |
+
if "reducer" in cfg:
|
| 597 |
+
Submitit()(cfg, RunType.REDUCE)
|
| 598 |
+
|
| 599 |
+
if __name__ == "__main__":
|
| 600 |
+
main()
|
weight/Jd.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b4059c45be246dcb6c49c545670b65c56550eb0c2e7a9c92b4b50a92d370dbe2
|
| 3 |
+
size 21697
|