OneScience commited on
Commit
bf314e8
·
verified ·
1 Parent(s): 0beda69

Upload folder using huggingface_hub

Browse files
Files changed (41) hide show
  1. README.md +169 -0
  2. configs/data/uma_conserving_data_task_energy.yaml +61 -0
  3. configs/data/uma_conserving_data_task_energy_force.yaml +86 -0
  4. configs/data/uma_conserving_data_task_energy_force_stress.yaml +107 -0
  5. configs/uma_sm_finetune_template.yaml +115 -0
  6. demo/_parse_config.py +253 -0
  7. demo/configs/_oc20_ef_4dcu_filled.yaml +303 -0
  8. demo/configs/oc20_ef_16dcu.yaml +270 -0
  9. demo/configs/oc20_ef_4dcu.yaml +303 -0
  10. demo/configs/oc20_ef_8dcu.yaml +263 -0
  11. demo/outputs/oc20_ef_4dcu_20260721_114458/config.yaml +303 -0
  12. demo/outputs/oc20_ef_4dcu_20260721_114458/hydra_config.yaml +288 -0
  13. demo/outputs/oc20_ef_4dcu_20260721_114458/models/pretrained_models.json +56 -0
  14. demo/run.sh +300 -0
  15. demo/templates/env_setup.sh +43 -0
  16. demo/templates/preflight_check.sh +53 -0
  17. demo/templates/slurm_header.template +9 -0
  18. inference/batch_inference_with_dataloader.py +50 -0
  19. inference/calculate_spin_gap.py +65 -0
  20. inference/generate_batches_at_runtime.py +49 -0
  21. inference/inference_heterogeneous_batches.py +50 -0
  22. inference/models/pretrained_models.json +56 -0
  23. inference/relax_adsorbate_on_slab.py +67 -0
  24. inference/relax_inorganic_crystal.py +58 -0
  25. inference/run_molecular_md.py +76 -0
  26. model/__init__.py +30 -0
  27. model/base.py +222 -0
  28. model/models/__init__.py +1 -0
  29. model/models/base.py +1 -0
  30. model/models/uma/__init__.py +1 -0
  31. model/models/uma/escn_md.py +1 -0
  32. model/models/uma/escn_moe.py +1 -0
  33. model/uma_escn_md.py +621 -0
  34. model/uma_escn_moe.py +184 -0
  35. models-json/pretrained_models.json +56 -0
  36. scripts/convert_model.py +151 -0
  37. scripts/create_finetune_dataset.py +186 -0
  38. scripts/create_uma_finetune_dataset.py +140 -0
  39. scripts/update_demo_config.py +48 -0
  40. train.py +600 -0
  41. 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