FourCastNet_v2 / model /fcnv2 /fcnv2_sfnonet.py
yzt15806542928's picture
Upload folder using huggingface_hub
eca4864 verified
Raw History Blame Contribute Delete
21.4 kB
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES.
# SPDX-FileCopyrightText: All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import partial
import torch
import torch.nn as nn
import torch_harmonics as harmonics
from apex.normalization import FusedLayerNorm
# helpers
# to fake the sht module with ffts
from fcnv2_layers import (
MLP,
DropPath,
InverseRealFFT2,
RealFFT2,
SpectralAttention2d,
SpectralAttentionS2,
SpectralConv2d,
SpectralConvS2,
trunc_normal_,
)
class SafeRealSHT(harmonics.RealSHT):
"""RealSHT variant that avoids in-place writes during autograd."""
def forward(self, x):
if x.dim() < 2:
raise ValueError("Expected tensor with at least 2 dimensions")
if x.shape[-2:] != (self.nlat, self.nlon):
raise ValueError(
f"Expected spatial shape {(self.nlat, self.nlon)}, got {tuple(x.shape[-2:])}"
)
transformed = torch.view_as_real(
2.0 * torch.pi * torch.fft.rfft(x, dim=-1, norm="forward")
)
weights = self.weights.to(dtype=transformed.dtype)
real = torch.einsum(
"...km,mlk->...lm", transformed[..., : self.mmax, 0], weights
)
imag = torch.einsum(
"...km,mlk->...lm", transformed[..., : self.mmax, 1], weights
)
return torch.view_as_complex(torch.stack((real, imag), dim=-1).contiguous())
class SafeInverseRealSHT(harmonics.InverseRealSHT):
"""InverseRealSHT variant that avoids in-place writes during autograd."""
def forward(self, x):
if x.dim() < 2:
raise ValueError("Expected tensor with at least 2 dimensions")
if x.shape[-2:] != (self.lmax, self.mmax):
raise ValueError(
f"Expected spectral shape {(self.lmax, self.mmax)}, got {tuple(x.shape[-2:])}"
)
spectral = torch.view_as_real(x)
spatial = torch.einsum(
"...lmr,mlk->...kmr", spectral, self.pct.to(dtype=spectral.dtype)
)
spatial = torch.view_as_complex(spatial.contiguous())
# torch_harmonics clears these components in-place. Build the same
# result functionally so autograd can retain the spectral tensor.
longitude = torch.arange(
self.mmax, device=spatial.device, dtype=spatial.real.dtype
)
constrained = (longitude != 0).to(spatial.real.dtype)
if self.nlon % 2 == 0 and self.nlon // 2 < self.mmax:
constrained = constrained * (
longitude != self.nlon // 2
).to(spatial.real.dtype)
spatial = torch.complex(spatial.real, spatial.imag * constrained)
return torch.fft.irfft(spatial, n=self.nlon, dim=-1, norm="forward")
class SpectralFilterLayer(nn.Module):
def __init__(
self,
forward_transform,
inverse_transform,
embed_dim,
filter_type="linear",
sparsity_threshold=0.0,
use_complex_kernels=True,
hidden_size_factor=2,
compression=None,
rank=128,
complex_network=True,
complex_activation="real",
spectral_layers=1,
drop_rate=0.0,
):
super(SpectralFilterLayer, self).__init__()
if filter_type == "non-linear" and isinstance(
forward_transform, harmonics.RealSHT
):
self.filter = SpectralAttentionS2(
forward_transform,
inverse_transform,
embed_dim,
sparsity_threshold,
use_complex_network=complex_network,
use_complex_kernels=use_complex_kernels,
hidden_size_factor=hidden_size_factor,
complex_activation=complex_activation,
spectral_layers=spectral_layers,
drop_rate=drop_rate,
bias=False,
)
elif filter_type == "non-linear" and isinstance(
forward_transform, harmonics.RealFFT2
):
self.filter = SpectralAttention2d(
forward_transform,
inverse_transform,
embed_dim,
sparsity_threshold,
use_complex_kernels=use_complex_kernels,
hidden_size_factor=hidden_size_factor,
complex_activation=complex_activation,
spectral_layers=spectral_layers,
drop_rate=drop_rate,
bias=False,
)
elif filter_type == "linear" and isinstance(
forward_transform, harmonics.RealSHT
):
self.filter = SpectralConvS2(
forward_transform,
inverse_transform,
embed_dim,
sparsity_threshold,
use_complex_kernels=use_complex_kernels,
compression=compression,
rank=rank,
bias=False,
)
elif filter_type == "linear" and isinstance(forward_transform, RealFFT2):
self.filter = SpectralConv2d(
forward_transform,
inverse_transform,
embed_dim,
sparsity_threshold,
use_complex_kernels=use_complex_kernels,
compression=compression,
rank=rank,
bias=False,
)
else:
raise (NotImplementedError)
def forward(self, x):
return self.filter(x)
class FourierNeuralOperatorBlock(nn.Module):
def __init__(
self,
forward_transform,
inverse_transform,
embed_dim,
filter_type="linear",
mlp_ratio=2.0,
drop_rate=0.0,
drop_path=0.0,
act_layer=nn.GELU,
norm_layer=(nn.LayerNorm, nn.LayerNorm),
# num_blocks = 8,
sparsity_threshold=0.0,
use_complex_kernels=True,
compression=None,
rank=128,
inner_skip="linear",
outer_skip=None, # None, nn.linear or nn.Identity
concat_skip=False,
mlp_mode="none",
complex_network=True,
complex_activation="real",
spectral_layers=1,
checkpointing=False,
):
super(FourierNeuralOperatorBlock, self).__init__()
# norm layer
self.norm0 = norm_layer[0]() # ((h,w))
# convolution layer
self.filter_layer = SpectralFilterLayer(
forward_transform,
inverse_transform,
embed_dim,
filter_type,
sparsity_threshold,
use_complex_kernels=use_complex_kernels,
hidden_size_factor=mlp_ratio,
compression=compression,
rank=rank,
complex_network=complex_network,
complex_activation=complex_activation,
spectral_layers=spectral_layers,
drop_rate=drop_rate,
)
if inner_skip == "linear":
self.inner_skip = nn.Conv2d(embed_dim, embed_dim, 1, 1)
elif inner_skip == "identity":
self.inner_skip = nn.Identity()
self.concat_skip = concat_skip
if concat_skip and inner_skip is not None:
self.inner_skip_conv = nn.Conv2d(2 * embed_dim, embed_dim, 1, bias=False)
if filter_type == "linear":
self.act_layer = act_layer()
# dropout
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
# norm layer
self.norm1 = norm_layer[1]() # ((h,w))
if mlp_mode != "none":
mlp_hidden_dim = int(embed_dim * mlp_ratio)
self.mlp = MLP(
in_features=embed_dim,
hidden_features=mlp_hidden_dim,
act_layer=act_layer,
drop_rate=drop_rate,
checkpointing=checkpointing,
)
if outer_skip == "linear":
self.outer_skip = nn.Conv2d(embed_dim, embed_dim, 1, 1)
elif outer_skip == "identity":
self.outer_skip = nn.Identity()
if concat_skip and outer_skip is not None:
self.outer_skip_conv = nn.Conv2d(2 * embed_dim, embed_dim, 1, bias=False)
def forward(self, x):
residual = x
x = self.norm0(x)
x = self.filter_layer(x).contiguous()
if hasattr(self, "inner_skip"):
if self.concat_skip:
x = torch.cat((x, self.inner_skip(residual)), dim=1)
x = self.inner_skip_conv(x)
else:
x = x + self.inner_skip(residual)
if hasattr(self, "act_layer"):
x = self.act_layer(x)
x = self.norm1(x)
if hasattr(self, "mlp"):
x = self.mlp(x)
x = self.drop_path(x)
if hasattr(self, "outer_skip"):
if self.concat_skip:
x = torch.cat((x, self.outer_skip(residual)), dim=1)
x = self.outer_skip_conv(x)
else:
x = x + self.outer_skip(residual)
return x
# @torch.jit.ignore
# def checkpoint_forward(self, x):
# return checkpoint(self._forward, x)
# def forward(self, x):
# if self.checkpointing:
# return self.checkpoint_forward(x)
# else:
# return self._forward(x)
class FourierNeuralOperatorNet(nn.Module):
def __init__(
self,
params,
spectral_transform="sht",
filter_type="non-linear",
img_size=(721, 1440),
scale_factor=16,
in_chans=2,
out_chans=2,
embed_dim=256,
num_layers=12,
mlp_mode="none",
mlp_ratio=2.0,
drop_rate=0.0,
drop_path_rate=0.0,
num_blocks=16,
sparsity_threshold=0.0,
normalization_layer="instance_norm",
hard_thresholding_fraction=1.0,
use_complex_kernels=True,
big_skip=True,
compression=None,
rank=128,
complex_network=True,
complex_activation="real",
spectral_layers=3,
laplace_weighting=False,
checkpointing=False,
):
super(FourierNeuralOperatorNet, self).__init__()
self.params = params
self.spectral_transform = (
params.spectral_transform
if hasattr(params, "spectral_transform")
else spectral_transform
)
self.filter_type = (
params.filter_type if hasattr(params, "filter_type") else filter_type
)
self.img_size = (params.img_crop_shape_x, params.img_crop_shape_y)
self.scale_factor = (
params.scale_factor if hasattr(params, "scale_factor") else scale_factor
)
self.in_chans = (
params.N_in_channels if hasattr(params, "N_in_channels") else in_chans
)
self.out_chans = (
params.N_out_channels if hasattr(params, "N_out_channels") else out_chans
)
self.embed_dim = self.num_features = (
params.embed_dim if hasattr(params, "embed_dim") else embed_dim
)
self.num_layers = (
params.num_layers if hasattr(params, "num_layers") else num_layers
)
self.num_blocks = (
params.num_blocks if hasattr(params, "num_blocks") else num_blocks
)
self.hard_thresholding_fraction = (
params.hard_thresholding_fraction
if hasattr(params, "hard_thresholding_fraction")
else hard_thresholding_fraction
)
self.normalization_layer = (
params.normalization_layer
if hasattr(params, "normalization_layer")
else normalization_layer
)
self.mlp_mode = params.mlp_mode if hasattr(params, "mlp_mode") else mlp_mode
self.big_skip = params.big_skip if hasattr(params, "big_skip") else big_skip
self.compression = (
params.compression if hasattr(params, "compression") else compression
)
self.rank = params.rank if hasattr(params, "rank") else rank
self.complex_network = (
params.complex_network
if hasattr(params, "complex_network")
else complex_network
)
self.complex_activation = (
params.complex_activation
if hasattr(params, "complex_activation")
else complex_activation
)
self.spectral_layers = (
params.spectral_layers
if hasattr(params, "spectral_layers")
else spectral_layers
)
self.laplace_weighting = (
params.laplace_weighting
if hasattr(params, "laplace_weighting")
else laplace_weighting
)
self.checkpointing = (
params.checkpointing if hasattr(params, "checkpointing") else checkpointing
)
# compute downsampled image size
self.h = self.img_size[0] // self.scale_factor
self.w = self.img_size[1] // self.scale_factor
# dropout
self.pos_drop = nn.Dropout(p=drop_rate) if drop_rate > 0.0 else nn.Identity()
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, self.num_layers)]
# pick norm layer
if self.normalization_layer == "layer_norm":
norm_layer0 = partial(
nn.LayerNorm,
normalized_shape=(self.img_size[0], self.img_size[1]),
eps=1e-6,
)
norm_layer1 = partial(
nn.LayerNorm, normalized_shape=(self.h, self.w), eps=1e-6
)
elif self.normalization_layer == "instance_norm":
norm_layer0 = partial(
nn.InstanceNorm2d,
num_features=self.embed_dim,
eps=1e-6,
affine=True,
track_running_stats=False,
)
norm_layer1 = norm_layer0
# elif self.normalization_layer == "batch_norm":
# norm_layer = partial(nn.InstanceNorm2d, num_features=self.embed_dim, eps=1e-6, affine=True, track_running_stats=False)
else:
raise NotImplementedError(
f"Error, normalization {self.normalization_layer} not implemented."
)
# ENCODER is just an MLP?
encoder_hidden_dim = self.embed_dim
encoder_act = nn.GELU
# encoder0 = nn.Conv2d(self.in_chans, encoder_hidden_dim, 1, bias=True)
# encoder1 = nn.Conv2d(encoder_hidden_dim, self.embed_dim, 1, bias=False)
# encoder_act = nn.GELU()
# self.encoder = nn.Sequential(encoder0, encoder_act, encoder1, norm_layer0())
self.encoder = MLP(
in_features=self.in_chans,
hidden_features=encoder_hidden_dim,
out_features=self.embed_dim,
output_bias=False,
act_layer=encoder_act,
drop_rate=0.0,
checkpointing=checkpointing,
)
# self.input_encoding = nn.Conv2d(self.in_chans, self.embed_dim, 1)
# self.pos_embed = nn.Parameter(torch.zeros(1, self.pos_embed_dim, self.img_size[0], self.img_size[1]))
self.pos_embed = nn.Parameter(
torch.zeros(1, self.embed_dim, self.img_size[0], self.img_size[1])
)
# prepare the SHT
modes_lat = int(self.h * self.hard_thresholding_fraction)
modes_lon = int((self.w // 2 + 1) * self.hard_thresholding_fraction)
if self.spectral_transform == "sht":
self.trans_down = SafeRealSHT(
*self.img_size, lmax=modes_lat, mmax=modes_lon, grid="equiangular"
).float()
self.itrans_up = SafeInverseRealSHT(
*self.img_size, lmax=modes_lat, mmax=modes_lon, grid="equiangular"
).float()
self.trans = SafeRealSHT(
self.h, self.w, lmax=modes_lat, mmax=modes_lon, grid="legendre-gauss"
).float()
self.itrans = SafeInverseRealSHT(
self.h, self.w, lmax=modes_lat, mmax=modes_lon, grid="legendre-gauss"
).float()
# we introduce some ad-hoc rescaling of the weights to aid gradient computation:
sht_rescaling_factor = 1e5
self.trans_down.weights = self.trans_down.weights * sht_rescaling_factor
self.itrans_up.pct = self.itrans_up.pct / sht_rescaling_factor
self.trans.weights = self.trans.weights * sht_rescaling_factor
self.itrans.pct = self.itrans.pct / sht_rescaling_factor
elif self.spectral_transform == "fft":
self.trans_down = RealFFT2(
*self.img_size, lmax=modes_lat, mmax=modes_lon
).float()
self.itrans_up = InverseRealFFT2(
*self.img_size, lmax=modes_lat, mmax=modes_lon
).float()
self.trans = RealFFT2(
self.h, self.w, lmax=modes_lat, mmax=modes_lon
).float()
self.itrans = InverseRealFFT2(
self.h, self.w, lmax=modes_lat, mmax=modes_lon
).float()
else:
raise (ValueError("Unknown spectral transform"))
self.blocks = nn.ModuleList([])
for i in range(self.num_layers):
first_layer = i == 0
last_layer = i == self.num_layers - 1
forward_transform = self.trans_down if first_layer else self.trans
inverse_transform = self.itrans_up if last_layer else self.itrans
inner_skip = "linear" if 0 < i < self.num_layers - 1 else None
outer_skip = "identity" if 0 < i < self.num_layers - 1 else None
mlp_mode = self.mlp_mode if not last_layer else "none"
if first_layer:
norm_layer = (norm_layer0, norm_layer1)
elif last_layer:
norm_layer = (norm_layer1, norm_layer0)
else:
norm_layer = (norm_layer1, norm_layer1)
block = FourierNeuralOperatorBlock(
forward_transform,
inverse_transform,
self.embed_dim,
filter_type=self.filter_type,
mlp_ratio=mlp_ratio,
drop_rate=drop_rate,
drop_path=dpr[i],
norm_layer=norm_layer,
sparsity_threshold=sparsity_threshold,
use_complex_kernels=use_complex_kernels,
inner_skip=inner_skip,
outer_skip=outer_skip,
mlp_mode=mlp_mode,
compression=self.compression,
rank=self.rank,
complex_network=self.complex_network,
complex_activation=self.complex_activation,
spectral_layers=self.spectral_layers,
checkpointing=self.checkpointing,
)
self.blocks.append(block)
# DECODER is also an MLP
decoder_hidden_dim = self.embed_dim
decoder_act = nn.GELU
# decoder0 = nn.Conv2d(self.embed_dim + self.big_skip*self.in_chans, decoder_hidden_dim, 1, bias=True)
# decoder1 = nn.Conv2d(decoder_hidden_dim, self.out_chans, 1, bias=False)
# decoder_act = nn.GELU()
# self.decoder = nn.Sequential(decoder0, decoder_act, decoder1)
self.decoder = MLP(
in_features=self.embed_dim + self.big_skip * self.in_chans,
hidden_features=decoder_hidden_dim,
out_features=self.out_chans,
output_bias=False,
act_layer=decoder_act,
drop_rate=0.0,
checkpointing=checkpointing,
)
trunc_normal_(self.pos_embed, std=0.02)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear) or isinstance(m, nn.Conv2d):
trunc_normal_(m.weight, std=0.02)
# nn.init.normal_(m.weight, std=0.02)
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.LayerNorm) or isinstance(m, FusedLayerNorm):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
@torch.jit.ignore
def no_weight_decay(self):
return {"pos_embed", "cls_token"}
def forward_features(self, x):
# x = x + self.pos_embed
x = self.pos_drop(x)
for blk in self.blocks:
x = blk(x)
return x
def forward(self, x):
# save big skip
if self.big_skip:
residual = x
# encoder
x = self.encoder(x)
# do positional embedding
x = x + self.pos_embed
# forward features
x = self.forward_features(x)
# concatenate the big skip
if self.big_skip:
x = torch.cat((x, residual), dim=1)
# decoder
x = self.decoder(x)
return x