From f38f3e7417be1127f8eee93d80be1b6415fa546d Mon Sep 17 00:00:00 2001 From: alphalm4 Date: Wed, 22 Jul 2026 17:00:59 +0900 Subject: [PATCH] add sevennet-nano --- .gitignore | 1 + CHANGELOG.md | 5 + README.md | 11 ++ docs/source/cite.md | 11 ++ docs/source/user_guide/ase_calculator.md | 2 +- docs/source/user_guide/pretrained.md | 29 +++++ sevenn/_const.py | 8 ++ sevenn/calculator.py | 10 +- sevenn/main/sevenn_get_model.py | 4 +- sevenn/util.py | 12 ++ tests/unit_tests/test_pretrained.py | 138 ++++++++++++++++++++++- 11 files changed, 222 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index e2f21aae..d78d6a60 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ sevenn/pretrained_potentials/SevenNet_omat sevenn/pretrained_potentials/SevenNet_omni sevenn/pretrained_potentials/SevenNet_omni_i8 sevenn/pretrained_potentials/SevenNet_omni_i12 +sevenn/pretrained_potentials/SevenNet_nano # from compile sevenn/pair_d3* diff --git a/CHANGELOG.md b/CHANGELOG.md index 14fabea4..73a24756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. - Training features used for SevenNet-Omni: batch training, `OrderedSampler`, `grad_clip`, `onecyclelr` - Loss: MAE, L2MAE - Dataset type: `aselmdb`, `custom` +- SevenNet-Nano checkpoints for `7net-nano-4.5`, `7net-nano-5.0`, `7net-nano-5.5`, and `7net-nano-6.0` + +### Changed +- The default dtype of the rescale layer is changed to double precision. Use the `SEVENN_SHIFT_SCALE_DTYPE` environment variable for backward single-precision compatibility. +- The recommended LAMMPS ML-IAP version is bumped to `stable_22Jul2025_update4`. ### Fixed - `D3Calculator()` segfault bug when reusing the calculator within different sized `Atoms`. diff --git a/README.md b/README.md index 420f67ea..fedb87ec 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,17 @@ If you utilize the pretrained model SevenNet-Omni or multi-task training strateg } ``` +If you utilize the pretrained model SevenNet-Nano, please cite the following paper: +```bib +@article{oh_lightweight_2026, + title = {A Lightweight Universal Machine-Learning Interatomic Potential via Knowledge Distillation for Scalable Atomistic Simulations}, + doi = {10.1021/acs.jcim.6c01103}, + journal = {J. Chem. Inf. Model.}, + author = {Oh, Sangmin and You, Jinmu and Kim, Jaesun and Lee, Jiho and An, Hyungmin and Han, Seungwu and Kang, Youngho}, + year = {2026}, +} +``` + If you utilize the reEWC forgetting-aware fine-tuning strategy for continual learning of pretrained universal machine-learning interatomic potentials, please cite the following paper: ```bib @article{kim_efficient_2026, diff --git a/docs/source/cite.md b/docs/source/cite.md index 5ed6cb2b..0e84e84d 100644 --- a/docs/source/cite.md +++ b/docs/source/cite.md @@ -40,3 +40,14 @@ If you utilize the pretrained model SevenNet-Omni or multi-task training strateg year = {2026}, } ``` + +If you utilize the pretrained model SevenNet-Nano, please cite the following paper: +```bib +@article{oh_lightweight_2026, + title = {A Lightweight Universal Machine-Learning Interatomic Potential via Knowledge Distillation for Scalable Atomistic Simulations}, + doi = {10.1021/acs.jcim.6c01103}, + journal = {J. Chem. Inf. Model.}, + author = {Oh, Sangmin and You, Jinmu and Kim, Jaesun and Lee, Jiho and An, Hyungmin and Han, Seungwu and Kang, Youngho}, + year = {2026}, +} +``` diff --git a/docs/source/user_guide/ase_calculator.md b/docs/source/user_guide/ase_calculator.md index b55212cd..eea248cc 100644 --- a/docs/source/user_guide/ase_calculator.md +++ b/docs/source/user_guide/ase_calculator.md @@ -69,7 +69,7 @@ calc = SevenNetCalculator(model='7net-0', enable_cueq=True) # or enable_flash=Tr If you encounter the error `CUDA is not installed or nvcc is not available`, please ensure the `nvcc` compiler is available. Currently, CPU + D3 is not supported. -Various pretrained SevenNet models can be accessed by setting the model variable to predefined keywords like `7net-omni`, `7net-mf-ompa`, `7net-omat`, `7net-l3i5`, and `7net-0`. +Various pretrained SevenNet models can be accessed by setting the model variable to predefined keywords like `7net-omni`, `7net-nano-5.5`, `7net-mf-ompa`, `7net-omat`, `7net-l3i5`, and `7net-0`. User-trained models can be applied with the ASE calculator. In this case, the `model` parameter should be set to the checkpoint path from training. diff --git a/docs/source/user_guide/pretrained.md b/docs/source/user_guide/pretrained.md index 6d7f9d52..29818662 100644 --- a/docs/source/user_guide/pretrained.md +++ b/docs/source/user_guide/pretrained.md @@ -42,6 +42,12 @@ Multiple inference tasks are available for multi-fidelity architecture models, S - Multi-task - `mpa` (PBE+U)
`matpes_r2scan` (r²SCAN)
`omol25_low` (ωB97M-V)
and 10 more tasks +* - [SevenNet-Nano](#sevennet-nano) + - Lightweight model distilled from the SevenNet-Omni `mpa` task + - $l_{\mathrm{max}}=2$
$N_{\mathrm{layer}}=3$
parity=full
cutoff=4.5, 5.0, 5.5, or 6.0 Å + - Single-task + - N/A + * - [SevenNet-MF-ompa](#sevennet-mf-ompa) - MPtrj, sAlex, OMat24 - $l_{\mathrm{max}}=3$
$N_{\mathrm{layer}}=5$
parity=full @@ -146,6 +152,29 @@ SevenNet-Omni-i12 follows the same training strategy as [SevenNet-Omni](#sevenne |:---:|:---:|:---:|:---:| |**0.873**|0.906|0.192|0.0617| +--- +## SevenNet-Nano +> Model keywords: `7net-nano-4.5` | `7net-nano-5.0` | `7net-nano-5.5` | `7net-nano-6.0` + +SevenNet-Nano is a lightweight pretrained model released with [A Lightweight Universal Machine-Learning Interatomic Potential via Knowledge Distillation for Scalable Atomistic Simulations](https://doi.org/10.1021/acs.jcim.6c01103). It is provided as four single-task checkpoint variants with different radial cutoffs. Distilled from the SevenNet-Omni `mpa` task, SevenNet-Nano compresses broad cross-domain chemical knowledge into a 105k-parameter model, achieving over an order-of-magnitude speedup over its teacher while retaining balanced accuracy across diverse atomistic environments. + +Choose the cutoff explicitly through the model keyword: + +```python +from sevenn.calculator import SevenNetCalculator +calc = SevenNetCalculator('7net-nano-5.5') +``` + +```{table} SevenNet-Nano cutoff variants +:widths: 40 60 +| Model keyword | Cutoff | +|---------------|--------| +| `7net-nano-4.5` | 4.5 Å | +| `7net-nano-5.0` | 5.0 Å | +| `7net-nano-5.5` | 5.5 Å | +| `7net-nano-6.0` | 6.0 Å | +``` + --- ## SevenNet-MF-ompa > Model keywords: `7net-mf-ompa` | `SevenNet-mf-ompa` diff --git a/sevenn/_const.py b/sevenn/_const.py index 6c78f9c8..3d23c80b 100644 --- a/sevenn/_const.py +++ b/sevenn/_const.py @@ -66,6 +66,10 @@ SEVENNET_omni = f'{_prefix}/SevenNet_omni/checkpoint_sevennet_omni.pth' SEVENNET_omni_i8 = f'{_prefix}/SevenNet_omni_i8/checkpoint_sevennet_omni_i8.pth' SEVENNET_omni_i12 = f'{_prefix}/SevenNet_omni_i12/checkpoint_sevennet_omni_i12.pth' +SEVENNET_nano_4_5 = f'{_prefix}/SevenNet_nano/checkpoint_7net_nano_4.5.pth' +SEVENNET_nano_5_0 = f'{_prefix}/SevenNet_nano/checkpoint_7net_nano_5.0.pth' +SEVENNET_nano_5_5 = f'{_prefix}/SevenNet_nano/checkpoint_7net_nano_5.5.pth' +SEVENNET_nano_6_0 = f'{_prefix}/SevenNet_nano/checkpoint_7net_nano_6.0.pth' _git_prefix = 'https://github.com/MDIL-SNU/SevenNet/releases/download' CHECKPOINT_DOWNLOAD_LINKS = { @@ -74,6 +78,10 @@ SEVENNET_omni: f'{_git_prefix}/v0.12.0.cp/checkpoint_sevennet_omni.pth', SEVENNET_omni_i8: f'{_git_prefix}/v0.12.1.cp/checkpoint_sevennet_omni_i8.pth', SEVENNET_omni_i12: f'{_git_prefix}/v0.12.1.cp/checkpoint_sevennet_omni_i12.pth', + SEVENNET_nano_4_5: f'{_git_prefix}/v0.13.1.cp/checkpoint_7net_nano_4.5.pth', + SEVENNET_nano_5_0: f'{_git_prefix}/v0.13.1.cp/checkpoint_7net_nano_5.0.pth', + SEVENNET_nano_5_5: f'{_git_prefix}/v0.13.1.cp/checkpoint_7net_nano_5.5.pth', + SEVENNET_nano_6_0: f'{_git_prefix}/v0.13.1.cp/checkpoint_7net_nano_6.0.pth', } # to avoid torch script to compile torch_geometry.data AtomGraphDataType = Dict[str, torch.Tensor] # But it can contain 'lmp_data' diff --git a/sevenn/calculator.py b/sevenn/calculator.py index a1256b5f..a3d85da5 100644 --- a/sevenn/calculator.py +++ b/sevenn/calculator.py @@ -44,8 +44,9 @@ def __init__( Parameters ---------- model: str | Path | AtomGraphSequential, default='7net-0' - Name of pretrained models (7net-omni, 7net-mf-ompa, 7net-omat, 7net-l3i5, - 7net-0) or path to the checkpoint, deployed model or the model itself + Name of pretrained models (7net-omni, 7net-nano-5.5, 7net-mf-ompa, + 7net-omat, 7net-l3i5, 7net-0) or path to the checkpoint, deployed model + or the model itself file_type: str, default='checkpoint' one of 'checkpoint' | 'model_instance' device: str | torch.device, default='auto' @@ -255,8 +256,9 @@ def __init__( Parameters ---------- model: str | Path | AtomGraphSequential - Name of pretrained models (7net-omni, 7net-mf-ompa, 7net-omat, 7net-l3i5, - 7net-0) or path to the checkpoint, deployed model or the model itself + Name of pretrained models (7net-omni, 7net-nano-5.5, 7net-mf-ompa, + 7net-omat, 7net-l3i5, 7net-0) or path to the checkpoint, deployed model + or the model itself file_type: str, default='checkpoint' one of 'checkpoint' | 'model_instance' device: str | torch.device, default='auto' diff --git a/sevenn/main/sevenn_get_model.py b/sevenn/main/sevenn_get_model.py index 8f7bcdb6..baea7bf8 100644 --- a/sevenn/main/sevenn_get_model.py +++ b/sevenn/main/sevenn_get_model.py @@ -7,8 +7,8 @@ 'deploy LAMMPS model from the checkpoint' ) checkpoint_help = ( - 'Pretrained model name (7net-omni, 7net-omni-i8, 7net-omni-i12, etc.) ' - 'or path to checkpoint file. See documentation for all available models.' + 'Pretrained model name (7net-omni, 7net-nano-5.5, etc.) or path to ' + 'checkpoint file. See documentation for all available models.' ) output_name_help = 'filename prefix' get_parallel_help = 'deploy parallel model' diff --git a/sevenn/util.py b/sevenn/util.py index 89d54065..502b3c09 100644 --- a/sevenn/util.py +++ b/sevenn/util.py @@ -289,6 +289,14 @@ def pretrained_name_to_path(name: str) -> str: checkpoint_path = _const.SEVENNET_omni_i8 elif name in [f'{n}-omni-i12' for n in heads]: checkpoint_path = _const.SEVENNET_omni_i12 + elif name in [f'{n}-nano-4.5' for n in heads]: + checkpoint_path = _const.SEVENNET_nano_4_5 + elif name in [f'{n}-nano-5.0' for n in heads]: + checkpoint_path = _const.SEVENNET_nano_5_0 + elif name in [f'{n}-nano-5.5' for n in heads]: + checkpoint_path = _const.SEVENNET_nano_5_5 + elif name in [f'{n}-nano-6.0' for n in heads]: + checkpoint_path = _const.SEVENNET_nano_6_0 else: raise ValueError('Not a valid pretrained model name') url = _const.CHECKPOINT_DOWNLOAD_LINKS.get(checkpoint_path) @@ -331,6 +339,10 @@ def get_available_pretrained_models() -> List[str]: 'SEVENNET_omni': '7net-omni', 'SEVENNET_omni_i8': '7net-omni-i8', 'SEVENNET_omni_i12': '7net-omni-i12', + 'SEVENNET_nano_4_5': '7net-nano-4.5', + 'SEVENNET_nano_5_0': '7net-nano-5.0', + 'SEVENNET_nano_5_5': '7net-nano-5.5', + 'SEVENNET_nano_6_0': '7net-nano-6.0', } models = [] diff --git a/tests/unit_tests/test_pretrained.py b/tests/unit_tests/test_pretrained.py index 7398fe2b..e5e597a0 100644 --- a/tests/unit_tests/test_pretrained.py +++ b/tests/unit_tests/test_pretrained.py @@ -373,14 +373,14 @@ def test_7net_omni_mpa(atoms_pbc, atoms_mol): ) g1_ref_s = -1 * torch.tensor( # xx, yy, zz, xy, yz, zx - [-0.6500675, -0.0290563, -0.0290563 , 0.02576996, 0.00374571, 0.02576996] + [-0.6500675, -0.0290563, -0.0290563, 0.02576996, 0.00374571, 0.02576996] ) g2_ref_e = torch.tensor([-12.918253898620605]) g2_ref_f = torch.tensor( [ [0.0, -13.32638, 7.1434574], - [0.0, 9.442289 , -9.77207], + [0.0, 9.442289, -9.77207], [0.0, 3.8840904, 2.6286132], ] ) @@ -486,3 +486,137 @@ def test_7net_omni_i12_mpa(atoms_pbc, atoms_mol): assert acl(g2.inferred_total_energy, g2_ref_e) assert acl(g2.inferred_force, g2_ref_f) + + +@pytest.mark.parametrize( + 'name, cutoff_ref, g1_ref_e, g1_ref_f, g1_ref_s, g2_ref_e, g2_ref_f', + [ + ( + '7net-nano-4.5', + 4.5, + -3.5564712945510024, + [ + [11.843839645385742, -0.06617936491966248, -0.06617937982082367], + [-11.843839645385742, 0.06617936491966248, 0.0661793053150177], + ], + [ + 0.605344831943512, + 0.02144481986761093, + 0.021444812417030334, + -0.03438035026192665, + -0.003832500660791993, + -0.03438035771250725, + ], + -12.919640449266296, + [ + [0.0, -13.341249465942383, 7.200963020324707], + [0.0, 9.452506065368652, -9.814258575439453], + [0.0, 3.8887438774108887, 2.6132965087890625], + ], + ), + ( + '7net-nano-5.0', + 5.0, + -3.4316864360099615, + [ + [12.497757911682129, -0.009577874094247818, -0.009577933698892593], + [-12.497756958007812, 0.009577878750860691, 0.009577957913279533], + ], + [ + 0.6648563146591187, + 0.040433332324028015, + 0.04043332114815712, + -0.029892124235630035, + -0.003740792628377676, + -0.029892126098275185, + ], + -12.910758047958524, + [ + [0.0, -13.562267303466797, 7.167722702026367], + [0.0, 9.584957122802734, -9.880509376525879], + [0.0, 3.9773099422454834, 2.7127861976623535], + ], + ), + ( + '7net-nano-5.5', + 5.5, + -3.532371955806397, + [ + [12.454666137695312, -0.034893378615379333, -0.0348934531211853], + [-12.454666137695312, 0.034893378615379333, 0.03489343076944351], + ], + [ + 0.6540243029594421, + 0.021435830742120743, + 0.021435843780636787, + -0.03487098217010498, + -0.00511842779815197, + -0.03487098589539528, + ], + -12.922036098234884, + [ + [0.0, -13.198136329650879, 7.108638763427734], + [0.0, 9.389389038085938, -9.699457168579102], + [0.0, 3.8087470531463623, 2.5908186435699463], + ], + ), + ( + '7net-nano-6.0', + 6.0, + -3.484466470155766, + [ + [12.169546127319336, -0.02828906662762165, -0.028289003297686577], + [-12.169546127319336, 0.028289061039686203, 0.02828901819884777], + ], + [ + 0.6406453251838684, + 0.026448842138051987, + 0.026448845863342285, + -0.026171253994107246, + -0.003374285064637661, + -0.026171250268816948, + ], + -12.934674727250979, + [ + [0.0, -13.23235034942627, 7.185378074645996], + [0.0, 9.451717376708984, -9.761429786682129], + [0.0, 3.780632972717285, 2.576051712036133], + ], + ), + ], + ids=['7net-nano-4.5', '7net-nano-5.0', '7net-nano-5.5', '7net-nano-6.0'], +) +def test_7net_nano( + name, + cutoff_ref, + g1_ref_e, + g1_ref_f, + g1_ref_s, + g2_ref_e, + g2_ref_f, + atoms_pbc, + atoms_mol, +): + cp_path = pretrained_name_to_path(name) + cp_dict = torch.load(cp_path, map_location='cpu', weights_only=False) + assert sorted(cp_dict) == ['config', 'hash', 'model_state_dict', 'time'] + + model, config = model_from_checkpoint( + cp_path, enable_flash=False, enable_cueq=False + ) + cutoff = config['cutoff'] + assert cutoff == cutoff_ref + + g1 = AtomGraphData.from_numpy_dict(unlabeled_atoms_to_graph(atoms_pbc, cutoff)) + g2 = AtomGraphData.from_numpy_dict(unlabeled_atoms_to_graph(atoms_mol, cutoff)) + + model.set_is_batch_data(False) + g1 = model(g1) + g2 = model(g2) + + assert acl(g1.inferred_total_energy, torch.tensor(g1_ref_e)) + assert acl(g1.inferred_force, torch.tensor(g1_ref_f)) + assert acl(g1.inferred_stress, torch.tensor(g1_ref_s)) + + assert acl(g2.inferred_total_energy, torch.tensor(g2_ref_e)) + assert acl(g2.inferred_force, torch.tensor(g2_ref_f))