MagNET / magnet /eqV2 /layer_norm.py
ekwan16's picture
Add files using upload-large-folder tool
64c992d verified
Raw
History Blame Contribute Delete
15.1 kB
'''
1. Normalize features of shape (N, sphere_basis, C),
with sphere_basis = (lmax + 1) ** 2.
2. The difference from `layer_norm.py` is that all type-L vectors have
the same number of channels and input features are of shape (N, sphere_basis, C).
'''
import torch
import torch.nn as nn
def get_normalization_layer(norm_type, lmax, num_channels, eps=1e-5, affine=True, normalization='component'):
assert norm_type in ['layer_norm', 'layer_norm_sh', 'rms_norm_sh']
if norm_type == 'layer_norm':
norm_class = EquivariantLayerNormArray
elif norm_type == 'layer_norm_sh':
norm_class = EquivariantLayerNormArraySphericalHarmonics
elif norm_type == 'rms_norm_sh':
norm_class = EquivariantRMSNormArraySphericalHarmonicsV2
else:
raise ValueError
return norm_class(lmax, num_channels, eps, affine, normalization)
def get_l_to_all_m_expand_index(lmax):
expand_index = torch.zeros([(lmax + 1) ** 2]).long()
for l in range(lmax + 1):
start_idx = l ** 2
length = 2 * l + 1
expand_index[start_idx : (start_idx + length)] = l
return expand_index
class EquivariantLayerNormArray(nn.Module):
def __init__(self, lmax, num_channels, eps=1e-5, affine=True, normalization='component'):
super().__init__()
self.lmax = lmax
self.num_channels = num_channels
self.eps = eps
self.affine = affine
if affine:
self.affine_weight = nn.Parameter(torch.ones(lmax + 1, num_channels))
self.affine_bias = nn.Parameter(torch.zeros(num_channels))
else:
self.register_parameter('affine_weight', None)
self.register_parameter('affine_bias', None)
assert normalization in ['norm', 'component']
self.normalization = normalization
def __repr__(self):
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps})"
@torch.cuda.amp.autocast(enabled=False)
def forward(self, node_input):
'''
Assume input is of shape [N, sphere_basis, C]
'''
out = []
for l in range(self.lmax + 1):
start_idx = l ** 2
length = 2 * l + 1
feature = node_input.narrow(1, start_idx, length)
# For scalars, first compute and subtract the mean
if l == 0:
feature_mean = torch.mean(feature, dim=2, keepdim=True)
feature = feature - feature_mean
# Then compute the rescaling factor (norm of each feature vector)
# Rescaling of the norms themselves based on the option "normalization"
if self.normalization == 'norm':
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
elif self.normalization == 'component':
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
feature_norm = (feature_norm + self.eps).pow(-0.5)
if self.affine:
weight = self.affine_weight.narrow(0, l, 1) # [1, C]
weight = weight.view(1, 1, -1) # [1, 1, C]
feature_norm = feature_norm * weight # [N, 1, C]
feature = feature * feature_norm
if self.affine and l == 0:
bias = self.affine_bias
bias = bias.view(1, 1, -1)
feature = feature + bias
out.append(feature)
out = torch.cat(out, dim=1)
return out
class EquivariantLayerNormArraySphericalHarmonics(nn.Module):
'''
1. Normalize over L = 0.
2. Normalize across all m components from degrees L > 0.
3. Do not normalize separately for different L (L > 0).
'''
def __init__(self, lmax, num_channels, eps=1e-5, affine=True, normalization='component', std_balance_degrees=True):
super().__init__()
self.lmax = lmax
self.num_channels = num_channels
self.eps = eps
self.affine = affine
self.std_balance_degrees = std_balance_degrees
# for L = 0
self.norm_l0 = torch.nn.LayerNorm(self.num_channels, eps=self.eps, elementwise_affine=self.affine)
# for L > 0
if self.affine:
self.affine_weight = nn.Parameter(torch.ones(self.lmax, self.num_channels))
else:
self.register_parameter('affine_weight', None)
assert normalization in ['norm', 'component']
self.normalization = normalization
if self.std_balance_degrees:
balance_degree_weight = torch.zeros((self.lmax + 1) ** 2 - 1, 1)
for l in range(1, self.lmax + 1):
start_idx = l ** 2 - 1
length = 2 * l + 1
balance_degree_weight[start_idx : (start_idx + length), :] = (1.0 / length)
balance_degree_weight = balance_degree_weight / self.lmax
self.register_buffer('balance_degree_weight', balance_degree_weight)
else:
self.balance_degree_weight = None
def __repr__(self):
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps}, std_balance_degrees={self.std_balance_degrees})"
@torch.cuda.amp.autocast(enabled=False)
def forward(self, node_input):
'''
Assume input is of shape [N, sphere_basis, C]
'''
out = []
# for L = 0
feature = node_input.narrow(1, 0, 1)
feature = self.norm_l0(feature)
out.append(feature)
# for L > 0
if self.lmax > 0:
num_m_components = (self.lmax + 1) ** 2
feature = node_input.narrow(1, 1, num_m_components - 1)
# Then compute the rescaling factor (norm of each feature vector)
# Rescaling of the norms themselves based on the option "normalization"
if self.normalization == 'norm':
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
elif self.normalization == 'component':
if self.std_balance_degrees:
feature_norm = feature.pow(2) # [N, (L_max + 1)**2 - 1, C], without L = 0
feature_norm = torch.einsum('nic, ia -> nac', feature_norm, self.balance_degree_weight) # [N, 1, C]
else:
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
feature_norm = (feature_norm + self.eps).pow(-0.5)
for l in range(1, self.lmax + 1):
start_idx = l ** 2
length = 2 * l + 1
feature = node_input.narrow(1, start_idx, length) # [N, (2L + 1), C]
if self.affine:
weight = self.affine_weight.narrow(0, (l - 1), 1) # [1, C]
weight = weight.view(1, 1, -1) # [1, 1, C]
feature_scale = feature_norm * weight # [N, 1, C]
else:
feature_scale = feature_norm
feature = feature * feature_scale
out.append(feature)
out = torch.cat(out, dim=1)
return out
class EquivariantRMSNormArraySphericalHarmonics(nn.Module):
'''
1. Normalize across all m components from degrees L >= 0.
'''
def __init__(self, lmax, num_channels, eps=1e-5, affine=True, normalization='component'):
super().__init__()
self.lmax = lmax
self.num_channels = num_channels
self.eps = eps
self.affine = affine
# for L >= 0
if self.affine:
self.affine_weight = nn.Parameter(torch.ones((self.lmax + 1), self.num_channels))
else:
self.register_parameter('affine_weight', None)
assert normalization in ['norm', 'component']
self.normalization = normalization
def __repr__(self):
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps})"
@torch.cuda.amp.autocast(enabled=False)
def forward(self, node_input):
'''
Assume input is of shape [N, sphere_basis, C]
'''
out = []
# for L >= 0
feature = node_input
if self.normalization == 'norm':
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
elif self.normalization == 'component':
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
feature_norm = (feature_norm + self.eps).pow(-0.5)
for l in range(0, self.lmax + 1):
start_idx = l ** 2
length = 2 * l + 1
feature = node_input.narrow(1, start_idx, length) # [N, (2L + 1), C]
if self.affine:
weight = self.affine_weight.narrow(0, l, 1) # [1, C]
weight = weight.view(1, 1, -1) # [1, 1, C]
feature_scale = feature_norm * weight # [N, 1, C]
else:
feature_scale = feature_norm
feature = feature * feature_scale
out.append(feature)
out = torch.cat(out, dim=1)
return out
class EquivariantRMSNormArraySphericalHarmonicsV2(nn.Module):
'''
1. Normalize across all m components from degrees L >= 0.
2. Expand weights and multiply with normalized feature to prevent slicing and concatenation.
'''
def __init__(self, lmax, num_channels, eps=1e-5, affine=True, normalization='component', centering=True, std_balance_degrees=True):
super().__init__()
self.lmax = lmax
self.num_channels = num_channels
self.eps = eps
self.affine = affine
self.centering = centering
self.std_balance_degrees = std_balance_degrees
# for L >= 0
if self.affine:
self.affine_weight = nn.Parameter(torch.ones((self.lmax + 1), self.num_channels))
if self.centering:
self.affine_bias = nn.Parameter(torch.zeros(self.num_channels))
else:
self.register_parameter('affine_bias', None)
else:
self.register_parameter('affine_weight', None)
self.register_parameter('affine_bias', None)
assert normalization in ['norm', 'component']
self.normalization = normalization
expand_index = get_l_to_all_m_expand_index(self.lmax)
self.register_buffer('expand_index', expand_index)
if self.std_balance_degrees:
balance_degree_weight = torch.zeros((self.lmax + 1) ** 2, 1)
for l in range(self.lmax + 1):
start_idx = l ** 2
length = 2 * l + 1
balance_degree_weight[start_idx : (start_idx + length), :] = (1.0 / length)
balance_degree_weight = balance_degree_weight / (self.lmax + 1)
self.register_buffer('balance_degree_weight', balance_degree_weight)
else:
self.balance_degree_weight = None
def __repr__(self):
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, eps={self.eps}, centering={self.centering}, std_balance_degrees={self.std_balance_degrees})"
@torch.cuda.amp.autocast(enabled=False)
def forward(self, node_input):
'''
Assume input is of shape [N, sphere_basis, C]
'''
feature = node_input
if self.centering:
feature_l0 = feature.narrow(1, 0, 1)
feature_l0_mean = feature_l0.mean(dim=2, keepdim=True) # [N, 1, 1]
feature_l0 = feature_l0 - feature_l0_mean
feature = torch.cat((feature_l0, feature.narrow(1, 1, feature.shape[1] - 1)), dim=1)
# for L >= 0
if self.normalization == 'norm':
assert not self.std_balance_degrees
feature_norm = feature.pow(2).sum(dim=1, keepdim=True) # [N, 1, C]
elif self.normalization == 'component':
if self.std_balance_degrees:
feature_norm = feature.pow(2) # [N, (L_max + 1)**2, C]
feature_norm = torch.einsum('nic, ia -> nac', feature_norm, self.balance_degree_weight) # [N, 1, C]
else:
feature_norm = feature.pow(2).mean(dim=1, keepdim=True) # [N, 1, C]
feature_norm = torch.mean(feature_norm, dim=2, keepdim=True) # [N, 1, 1]
feature_norm = (feature_norm + self.eps).pow(-0.5)
if self.affine:
weight = self.affine_weight.view(1, (self.lmax + 1), self.num_channels) # [1, L_max + 1, C]
weight = torch.index_select(weight, dim=1, index=self.expand_index) # [1, (L_max + 1)**2, C]
feature_norm = feature_norm * weight # [N, (L_max + 1)**2, C]
out = feature * feature_norm
if self.affine and self.centering:
out[:, 0:1, :] = out.narrow(1, 0, 1) + self.affine_bias.view(1, 1, self.num_channels)
return out
class EquivariantDegreeLayerScale(nn.Module):
'''
1. Similar to Layer Scale used in CaiT (Going Deeper With Image Transformers (ICCV'21)), we scale the output of both attention and FFN.
2. For degree L > 0, we scale down the square root of 2 * L, which is to emulate halving the number of channels when using higher L.
'''
def __init__(self, lmax, num_channels, scale_factor=2.0):
super().__init__()
self.lmax = lmax
self.num_channels = num_channels
self.scale_factor = scale_factor
self.affine_weight = nn.Parameter(torch.ones(1, (self.lmax + 1), self.num_channels))
for l in range(1, self.lmax + 1):
self.affine_weight.data[0, l, :].mul_(1.0 / math.sqrt(self.scale_factor * l))
expand_index = get_l_to_all_m_expand_index(self.lmax)
self.register_buffer('expand_index', expand_index)
def __repr__(self):
return f"{self.__class__.__name__}(lmax={self.lmax}, num_channels={self.num_channels}, scale_factor={self.scale_factor})"
def forward(self, node_input):
weight = torch.index_select(self.affine_weight, dim=1, index=self.expand_index) # [1, (L_max + 1)**2, C]
node_input = node_input * weight # [N, (L_max + 1)**2, C]
return node_input