NequIP / model /nn /norm.py
dadadaxi's picture
Upload folder using huggingface_hub
3e02ab8 verified
Raw
History Blame Contribute Delete
2.93 kB
import torch
from typing import Union, Sequence, Dict
from math import sqrt
from onescience.datapipes.materials.nequip import AtomicDataDict
class AvgNumNeighborsNorm(torch.nn.Module):
def __init__(
self,
type_names: Sequence[str],
avg_num_neighbors: Union[float, Dict[str, float]],
) -> None:
"""
Module to normalize features during training using per type edge sum normalization.
Args:
type_names (Sequence[str]): list of atom type names
avg_num_neighbors (float/Dict[str, float]): used to normalize edge sums for better numerics
"""
super().__init__()
assert avg_num_neighbors is not None, "avg_num_neighbors must be specified"
self.in_field = self.out_field = AtomicDataDict.NODE_FEATURES_KEY
self.norm_key = AtomicDataDict.FEATURE_NORM_FACTOR_KEY
# Put avg_num_neighbors in a list (global or per type)
if isinstance(avg_num_neighbors, (float, int)):
avg_num_neighbors = [avg_num_neighbors]
elif isinstance(avg_num_neighbors, dict):
assert set(type_names) == set(avg_num_neighbors.keys())
avg_num_neighbors = [avg_num_neighbors[k] for k in type_names]
else:
raise RuntimeError(
"Unrecognized format for `avg_num_neighbors`, only floats or dicts allowed."
)
assert isinstance(avg_num_neighbors, list)
# Tensorize avg_num_neighbors and register as buffer
norm_const = torch.tensor([(1.0 / sqrt(N)) for N in avg_num_neighbors])
norm_const = norm_const.reshape(-1, 1)
# Persistent=False to ensure backwards compatibility of FMs.
# TODO remove this once we're sure FMs are not using this anymore
self.register_buffer("norm_const", norm_const, persistent=False)
# If global avg_num_neighbors or only one type, no need to do embedding lookup in forward
self.norm_shortcut = self.norm_const.numel() == 1
def forward(self, data: AtomicDataDict.Type) -> AtomicDataDict.Type:
features = data[self.in_field]
norm_size = features.size(0)
if self.norm_key in data and data[self.norm_key].size(0) == norm_size:
norm_factor = data[self.norm_key]
else:
# Compute norm factor for the first time
if self.norm_shortcut:
# No need to do embedding lookup in forward
norm_factor = self.norm_const.expand(norm_size, -1)
else:
# Embed each avg_num_neighbors value per type
norm_factor = torch.nn.functional.embedding(
data[AtomicDataDict.ATOM_TYPE_KEY][:norm_size],
self.norm_const,
)
data[self.norm_key] = norm_factor # shape: (num_local_nodes, 1)
data[self.out_field] = norm_factor * features
return data