uma-finetune-wrapper 0.1.0.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ '''Simple UMA dataset preparation and fine-tuning tools.'''
2
+
3
+ from .dataset import create_dataset
4
+ from .training import finetune
5
+
6
+ __all__ = ['create_dataset', 'finetune']
@@ -0,0 +1,62 @@
1
+ '''Prepare datasets with FAIRChem's existing UMA conversion script.'''
2
+
3
+ import subprocess
4
+ import sys
5
+ from importlib.resources import as_file, files
6
+ from pathlib import Path
7
+
8
+
9
+ def create_dataset(
10
+ train_path,
11
+ val_path,
12
+ output_dir,
13
+ *,
14
+ task='oc25',
15
+ targets='ef',
16
+ base_model=None,
17
+ num_workers=1,
18
+ ):
19
+ '''Convert directories of labelled ASE-readable files and return their output path.
20
+
21
+ targets: 'e' (energies), 'ef' (energies and forces), or 'efs' (also stress).
22
+ base_model=None uses FAIRChem's default for the generated configuration.
23
+ output_dir must not already exist. Templates are included in the package.
24
+ Conversion failures raise subprocess.CalledProcessError; logs remain visible.
25
+ '''
26
+ train_path = Path(train_path).expanduser().resolve()
27
+ val_path = Path(val_path).expanduser().resolve()
28
+ output_dir = Path(output_dir).expanduser().resolve()
29
+
30
+ for path in (train_path, val_path):
31
+ if not path.is_dir():
32
+ raise NotADirectoryError(f'Input directory does not exist: {path}')
33
+
34
+ if output_dir.exists():
35
+ raise FileExistsError(f'Output directory already exists: {output_dir}')
36
+ if task not in ('omol', 'omat', 'odac', 'oc20', 'oc25', 'omc'):
37
+ raise ValueError('task must be omol, omat, odac, oc20, oc25, or omc.')
38
+ if targets not in ('e', 'ef', 'efs'):
39
+ raise ValueError('targets must be e, ef, or efs.')
40
+ if type(num_workers) is not int or num_workers < 1:
41
+ raise ValueError('num_workers must be a positive integer.')
42
+
43
+ command = [
44
+ sys.executable,
45
+ '-m', 'fairchem.core.scripts.create_uma_finetune_dataset',
46
+ '--train-dir', str(train_path),
47
+ '--val-dir', str(val_path),
48
+ '--output-dir', str(output_dir),
49
+ '--uma-task', task,
50
+ '--regression-tasks', targets,
51
+ '--num-workers', str(num_workers),
52
+ ]
53
+
54
+ if base_model is not None:
55
+ command.extend(['--base-model', base_model])
56
+
57
+ # FAIRChem reads configs/uma/finetune relative to its working directory.
58
+ # Input and output paths are absolute, so only template lookup changes.
59
+ resources = files('uma_finetune').joinpath('resources')
60
+ with as_file(resources) as resource_root:
61
+ subprocess.run(command, check=True, cwd=resource_root)
62
+ return output_dir
@@ -0,0 +1,23 @@
1
+ # License
2
+
3
+ MIT License
4
+
5
+ Copyright (c) Meta, Inc. and its affiliates.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ # FAIRChem templates
2
+
3
+ The four YAML files under configs/uma/finetune are unmodified copies from
4
+ FAIRChem 2.22.0, supplied from the working source checkout for this project.
5
+ Upstream project: https://github.com/facebookresearch/fairchem
6
+
7
+ Copyright (c) Meta, Inc. and its affiliates.
8
+ These templates are distributed under the MIT license reproduced in
9
+ FAIRChem-LICENSE.md. This notice does not assign a license to the wrapper itself.
10
+
11
+ The conversion script is supplied by the installed fairchem-core dependency;
12
+ it is not copied into this package. Keep these resources matched to the pinned
13
+ FAIRChem version when upgrading.
@@ -0,0 +1,89 @@
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: fairchem.core.models.uma.escn_md.MLP_EFS_Head
33
+
34
+ tasks_list:
35
+ - _target_: fairchem.core.units.mlip_unit.mlip_unit.Task
36
+ name: energy
37
+ level: system
38
+ property: energy
39
+ loss_fn:
40
+ _target_: fairchem.core.modules.loss.DDPMTLoss
41
+ loss_fn:
42
+ _target_: fairchem.core.modules.loss.PerAtomMAELoss
43
+ coefficient: 20
44
+ out_spec:
45
+ dim: [1]
46
+ dtype: float32
47
+ normalizer:
48
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
49
+ mean: 0.0
50
+ rmsd: ${data.normalizer_rmsd}
51
+ element_references:
52
+ _target_: fairchem.core.modules.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_: fairchem.core.units.mlip_unit.mlip_unit.Task
63
+ name: forces
64
+ level: atom
65
+ property: forces
66
+ out_spec:
67
+ dim: [3]
68
+ dtype: float32
69
+ normalizer:
70
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
71
+ mean: 0.0
72
+ rmsd: ${data.normalizer_rmsd}
73
+ datasets:
74
+ - ${data.dataset_name}
75
+ inference_only: True
76
+ - _target_: fairchem.core.units.mlip_unit.mlip_unit.Task
77
+ name: stress
78
+ level: system
79
+ property: stress
80
+ out_spec:
81
+ dim: [1, 9]
82
+ dtype: float32
83
+ normalizer:
84
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
85
+ mean: 0.0
86
+ rmsd: ${data.normalizer_rmsd}
87
+ datasets:
88
+ - ${data.dataset_name}
89
+ inference_only: True
@@ -0,0 +1,100 @@
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: fairchem.core.models.uma.escn_md.MLP_EFS_Head
33
+
34
+ tasks_list:
35
+ - _target_: fairchem.core.units.mlip_unit.mlip_unit.Task
36
+ name: energy
37
+ level: system
38
+ property: energy
39
+ loss_fn:
40
+ _target_: fairchem.core.modules.loss.DDPMTLoss
41
+ loss_fn:
42
+ _target_: fairchem.core.modules.loss.PerAtomMAELoss
43
+ coefficient: 20
44
+ out_spec:
45
+ dim: [1]
46
+ dtype: float32
47
+ normalizer:
48
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
49
+ mean: 0.0
50
+ rmsd: ${data.normalizer_rmsd}
51
+ element_references:
52
+ _target_: fairchem.core.modules.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_: fairchem.core.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_: fairchem.core.modules.loss.DDPMTLoss
70
+ loss_fn:
71
+ _target_: fairchem.core.modules.loss.L2NormLoss
72
+ reduction: mean
73
+ coefficient: 2
74
+ out_spec:
75
+ dim: [3]
76
+ dtype: float32
77
+ normalizer:
78
+ _target_: fairchem.core.modules.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_: fairchem.core.units.mlip_unit.mlip_unit.Task
88
+ name: stress
89
+ level: system
90
+ property: stress
91
+ out_spec:
92
+ dim: [1, 9]
93
+ dtype: float32
94
+ normalizer:
95
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
96
+ mean: 0.0
97
+ rmsd: ${data.normalizer_rmsd}
98
+ datasets:
99
+ - ${data.dataset_name}
100
+ inference_only: True
@@ -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: fairchem.core.models.uma.escn_md.MLP_EFS_Head
33
+
34
+ tasks_list:
35
+ - _target_: fairchem.core.units.mlip_unit.mlip_unit.Task
36
+ name: energy
37
+ level: system
38
+ property: energy
39
+ loss_fn:
40
+ _target_: fairchem.core.modules.loss.DDPMTLoss
41
+ loss_fn:
42
+ _target_: fairchem.core.modules.loss.PerAtomMAELoss
43
+ coefficient: 20
44
+ out_spec:
45
+ dim: [1]
46
+ dtype: float32
47
+ normalizer:
48
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
49
+ mean: 0.0
50
+ rmsd: ${data.normalizer_rmsd}
51
+ element_references:
52
+ _target_: fairchem.core.modules.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_: fairchem.core.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_: fairchem.core.modules.loss.DDPMTLoss
70
+ loss_fn:
71
+ _target_: fairchem.core.modules.loss.L2NormLoss
72
+ reduction: mean
73
+ coefficient: 2
74
+ out_spec:
75
+ dim: [3]
76
+ dtype: float32
77
+ normalizer:
78
+ _target_: fairchem.core.modules.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_: fairchem.core.units.mlip_unit.mlip_unit.Task
88
+ name: stress
89
+ level: system
90
+ property: stress
91
+ loss_fn:
92
+ _target_: fairchem.core.modules.loss.DDPMTLoss
93
+ loss_fn:
94
+ _target_: fairchem.core.modules.loss.MAELoss
95
+ reduction: mean
96
+ coefficient: 1
97
+ out_spec:
98
+ dim: [1, 9]
99
+ dtype: float32
100
+ normalizer:
101
+ _target_: fairchem.core.modules.normalization.normalizer.Normalizer
102
+ mean: 0.0
103
+ rmsd: ${data.normalizer_rmsd}
104
+ datasets:
105
+ - ${data.dataset_name}
106
+ metrics:
107
+ - mae
@@ -0,0 +1,118 @@
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: /tmp/uma_finetune_runs/
13
+ run_name: uma_finetune
14
+ logger:
15
+ _target_: fairchem.core.common.logger.WandBSingletonLogger.init_wandb
16
+ _partial_: true
17
+ entity: example
18
+ project: uma_finetune
19
+
20
+ # refers to a model name that can be retrieved from (huggingface)[https://huggingface.co/facebook/UMA]
21
+ base_model_name: uma-s-1p1
22
+ # 300 is effectively infinite neighbors, but if you are memory strained, using 100 is usually just as good
23
+ max_neighbors: 300
24
+ # choose to either run for integer number of epochs or steps, only 1 can be step, the other must be null
25
+ epochs: 1
26
+ steps: null
27
+ # customize batch size depending on your system
28
+ batch_size: 2
29
+ lr: 4e-4
30
+ weight_decay: 1e-3
31
+ evaluate_every_n_steps: 100
32
+ checkpoint_every_n_steps: 1000
33
+
34
+
35
+ train_dataset:
36
+ _target_: fairchem.core.datasets.mt_concat_dataset.create_concat_dataset
37
+ dataset_configs:
38
+ DATASET_NAME: ${data.train_dataset}
39
+ combined_dataset_config: { sampling: {type: temperature, temperature: 1.0} }
40
+
41
+ train_dataloader:
42
+ _target_: fairchem.core.components.common.dataloader_builder.get_dataloader
43
+ dataset: ${train_dataset}
44
+ batch_sampler_fn:
45
+ _target_: fairchem.core.common.data_parallel.BalancedBatchSampler
46
+ _partial_: True
47
+ batch_size: ${batch_size}
48
+ shuffle: True
49
+ seed: 0
50
+ num_workers: 0
51
+ collate_fn:
52
+ _target_: fairchem.core.units.mlip_unit.mlip_unit.mt_collater_adapter
53
+ tasks: ${data.tasks_list}
54
+
55
+ val_dataset:
56
+ _target_: fairchem.core.datasets.mt_concat_dataset.create_concat_dataset
57
+ dataset_configs:
58
+ DATASET_NAME: ${data.val_dataset}
59
+ combined_dataset_config: { sampling: {type: temperature, temperature: 1.0} }
60
+
61
+ eval_dataloader:
62
+ _target_: fairchem.core.components.common.dataloader_builder.get_dataloader
63
+ dataset: ${val_dataset}
64
+ batch_sampler_fn:
65
+ _target_: fairchem.core.common.data_parallel.BalancedBatchSampler
66
+ _partial_: True
67
+ batch_size: ${batch_size}
68
+ shuffle: False
69
+ seed: 0
70
+ num_workers: 0
71
+ collate_fn:
72
+ _target_: fairchem.core.units.mlip_unit.mlip_unit.mt_collater_adapter
73
+ tasks: ${data.tasks_list}
74
+
75
+ runner:
76
+ _target_: fairchem.core.components.train.train_runner.TrainEvalRunner
77
+ train_dataloader: ${train_dataloader}
78
+ eval_dataloader: ${eval_dataloader}
79
+ train_eval_unit:
80
+ _target_: fairchem.core.units.mlip_unit.mlip_unit.MLIPTrainEvalUnit
81
+ job_config: ${job}
82
+ tasks: ${data.tasks_list}
83
+ model:
84
+ _target_: fairchem.core.units.mlip_unit.mlip_unit.initialize_finetuning_model
85
+ checkpoint_location:
86
+ _target_: fairchem.core.calculate.pretrained_mlip.pretrained_checkpoint_path_from_name
87
+ model_name: ${base_model_name}
88
+ overrides:
89
+ backbone:
90
+ otf_graph: True
91
+ max_neighbors: ${max_neighbors}
92
+ regress_stress: ${data.regress_stress}
93
+ always_use_pbc: False
94
+ pass_through_head_outputs: ${data.pass_through_head_outputs}
95
+ heads: ${data.heads}
96
+ optimizer_fn:
97
+ _target_: torch.optim.AdamW
98
+ _partial_: true
99
+ lr: ${lr}
100
+ weight_decay: ${weight_decay}
101
+ cosine_lr_scheduler_fn:
102
+ _target_: fairchem.core.units.mlip_unit.mlip_unit._get_consine_lr_scheduler
103
+ _partial_: true
104
+ warmup_factor: 0.2
105
+ warmup_epochs: 0.01
106
+ lr_min_factor: 0.01
107
+ epochs: ${epochs}
108
+ steps: ${steps}
109
+ print_every: 10
110
+ clip_grad_norm: 100
111
+ max_epochs: ${epochs}
112
+ max_steps: ${steps}
113
+ evaluate_every_n_steps: ${evaluate_every_n_steps}
114
+ callbacks:
115
+ - _target_: fairchem.core.components.callbacks.TrainCheckpointCallback
116
+ checkpoint_every_n_steps: ${checkpoint_every_n_steps}
117
+ max_saved_checkpoints: 5
118
+ - _target_: torchtnt.framework.callbacks.TQDMProgressBar
@@ -0,0 +1,153 @@
1
+ '''Run UMA fine-tuning with configurable backbone freezing.'''
2
+
3
+ import json
4
+ import math
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import yaml
10
+
11
+
12
+ def initialize_model(mode='full', freeze_blocks=0, **kwargs):
13
+ '''Initialize UMA and apply the requested parameter-freezing policy.'''
14
+ from fairchem.core.units.mlip_unit.mlip_unit import initialize_finetuning_model
15
+
16
+ model = initialize_finetuning_model(**kwargs)
17
+
18
+ if mode not in ('full', 'head_only', 'partial'):
19
+ raise ValueError('mode must be full, head_only, or partial.')
20
+ if type(freeze_blocks) is not int or freeze_blocks < 0:
21
+ raise ValueError('freeze_blocks must be a nonnegative integer.')
22
+ if mode != 'partial' and freeze_blocks != 0:
23
+ raise ValueError('freeze_blocks is only used with mode=partial.')
24
+
25
+ if mode == 'partial':
26
+ blocks = model.backbone.blocks
27
+ if freeze_blocks > len(blocks):
28
+ raise ValueError(f'freeze_blocks exceeds the {len(blocks)} backbone blocks.')
29
+
30
+ # Keep the heads trainable; control backbone parameters separately.
31
+ for parameter in model.parameters():
32
+ parameter.requires_grad = True
33
+
34
+ if mode in ('head_only', 'partial'):
35
+ for parameter in model.backbone.parameters():
36
+ parameter.requires_grad = False
37
+
38
+ if mode == 'partial':
39
+ for block in blocks[freeze_blocks:]:
40
+ for parameter in block.parameters():
41
+ parameter.requires_grad = True
42
+ print(f'Trainable backbone blocks: {len(blocks) - freeze_blocks}/{len(blocks)}')
43
+
44
+ trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
45
+ total = sum(p.numel() for p in model.parameters())
46
+ print(f'Trainable parameters: {trainable:,}/{total:,}')
47
+ return model
48
+
49
+
50
+ def finetune(
51
+ dataset,
52
+ output_dir,
53
+ *,
54
+ model=None,
55
+ mode='full',
56
+ freeze_blocks=0,
57
+ energy_coef=20,
58
+ force_coef=2,
59
+ epochs=30,
60
+ batch_size=4,
61
+ lr=1e-4,
62
+ device='cuda',
63
+ max_neighbors=300,
64
+ max_saved_checkpoints=2,
65
+ ):
66
+ '''
67
+ Fine-tune UMA using a directory returned by create_dataset().
68
+
69
+ Args:
70
+ dataset: Directory containing the generated training YAML and datasets.
71
+ output_dir: Root directory for FAIRChem run outputs.
72
+ model: Pretrained UMA model name; None keeps the generated YAML setting.
73
+ mode: 'full', 'head_only', or 'partial'.
74
+ freeze_blocks: Number of initial backbone blocks frozen in partial mode.
75
+ Other backbone parameters remain frozen; heads stay trainable.
76
+ energy_coef: Energy loss coefficient; None keeps the YAML setting.
77
+ force_coef: Force loss coefficient; None keeps the YAML setting.
78
+ Use None for an energy-only dataset.
79
+ epochs: Total training epochs.
80
+ batch_size: Training and validation batch size.
81
+ lr: Learning rate.
82
+ device: 'cuda' or 'cpu'.
83
+ max_neighbors: Maximum neighbors used by the model.
84
+ max_saved_checkpoints: Checkpoint retention limit.
85
+
86
+ Returns:
87
+ Absolute output root as a Path. FAIRChem creates its run folder inside.
88
+ This function starts a new run; it does not implement checkpoint resume.
89
+ '''
90
+ dataset = Path(dataset).expanduser().resolve()
91
+ output_dir = Path(output_dir).expanduser().resolve()
92
+ config = dataset / 'uma_sm_finetune_template.yaml'
93
+ if not config.is_file():
94
+ raise FileNotFoundError(config)
95
+ if mode not in ('full', 'head_only', 'partial'):
96
+ raise ValueError('mode must be full, head_only, or partial.')
97
+ if type(freeze_blocks) is not int or freeze_blocks < 0:
98
+ raise ValueError('freeze_blocks must be a nonnegative integer.')
99
+ if mode != 'partial' and freeze_blocks != 0:
100
+ raise ValueError('freeze_blocks is only used with mode=partial.')
101
+ device = device.upper()
102
+ if device not in ('CPU', 'CUDA'):
103
+ raise ValueError('device must be cpu or cuda.')
104
+ for name, value in [('epochs', epochs), ('batch_size', batch_size),
105
+ ('max_neighbors', max_neighbors),
106
+ ('max_saved_checkpoints', max_saved_checkpoints)]:
107
+ if type(value) is not int or value < 1:
108
+ raise ValueError(f'{name} must be a positive integer.')
109
+ if not math.isfinite(lr) or lr <= 0:
110
+ raise ValueError('lr must be finite and positive.')
111
+
112
+ # Invoke the installed FAIRChem entry point with this Python interpreter.
113
+ entry_point = '''from importlib.metadata import distribution
114
+ next(ep for ep in distribution('fairchem-core').entry_points
115
+ if ep.group == 'console_scripts' and ep.name == 'fairchem').load()()
116
+ '''
117
+ command = [
118
+ sys.executable, '-c', entry_point,
119
+ '-c', str(config),
120
+ f'job.device_type={device}',
121
+ '+job.scheduler.distributed_init_method=FILE',
122
+ f'job.run_dir={json.dumps(str(output_dir))}',
123
+ f'epochs={epochs}',
124
+ f'batch_size={batch_size}',
125
+ f'lr={lr}',
126
+ f'max_neighbors={max_neighbors}',
127
+ 'runner.train_eval_unit.model._target_=uma_finetune.training.initialize_model',
128
+ f'+runner.train_eval_unit.model.mode={mode}',
129
+ f'+runner.train_eval_unit.model.freeze_blocks={freeze_blocks}',
130
+ f'runner.callbacks.0.max_saved_checkpoints={max_saved_checkpoints}',
131
+ ]
132
+ if model is not None:
133
+ command.append(f'base_model_name={json.dumps(model)}')
134
+
135
+ # Find loss positions by property rather than assuming their list order.
136
+ template = yaml.safe_load(config.read_text())
137
+ data_name = next(item['data'] for item in template['defaults']
138
+ if isinstance(item, dict) and 'data' in item)
139
+ data = yaml.safe_load((dataset / 'data' / f'{data_name}.yaml').read_text())
140
+ for property_name, coefficient in [('energy', energy_coef), ('forces', force_coef)]:
141
+ if coefficient is None:
142
+ continue
143
+ if not math.isfinite(coefficient) or coefficient < 0:
144
+ raise ValueError(f'{property_name} coefficient must be finite and nonnegative.')
145
+ index = next((i for i, task in enumerate(data['tasks_list'])
146
+ if task['property'] == property_name
147
+ and not task.get('inference_only', False)), None)
148
+ if index is None:
149
+ raise ValueError(f'No training task for {property_name}; set its coefficient to None.')
150
+ command.append(f'data.tasks_list.{index}.loss_fn.coefficient={coefficient}')
151
+
152
+ subprocess.run(command, check=True, cwd=dataset)
153
+ return output_dir
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.4
2
+ Name: uma-finetune-wrapper
3
+ Version: 0.1.0.dev2
4
+ Summary: A simple Python interface for UMA dataset preparation and fine-tuning with FAIRChem.
5
+ Author: Timmo-hendrik Pukk
6
+ License-Expression: MIT
7
+ Keywords: UMA,FAIRChem,fine-tuning,atomistic,machine-learning
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ License-File: src/uma_finetune/resources/FAIRChem-LICENSE.md
19
+ License-File: src/uma_finetune/resources/NOTICE.md
20
+ Requires-Dist: fairchem-core==2.22.0
21
+ Requires-Dist: PyYAML>=6.0
22
+ Dynamic: license-file
23
+
24
+ # UMA fine-tuning wrapper
25
+
26
+ Prepare a dataset and fine-tune a UMA model through two Python functions:
27
+ `create_dataset()` and `finetune()`.
28
+
29
+ This package wraps FAIRChem's existing dataset-generation and training tools.
30
+ It provides options for backbone freezing, energy and force loss coefficients,
31
+ and basic training settings. The training loop and model implementation remain
32
+ in FAIRChem.
33
+
34
+ The distribution name is **uma-finetune-wrapper**; the Python import is
35
+ **uma_finetune**. This is an independent wrapper, not an official FAIRChem package.
36
+
37
+ ## Installation
38
+
39
+ Python 3.12 and Linux are the tested development environment. The wrapper pins
40
+ `fairchem-core==2.22.0`; compatibility with other FAIRChem versions has not been
41
+ established.
42
+
43
+ Install a PyTorch build suitable for your hardware first. The tested Tesla V100
44
+ configuration uses:
45
+
46
+ ```bash
47
+ python -m pip install 'torch==2.13.0' \
48
+ --index-url https://download.pytorch.org/whl/cu126
49
+ ```
50
+
51
+ See the [PyTorch installation instructions](https://pytorch.org/get-started/locally/)
52
+ for other hardware. This CUDA build is not a recommendation for every GPU.
53
+
54
+ Before publication, install from the package source directory:
55
+
56
+ ```bash
57
+ python -m pip install .
58
+ ```
59
+
60
+ To build and install a wheel instead:
61
+
62
+ ```bash
63
+ python -m pip wheel --no-deps --wheel-dir dist .
64
+ python -m pip install dist/uma_finetune_wrapper-0.1.0.dev2-py3-none-any.whl
65
+ python -m pip check
66
+ ```
67
+
68
+ The installation command installs the declared dependencies. A separate FAIRChem
69
+ source checkout is not needed: this package includes the required YAML templates.
70
+ You also need access to the chosen UMA model and any authentication required by
71
+ its [model repository](https://huggingface.co/facebook/UMA). Model weights are not
72
+ included in this package.
73
+
74
+ ## Quick start
75
+
76
+ Prepare separate training and validation directories containing labelled
77
+ ASE-readable files, such as `.db`, `.traj`, or `.extxyz` files. The labels must be
78
+ readable as ASE calculator results. Split your data before calling the wrapper;
79
+ for related structures, keep the appropriate groups together to avoid leakage.
80
+
81
+ Save this as a Python script and run it in your compute allocation:
82
+
83
+ ```python
84
+ from uma_finetune import create_dataset, finetune
85
+
86
+ if __name__ == '__main__':
87
+ dataset = create_dataset(
88
+ train_path='data/train',
89
+ val_path='data/validation',
90
+ output_dir='data/prepared',
91
+ task='oc25',
92
+ targets='ef',
93
+ base_model='uma-s-1p2',
94
+ num_workers=1,
95
+ )
96
+
97
+ output = finetune(
98
+ dataset=dataset,
99
+ output_dir='runs/example',
100
+ mode='head_only',
101
+ energy_coef=20,
102
+ force_coef=2,
103
+ epochs=1,
104
+ batch_size=4,
105
+ lr=1e-4,
106
+ device='cuda',
107
+ )
108
+
109
+ print('Run output root:', output)
110
+ ```
111
+
112
+ `data/prepared` must not already exist. To train again using prepared data, call
113
+ only `finetune(dataset='data/prepared', ...)`. Each call starts a new training run.
114
+ On an HPC cluster, submit training inside a GPU allocation; the wrapper does not
115
+ submit scheduler jobs.
116
+
117
+ ## Dataset options
118
+
119
+ `create_dataset(train_path, val_path, output_dir, ...)` accepts directories for
120
+ both inputs and returns the absolute prepared-directory path.
121
+
122
+ | Option | Default | Meaning |
123
+ | --- | --- | --- |
124
+ | `task` | `'oc25'` | UMA task: `omol`, `omat`, `odac`, `oc20`, `oc25`, or `omc` |
125
+ | `targets` | `'ef'` | `e`: energy; `ef`: energy and forces; `efs`: also stress |
126
+ | `base_model` | `None` | Model name for the generated configuration; `None` uses FAIRChem's default |
127
+ | `num_workers` | `1` | Positive number of conversion workers |
128
+
129
+ Use the task corresponding to your data and pretrained model. Dataset conversion,
130
+ normalization, and element-reference fitting are performed by FAIRChem. Its input
131
+ requirements apply; real-data testing of this wrapper has used `targets='ef'`.
132
+
133
+ ## Training options
134
+
135
+ `finetune(dataset, output_dir, ...)` uses the generated configuration and returns
136
+ the absolute output root. FAIRChem creates a timestamped run directory inside it.
137
+
138
+ | Option | Default | Meaning |
139
+ | --- | --- | --- |
140
+ | `model` | `None` | Pretrained model name; `None` keeps the dataset configuration's model |
141
+ | `mode` | `'full'` | `full`, `head_only`, or `partial` |
142
+ | `freeze_blocks` | `0` | Number of initial backbone blocks frozen in partial mode |
143
+ | `energy_coef` | `20` | Energy loss coefficient |
144
+ | `force_coef` | `2` | Force loss coefficient |
145
+ | `epochs` | `30` | Total training epochs |
146
+ | `batch_size` | `4` | Training and validation batch size |
147
+ | `lr` | `1e-4` | Learning rate |
148
+ | `device` | `'cuda'` | `cuda` or `cpu` |
149
+ | `max_neighbors` | `300` | Model neighbor limit |
150
+ | `max_saved_checkpoints` | `2` | Retention limit passed to FAIRChem's checkpoint callback |
151
+
152
+ Loss coefficients must be finite and nonnegative. Set either coefficient to
153
+ `None` to keep its YAML setting. For energy-only training, use `force_coef=None`.
154
+ Stress training retains its generated loss coefficient.
155
+
156
+ ### Freezing
157
+
158
+ - **full:** all model parameters are trainable.
159
+ - **head_only:** the backbone is frozen; parameters outside it remain trainable.
160
+ - **partial:** the first `freeze_blocks` blocks are frozen; subsequent blocks
161
+ and heads are trainable. Other backbone parameters remain frozen.
162
+
163
+ For example, freeze the first block:
164
+
165
+ ```python
166
+ output = finetune(
167
+ dataset='data/prepared',
168
+ output_dir='runs/partial',
169
+ mode='partial',
170
+ freeze_blocks=1,
171
+ epochs=1,
172
+ batch_size=1,
173
+ )
174
+ ```
175
+
176
+ `freeze_blocks` must be between zero and the model's block count. A nonzero value
177
+ is only accepted in partial mode. Partial mode with zero frozen blocks still
178
+ freezes backbone parameters outside the blocks, so it differs from full mode.
179
+ The initializer prints the number of trainable parameters.
180
+
181
+ ## Use a trained model
182
+
183
+ After a completed run, find:
184
+
185
+ ```text
186
+ <output_dir>/<run_id>/checkpoints/final/inference_ckpt.pt
187
+ ```
188
+
189
+ Load it through FAIRChem using the same task used for fine-tuning:
190
+
191
+ ```python
192
+ from ase.io import read
193
+ from fairchem.core import FAIRChemCalculator
194
+ from fairchem.core.units.mlip_unit import load_predict_unit
195
+
196
+ predictor = load_predict_unit(
197
+ 'runs/example/<run_id>/checkpoints/final/inference_ckpt.pt',
198
+ device='cuda',
199
+ )
200
+ atoms = read('data/validation/example.db', index=0)
201
+ atoms.calc = FAIRChemCalculator(predictor, task_name='oc25')
202
+
203
+ energy = atoms.get_potential_energy() # eV
204
+ forces = atoms.get_forces(apply_constraint=False) # eV/angstrom
205
+ ```
206
+
207
+ Replace `<run_id>` and the input filename with actual paths. The wrapper does
208
+ not currently implement checkpoint resume or automatic best-checkpoint selection.
209
+
210
+ ## Validation status
211
+
212
+ The following checks were completed on 17 October 2026:
213
+
214
+ - Dataset conversion with the bundled templates: 2,130 training structures.
215
+ - Head-only GPU training, final checkpoint loading, and finite energy/force
216
+ predictions on a 54-atom validation structure.
217
+ - Partial training with `freeze_blocks=1`, producing a final checkpoint.
218
+ - Fresh-environment wheel installation followed by dataset conversion,
219
+ head-only GPU training, and checkpoint inference without an editable FAIRChem
220
+ checkout.
221
+
222
+ The tested combination was Python 3.12, FAIRChem 2.22.0, PyTorch 2.13.0+cu126,
223
+ and a Tesla V100-SXM2-32GB. Full-mode GPU training, CPU training, other tasks,
224
+ and real-data energy-only/stress training have not been tested for this wrapper.
225
+ These are functionality checks, not evidence of model accuracy or generalization.
226
+
227
+ Lightweight wrapper tests can be run from the source directory:
228
+
229
+ ```bash
230
+ python -m unittest discover -s tests -v
231
+ ```
232
+
233
+ For development, install with `python -m pip install -e .`. Keep a snapshot of
234
+ an environment that passed your tests using `python -m pip freeze > requirements-tested.txt`.
235
+ This snapshot records an environment; it does not replace the package's dependency
236
+ metadata or the CUDA-specific installation instructions.
237
+
238
+ ## License and attribution
239
+
240
+ The wrapper is distributed under the MIT license; see `LICENSE`.
241
+ The bundled FAIRChem 2.22.0 YAML templates retain their original copyright and
242
+ MIT license in `src/uma_finetune/resources/FAIRChem-LICENSE.md`, with provenance
243
+ in `src/uma_finetune/resources/NOTICE.md`.
244
+
245
+ The wrapper's license does not change the terms for UMA model weights, datasets,
246
+ or other dependencies. Follow the upstream model's terms and citation guidance
247
+ when using it in research.
248
+
249
+ Upstream: [FAIRChem](https://github.com/facebookresearch/fairchem).
@@ -0,0 +1,16 @@
1
+ uma_finetune/__init__.py,sha256=42u-cwQRprTXU_pRLNQPKYrk9YDF9wqzXlhUzrsHAPs,170
2
+ uma_finetune/dataset.py,sha256=s8EoSvie0pA7s-sma2x0474qWfSvgf_ObQ7EW7WF0Gw,2311
3
+ uma_finetune/training.py,sha256=Ei0CkT2TY1ytSvD0zy1BILLjLDmP9DEvY_Bxha7z_tk,6505
4
+ uma_finetune/resources/FAIRChem-LICENSE.md,sha256=PhCCeTiq6eWRZHciyDAGWpWzrych8NfwLzc4abQP6B0,1093
5
+ uma_finetune/resources/NOTICE.md,sha256=BiU7jlt9w5979X4783bfwVzaKPH5pPEsZfEKH0tzME8,622
6
+ uma_finetune/resources/configs/uma/finetune/uma_sm_finetune_template.yaml,sha256=OGQgSaSqvExd6rmpg01IjLx6b6o--ZW4gGDknzGjcq0,3800
7
+ uma_finetune/resources/configs/uma/finetune/data/uma_conserving_data_task_energy.yaml,sha256=BR_7QQvBo_d06MANmvYXrWNAZ-HJKDMOBVOtZUmM_Jo,2041
8
+ uma_finetune/resources/configs/uma/finetune/data/uma_conserving_data_task_energy_force.yaml,sha256=wa6zpC3i5KHOV8swE5dBK4QpdORmDXcaSZkKsqzZ8Q8,2308
9
+ uma_finetune/resources/configs/uma/finetune/data/uma_conserving_data_task_energy_force_stress.yaml,sha256=5IpUVwGBDVR567U3cQZFDlB4UFXTPVETY7DXdWdZPdE,2471
10
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/licenses/LICENSE,sha256=V8T-HymNKX2rCymqfxOzsNgiAkMZ1kVRmG4w8W1Uk5A,1075
11
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/licenses/src/uma_finetune/resources/FAIRChem-LICENSE.md,sha256=PhCCeTiq6eWRZHciyDAGWpWzrych8NfwLzc4abQP6B0,1093
12
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/licenses/src/uma_finetune/resources/NOTICE.md,sha256=BiU7jlt9w5979X4783bfwVzaKPH5pPEsZfEKH0tzME8,622
13
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/METADATA,sha256=7p6Wja0uOaCUulQRiauccXGliC18RhoYwjsOOfZtG28,9332
14
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
15
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/top_level.txt,sha256=gKWWMdhx1KrESvpMdYjVKvpvoUJ5HNm52Ijc6AbdJzg,13
16
+ uma_finetune_wrapper-0.1.0.dev2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Timmo-hendrik Pukk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # License
2
+
3
+ MIT License
4
+
5
+ Copyright (c) Meta, Inc. and its affiliates.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ # FAIRChem templates
2
+
3
+ The four YAML files under configs/uma/finetune are unmodified copies from
4
+ FAIRChem 2.22.0, supplied from the working source checkout for this project.
5
+ Upstream project: https://github.com/facebookresearch/fairchem
6
+
7
+ Copyright (c) Meta, Inc. and its affiliates.
8
+ These templates are distributed under the MIT license reproduced in
9
+ FAIRChem-LICENSE.md. This notice does not assign a license to the wrapper itself.
10
+
11
+ The conversion script is supplied by the installed fairchem-core dependency;
12
+ it is not copied into this package. Keep these resources matched to the pinned
13
+ FAIRChem version when upgrading.
@@ -0,0 +1 @@
1
+ uma_finetune