yzt15806542928 commited on
Commit
f1d3656
·
verified ·
1 Parent(s): a7a53d9

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. .gitignore +8 -0
  3. README.md +171 -0
  4. conf/config.yaml +13 -0
  5. configuration.json +1 -0
  6. model/__init__.py +3 -0
  7. model/fake_data.py +23 -0
  8. model/tiny_atmorep.py +132 -0
  9. resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml105.bin +3 -0
  10. resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml114.bin +3 -0
  11. resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml123.bin +3 -0
  12. resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml137.bin +3 -0
  13. resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml96.bin +3 -0
  14. resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod +3 -0
  15. resources/id4nvwbetz/model_id4nvwbetz.json +1 -0
  16. scripts/download_official_resources.sh +20 -0
  17. scripts/inference.py +57 -0
  18. scripts/result.py +46 -0
  19. scripts/train.py +50 -0
  20. vendor/atmorep-official/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  21. vendor/atmorep-official/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  22. vendor/atmorep-official/.github/ISSUE_TEMPLATE/scientific-discussion.md +20 -0
  23. vendor/atmorep-official/.gitignore +167 -0
  24. vendor/atmorep-official/CITATION.cff +56 -0
  25. vendor/atmorep-official/LICENSE +21 -0
  26. vendor/atmorep-official/README.md +200 -0
  27. vendor/atmorep-official/atmorep/__init__.py +0 -0
  28. vendor/atmorep-official/atmorep/config/__init__.py +0 -0
  29. vendor/atmorep-official/atmorep/config/config.py +15 -0
  30. vendor/atmorep-official/atmorep/core/__init__.py +0 -0
  31. vendor/atmorep-official/atmorep/core/atmorep_model.py +590 -0
  32. vendor/atmorep-official/atmorep/core/evaluate.py +74 -0
  33. vendor/atmorep-official/atmorep/core/evaluator.py +240 -0
  34. vendor/atmorep-official/atmorep/core/train.py +251 -0
  35. vendor/atmorep-official/atmorep/core/train_multi.py +277 -0
  36. vendor/atmorep-official/atmorep/core/trainer.py +863 -0
  37. vendor/atmorep-official/atmorep/datasets/__init__.py +0 -0
  38. vendor/atmorep-official/atmorep/datasets/data_writer.py +181 -0
  39. vendor/atmorep-official/atmorep/datasets/multifield_data_sampler.py +371 -0
  40. vendor/atmorep-official/atmorep/datasets/normalizer.py +82 -0
  41. vendor/atmorep-official/atmorep/tests/__init__.py +1 -0
  42. vendor/atmorep-official/atmorep/tests/conftest.py +8 -0
  43. vendor/atmorep-official/atmorep/tests/test_utils.py +77 -0
  44. vendor/atmorep-official/atmorep/tests/validation_test.py +126 -0
  45. vendor/atmorep-official/atmorep/training/__init__.py +0 -0
  46. vendor/atmorep-official/atmorep/training/bert.py +228 -0
  47. vendor/atmorep-official/atmorep/transformer/__init__.py +0 -0
  48. vendor/atmorep-official/atmorep/transformer/axial_attention.py +329 -0
  49. vendor/atmorep-official/atmorep/transformer/decoder.py +82 -0
  50. vendor/atmorep-official/atmorep/transformer/interformer.py +52 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.pdf
4
+ result/
5
+ weight/*
6
+ !weight/.gitkeep
7
+ .pytest_cache/
8
+ .ipynb_checkpoints/
README.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ language:
4
+ - en
5
+ - zh
6
+ tags:
7
+ - OneScience
8
+ - Earth Science
9
+ - Weather Forecasting
10
+ - ERA5
11
+ - Representation Learning
12
+ frameworks: PyTorch
13
+ datasets:
14
+ - OneScience/ERA5
15
+ ---
16
+
17
+ <p align="center">
18
+ <strong><span style="font-size: 30px;">AtmoRep</span></strong>
19
+ </p>
20
+
21
+ # Model Overview
22
+
23
+ AtmoRep is a stochastic atmospheric dynamics model based on large-scale representation learning that captures the distribution of atmospheric states through masked-token training and ensemble outputs.
24
+
25
+ Paper: *AtmoRep: A stochastic model of atmosphere dynamics using large scale representation learning*
26
+
27
+ https://arxiv.org/abs/2308.13280
28
+
29
+ # Model Description
30
+
31
+ This directory retains the official vorticity single-field model weights, configuration, and normalization, and provides a tiny AtmoRep-style model for local training and inference verification.
32
+
33
+ # Use Cases
34
+
35
+ | Scenario | Description |
36
+ | :---: | :--- |
37
+ | Official Resource Validation | Load official `.mod` weights and verify configuration. |
38
+ | Local Rapid Verification | Run masked-token training and inference with the tiny model. |
39
+ | ERA5 Atmospheric Representation Learning | Subsequently interface with official GRIB or Zarr data. |
40
+
41
+ # Usage
42
+
43
+ ## 1. OneCode
44
+
45
+ [Click to experience intelligent one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
46
+
47
+ ## 2. Manual Installation & Usage
48
+
49
+ **Hardware Requirements**
50
+
51
+ - The tiny model runs on CPU.
52
+ - GPU is recommended for the official model and real-data inference.
53
+
54
+ ### Download the Model Package
55
+
56
+ ```bash
57
+ hf download --model OneScience-Group/AtmoRep --local-dir ./AtmoRep
58
+ cd AtmoRep
59
+ ```
60
+
61
+ ### Set Up the Runtime Environment
62
+
63
+ **DCU Environment**
64
+
65
+ ```bash
66
+ conda create -n onescience311 python=3.11 -y
67
+ conda activate onescience311
68
+ pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
69
+ ```
70
+
71
+ **GPU Environment**
72
+
73
+ ```bash
74
+ conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
75
+ conda activate onescience311
76
+ pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
77
+ ```
78
+
79
+ If dependencies are missing from the official paths, install them with:
80
+
81
+ ```bash
82
+ pip install zarr wandb cfgrib xarray dask netCDF4 torchinfo
83
+ ```
84
+
85
+ ### Weights & Data
86
+
87
+ The current directory preserves:
88
+
89
+ ```text
90
+ resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod
91
+ resources/id4nvwbetz/model_id4nvwbetz.json
92
+ resources/data/normalization/vorticity/
93
+ ```
94
+
95
+ Before re-downloading official resources please note: the current vendor snapshot is not a Git checkout, and the existing download script cannot re-clone into a non-empty `vendor/atmorep-official`. The resources bundled with this package do not require re-downloading; the script is only suitable for an empty target directory.
96
+
97
+ ```bash
98
+ bash scripts/download_official_resources.sh .
99
+ ```
100
+
101
+ ### Tiny Training
102
+
103
+ ```bash
104
+ python scripts/train.py
105
+ ```
106
+
107
+ This command performs multi-epoch masked-token training using independent train/validation fake Datasets, including DataLoader, AdamW, validation, learning rate scheduling, early stopping, best/latest checkpointing, and training history. Default parameters reside in `conf/config.yaml`.
108
+
109
+ Resuming training:
110
+
111
+ ```bash
112
+ python scripts/train.py --resume weight/training/latest.pth --epochs 20
113
+ ```
114
+
115
+ Training artifacts are `weight/training/latest.pth`, `best.pth`, and `history.json`; the inference-compatible weights `weight/tiny_atmorep.pth` are also updated. Each Dataset sample consists of `fields [T,V,H,W]` and a non-empty `mask [N]`; train/validation use different seeds. This is a complete training pipeline for the tiny model, not a reproduction of the paper's 3.5-billion-parameter official model training.
116
+
117
+ ### Tiny Inference
118
+
119
+ ```bash
120
+ python scripts/inference.py
121
+ ```
122
+
123
+ Inference results are saved as:
124
+
125
+ ```text
126
+ result/prediction.pt
127
+ result/target.pt
128
+ ```
129
+
130
+ These include the ensemble, ensemble mean, ensemble std, and mask.
131
+
132
+ ### Result Inspection
133
+
134
+ ```bash
135
+ python scripts/result.py
136
+ ```
137
+
138
+ This command generates `result/metrics.json` and `result/comparison.png`. The reported metrics are ensemble/mean/spread RMSE in normalized token space, not the paper's physical-unit RMSE, ACC, CRPS, or spread-skill.
139
+
140
+ ### Paper vs. Current Implementation I/O
141
+
142
+ | Item | Paper / Official Model | Tiny Smoke Model |
143
+ | --- | --- | --- |
144
+ | Input | ERA5 local 4D neighborhoods, 5 mode levels, multiple physical fields | `[B,4,1,8,8]` single-field random tensor |
145
+ | Token | Variable-correlated 4D tokens with absolute space-time and level conditioning | `1×4×4` patches, 16 tokens, relative coordinates with single-level conditioning |
146
+ | Output | Multi-head ensembles supporting reconstruction, nowcasting, and interpolation | 4-member masked-token ensemble |
147
+ | Training | Large-scale masked-token distribution learning | Multi-epoch Dataset training with independent validation and checkpoint resumption |
148
+ | Weights | `resources/id4nvwbetz` official vorticity weights | `weight/tiny_atmorep.pth`; the two are mutually incompatible |
149
+
150
+ The complete tiny execution flow is `train.py -> inference.py -> result.py`. The result analysis reads `weight/training/history.json` when present; inference output includes `ensemble`, `ensemble_mean`, `ensemble_std`, `mask`, and `target`. Random data is generated by the Dataset by index; there is currently no independent fake dataset that can be used with the official Zarr sampler. The model package is distributed without local training weights or `result/` artifacts — these are created at the paths described above after running the commands.
151
+
152
+ ### Official Real-Data Inference
153
+
154
+ A directly runnable official real-data inference command is not yet available. It further requires ERA5 vorticity GRIB/Zarr data, the ecCodes environment, and parameterization of the file paths in the official `evaluate.py`.
155
+
156
+ ### Real Data
157
+
158
+ The official vorticity model requires ERA5 vorticity, model levels 96/105/114/123/137, hourly temporal axes, and 0.25° global GRIB/Zarr data.
159
+
160
+ # OneScience Official Information
161
+
162
+ | Platform | OneScience Main Repository | Skills Repository |
163
+ | --- | --- | --- |
164
+ | Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
165
+ | GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
166
+
167
+ # Citation & License
168
+
169
+ - Official code is MIT License.
170
+ - Official model weights are declared CC BY 4.0.
171
+ - ERA5 is subject to Copernicus/ECMWF data terms.
conf/config.yaml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ training:
2
+ epochs: 10
3
+ batch_size: 8
4
+ train_samples: 64
5
+ validation_samples: 16
6
+ learning_rate: 0.001
7
+ weight_decay: 0.000001
8
+ mask_fraction: 0.25
9
+ patience: 3
10
+ seed: 2026
11
+ num_workers: 0
12
+ device: cpu
13
+ checkpoint_dir: weight/training
configuration.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"framework":"Pytorch","task":"atmospheric-representation-learning","implementation":"tiny-smoke-and-official-resources"}
model/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .tiny_atmorep import TinyAtmoRep, ensemble_statistical_loss
2
+
3
+ __all__ = ["TinyAtmoRep", "ensemble_statistical_loss"]
model/fake_data.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic fake dataset for the tiny AtmoRep training pipeline."""
2
+
3
+ import torch
4
+ from torch.utils.data import Dataset
5
+
6
+ from .tiny_atmorep import TinyAtmoRepConfig
7
+
8
+
9
+ class FakeAtmoRepDataset(Dataset):
10
+ def __init__(self, config: TinyAtmoRepConfig, samples: int, seed: int, mask_fraction: float) -> None:
11
+ self.config, self.samples, self.seed = config, samples, seed
12
+ self.num_tokens = (config.input_shape[0] // config.patch_shape[0]) * (config.input_shape[2] // config.patch_shape[1]) * (config.input_shape[3] // config.patch_shape[2])
13
+ self.mask_count = max(1, round(self.num_tokens * mask_fraction))
14
+
15
+ def __len__(self):
16
+ return self.samples
17
+
18
+ def __getitem__(self, index):
19
+ generator = torch.Generator().manual_seed(self.seed + index)
20
+ fields = torch.randn(self.config.input_shape, generator=generator)
21
+ mask = torch.zeros(self.num_tokens, dtype=torch.bool)
22
+ mask[torch.randperm(self.num_tokens, generator=generator)[:self.mask_count]] = True
23
+ return fields, mask
model/tiny_atmorep.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Independent tiny single-field AtmoRep-style fallback for pipeline validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import asdict, dataclass
7
+
8
+ import torch
9
+ from torch import Tensor, nn
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class TinyAtmoRepConfig:
14
+ input_shape: tuple[int, int, int, int] = (4, 1, 8, 8)
15
+ patch_shape: tuple[int, int, int] = (1, 4, 4)
16
+ embed_dim: int = 32
17
+ num_heads: int = 4
18
+ num_layers: int = 2
19
+ ensemble_size: int = 4
20
+
21
+ def to_dict(self) -> dict:
22
+ return asdict(self)
23
+
24
+
25
+ class TinyAtmoRep(nn.Module):
26
+ """Masked-token transformer with four-dimensional token conditioning.
27
+
28
+ Inputs use ``[batch, time, variable, latitude, longitude]``. This fallback
29
+ intentionally supports one field only; level is supplied as token metadata.
30
+ """
31
+
32
+ def __init__(self, config: TinyAtmoRepConfig | None = None) -> None:
33
+ super().__init__()
34
+ self.config = config or TinyAtmoRepConfig()
35
+ time, variables, height, width = self.config.input_shape
36
+ pt, ph, pw = self.config.patch_shape
37
+ if variables != 1:
38
+ raise ValueError("TinyAtmoRep is a single-field fallback (V must equal 1)")
39
+ if time % pt or height % ph or width % pw:
40
+ raise ValueError("input_shape must be divisible by patch_shape")
41
+
42
+ self.grid_shape = (time // pt, height // ph, width // pw)
43
+ self.patch_dim = pt * ph * pw
44
+ self.patch_embed = nn.Conv3d(
45
+ 1, self.config.embed_dim, kernel_size=self.config.patch_shape,
46
+ stride=self.config.patch_shape,
47
+ )
48
+ self.condition_embed = nn.Sequential(
49
+ nn.Linear(4, self.config.embed_dim), nn.GELU(),
50
+ nn.Linear(self.config.embed_dim, self.config.embed_dim),
51
+ )
52
+ self.mask_token = nn.Parameter(torch.zeros(1, 1, self.config.embed_dim))
53
+ layer = nn.TransformerEncoderLayer(
54
+ d_model=self.config.embed_dim,
55
+ nhead=self.config.num_heads,
56
+ dim_feedforward=4 * self.config.embed_dim,
57
+ dropout=0.0,
58
+ activation="gelu",
59
+ batch_first=True,
60
+ norm_first=True,
61
+ )
62
+ self.encoder = nn.TransformerEncoder(layer, self.config.num_layers)
63
+ self.ensemble_heads = nn.ModuleList(
64
+ nn.Linear(self.config.embed_dim, self.patch_dim)
65
+ for _ in range(self.config.ensemble_size)
66
+ )
67
+ nn.init.normal_(self.mask_token, std=0.02)
68
+
69
+ @property
70
+ def num_tokens(self) -> int:
71
+ return math.prod(self.grid_shape)
72
+
73
+ def token_conditions(self, batch_size: int, level: float, device: torch.device) -> Tensor:
74
+ """Return normalized [time, level, latitude, longitude] per token."""
75
+ nt, nh, nw = self.grid_shape
76
+ axes = [torch.linspace(-1.0, 1.0, n, device=device) for n in (nt, nh, nw)]
77
+ time, latitude, longitude = torch.meshgrid(*axes, indexing="ij")
78
+ model_level = torch.full_like(time, float(level) / 137.0)
79
+ conditions = torch.stack((time, model_level, latitude, longitude), dim=-1)
80
+ return conditions.reshape(1, self.num_tokens, 4).expand(batch_size, -1, -1)
81
+
82
+ def tokenize(self, fields: Tensor) -> Tensor:
83
+ self._validate_fields(fields)
84
+ volume = fields.permute(0, 2, 1, 3, 4)
85
+ pt, ph, pw = self.config.patch_shape
86
+ patches = volume.unfold(2, pt, pt).unfold(3, ph, ph).unfold(4, pw, pw)
87
+ return patches.permute(0, 2, 3, 4, 1, 5, 6, 7).reshape(
88
+ fields.shape[0], self.num_tokens, self.patch_dim
89
+ )
90
+
91
+ def forward(self, fields: Tensor, mask: Tensor, level: float = 137.0) -> Tensor:
92
+ self._validate_fields(fields)
93
+ if mask.shape != (fields.shape[0], self.num_tokens) or mask.dtype != torch.bool:
94
+ raise ValueError(f"mask must be bool [B, {self.num_tokens}]")
95
+ tokens = self.patch_embed(fields.permute(0, 2, 1, 3, 4)).flatten(2).transpose(1, 2)
96
+ conditions = self.token_conditions(fields.shape[0], level, fields.device)
97
+ tokens = tokens + self.condition_embed(conditions)
98
+ tokens = torch.where(mask.unsqueeze(-1), self.mask_token.expand_as(tokens), tokens)
99
+ encoded = self.encoder(tokens)
100
+ return torch.stack([head(encoded) for head in self.ensemble_heads], dim=1)
101
+
102
+ def _validate_fields(self, fields: Tensor) -> None:
103
+ expected = self.config.input_shape
104
+ if fields.ndim != 5 or tuple(fields.shape[1:]) != expected:
105
+ raise ValueError(f"fields must have shape [B, {expected}], got {tuple(fields.shape)}")
106
+
107
+
108
+ def ensemble_statistical_loss(
109
+ predictions: Tensor,
110
+ targets: Tensor,
111
+ mask: Tensor,
112
+ statistical_weight: float = 0.1,
113
+ ) -> tuple[Tensor, dict[str, Tensor]]:
114
+ """Combine masked ensemble MSE with ensemble mean/spread statistics."""
115
+ if predictions.ndim != 4 or targets.ndim != 3:
116
+ raise ValueError("predictions must be [B,E,N,P] and targets [B,N,P]")
117
+ selected = mask[:, None, :, None].expand_as(predictions)
118
+ expanded_targets = targets[:, None].expand_as(predictions)
119
+ ensemble_mse = (predictions[selected] - expanded_targets[selected]).square().mean()
120
+
121
+ ensemble_mean = predictions.mean(dim=1)
122
+ ensemble_std = predictions.std(dim=1, unbiased=False)
123
+ target_std = targets.std(dim=-1, unbiased=False, keepdim=True).expand_as(targets)
124
+ masked = mask.unsqueeze(-1).expand_as(targets)
125
+ mean_loss = (ensemble_mean[masked] - targets[masked]).square().mean()
126
+ spread_loss = (ensemble_std[masked] - target_std[masked]).square().mean()
127
+ stats_loss = mean_loss + spread_loss
128
+ total = ensemble_mse + statistical_weight * stats_loss
129
+ return total, {
130
+ "ensemble_mse": ensemble_mse.detach(),
131
+ "statistical": stats_loss.detach(),
132
+ }
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml105.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6d509500b9b870f44f12e998e7e50ab3c5cb0646867d5c2789b63af51ad0836b
3
+ size 8640
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml114.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2fac57f8d31533a7a4c825e2240c6751a816be93ab79ebff7007368749b537e
3
+ size 8640
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml123.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c5f5d843b9bbf5d876d1faa287ba09fa2556ecf9b3437bc6289f159d34a288de
3
+ size 8640
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml137.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a980bfa8920ae1ddeca783852811b1d49aad80de695e57baa5dcab1b35249e0f
3
+ size 8640
resources/data/normalization/vorticity/global_normalization_mean_var_vorticity_ml96.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e4344b3f123303183e2a2af5d58ab001982406225256e75ca158153a0d41f5a7
3
+ size 8640
resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fec447ac33bf6774dc552689199822f2803d772783a473a9452ce558c5d4ff55
3
+ size 2719496689
resources/id4nvwbetz/model_id4nvwbetz.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"num_accs_per_task": 1, "with_hvd": true, "hvd_rank": 0, "hvd_size": 16, "back_passes_per_step": 4, "comment": "", "file_format": "grib", "data_dir": "../data/", "level_type": "ml", "fields": [["vorticity", [1, 2048, [], 0], [96, 105, 114, 123, 137], [12, 6, 12], [3, 9, 9], [0.5, 0.9, 0.1, 0.05]]], "fields_prediction": [["vorticity", 1.0]], "fields_targets": [], "years_train": [1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017], "years_test": [2018], "month": null, "geo_range_sampling": [[-90.0, 90.0], [0.0, 360.0]], "time_sampling": 1, "data_smoothing": 0, "file_shape": [-1, 721, 1440], "num_t_samples": 744, "num_patches_per_t_train": 4, "num_patches_per_t_test": 1, "torch_seed": 5371822430435701752, "batch_size_test": 24, "batch_size_start": 6, "batch_size_max": 6, "batch_size_delta": 8, "num_epochs": 2048, "num_files_train": 2, "num_files_test": 2, "loader_num_workers": 8, "size_token_info": 8, "size_token_info_net": 16, "grad_checkpointing": true, "with_cls": false, "with_layernorm": true, "coupling_num_heads_per_field": 1, "dropout_rate": 0.05, "learnable_mask": false, "with_qk_lnorm": true, "num_neighborhoods": 1, "encoder_num_layers": 10, "encoder_num_heads": 16, "encoder_num_mlp_layers": 2, "encoder_att_type": "dense", "decoder_num_layers": 10, "decoder_num_heads": 16, "decoder_num_mlp_layers": 2, "decoder_self_att": false, "decoder_cross_att_ratio": 0.5, "decoder_cross_att_rate": 1.0, "decoder_att_type": "dense", "net_tail_num_nets": 16, "net_tail_num_layers": 0, "ensemble_weighted": false, "ensemble_weighted_T": 50.0, "losses": ["mse_ensemble", "stats"], "lr_start": 1e-05, "lr_max": 8e-05, "lr_min": 6.000000000000001e-05, "weight_decay": 0.025, "lr_decay_rate": 1.025, "lr_start_epochs": 3, "BERT_strategy": "BERT", "BERT_window": false, "BERT_fields_synced": false, "BERT_mr_max": 2, "log_test_num_ranks": 0, "save_grads": false, "profile": false, "test_initial": true, "rng_seed": null, "with_wandb": true, "slurm_job_id": "7749847", "wandb_id": "4nvwbetz", "lat_sampling_weighted": true}
scripts/download_official_resources.sh ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="${1:?usage: download_official_resources.sh ROOT_DIR}"
5
+ COMMIT="055f858e68f5e0151eb6fece9f6b574d3da4af8d"
6
+ REPOSITORY="https://github.com/clessig/atmorep.git"
7
+ MODEL_URL="https://datapub.fz-juelich.de/atmorep/models/model_id4nvwbetz.tar.gz"
8
+
9
+ mkdir -p "$ROOT/vendor" "$ROOT/resources"
10
+ if [[ ! -d "$ROOT/vendor/atmorep-official/.git" ]]; then
11
+ git clone --no-checkout "$REPOSITORY" "$ROOT/vendor/atmorep-official"
12
+ fi
13
+ git -C "$ROOT/vendor/atmorep-official" fetch origin "$COMMIT"
14
+ git -C "$ROOT/vendor/atmorep-official" checkout --detach "$COMMIT"
15
+ curl -fL --retry 3 --continue-at - -o "$ROOT/resources/model_id4nvwbetz.tar.gz" "$MODEL_URL"
16
+ gzip -t "$ROOT/resources/model_id4nvwbetz.tar.gz"
17
+ tar -xzf "$ROOT/resources/model_id4nvwbetz.tar.gz" -C "$ROOT/resources"
18
+ sha256sum "$ROOT/resources/model_id4nvwbetz.tar.gz" \
19
+ "$ROOT/resources/id4nvwbetz/AtmoRep_id4nvwbetz.mod" \
20
+ "$ROOT/resources/id4nvwbetz/model_id4nvwbetz.json"
scripts/inference.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import torch
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+ sys.path.insert(0, str(ROOT))
13
+
14
+ from model.tiny_atmorep import TinyAtmoRep, TinyAtmoRepConfig
15
+
16
+
17
+ def main() -> None:
18
+ parser = argparse.ArgumentParser()
19
+ parser.add_argument("--checkpoint", type=Path, default=ROOT / "weight" / "tiny_atmorep.pth")
20
+ parser.add_argument("--output", type=Path, default=ROOT / "result" / "prediction.pt")
21
+ parser.add_argument("--seed", type=int, default=17)
22
+ args = parser.parse_args()
23
+
24
+ payload = torch.load(args.checkpoint, map_location="cpu", weights_only=True)
25
+ config = TinyAtmoRepConfig(**payload["config"])
26
+ model = TinyAtmoRep(config)
27
+ model.load_state_dict(payload["model"])
28
+ model.eval()
29
+ torch.manual_seed(args.seed)
30
+ fields = torch.randn(1, *config.input_shape)
31
+ mask = torch.zeros(1, model.num_tokens, dtype=torch.bool)
32
+ mask[:, 1::4] = True
33
+ with torch.inference_mode():
34
+ ensemble = model(fields, mask, level=137.0)
35
+ target = model.tokenize(fields)
36
+ result = {
37
+ "ensemble": ensemble,
38
+ "ensemble_mean": ensemble.mean(dim=1),
39
+ "ensemble_std": ensemble.std(dim=1, unbiased=False),
40
+ "mask": mask,
41
+ "target": target,
42
+ "input_shape": tuple(fields.shape),
43
+ }
44
+ args.output.parent.mkdir(parents=True, exist_ok=True)
45
+ torch.save(target, args.output.parent / "target.pt")
46
+ torch.save(result, args.output)
47
+ print(json.dumps({
48
+ "output": str(args.output),
49
+ "ensemble_shape": list(ensemble.shape),
50
+ "mean_shape": list(result["ensemble_mean"].shape),
51
+ "finite": bool(torch.isfinite(ensemble).all()),
52
+ "bytes": args.output.stat().st_size,
53
+ }, indent=2))
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
scripts/result.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Show the saved tiny AtmoRep training and inference summaries."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import torch
7
+ import matplotlib
8
+ matplotlib.use("Agg")
9
+ import matplotlib.pyplot as plt
10
+
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+
14
+
15
+ if __name__ == "__main__":
16
+ history_path = ROOT / "weight" / "training" / "history.json"
17
+ train_history = json.loads(history_path.read_text()) if history_path.exists() else []
18
+ prediction = torch.load(ROOT / "result" / "prediction.pt", map_location="cpu", weights_only=True)
19
+ target = torch.load(ROOT / "result" / "target.pt", map_location="cpu", weights_only=True)
20
+ ensemble = prediction["ensemble"]
21
+ target = target["target"] if isinstance(target, dict) else target
22
+ mean = ensemble.mean(dim=1)
23
+ std = ensemble.std(dim=1, unbiased=False)
24
+ ensemble_rmse = torch.sqrt((ensemble - target[:, None]).square().mean())
25
+ mean_rmse = torch.sqrt((mean - target).square().mean())
26
+ target_std = target.std(dim=-1, unbiased=False, keepdim=True).expand_as(target)
27
+ spread_rmse = torch.sqrt((std - target_std).square().mean())
28
+ summary = {
29
+ "train_history": train_history,
30
+ "ensemble_shape": list(ensemble.shape),
31
+ "target_shape": list(target.shape),
32
+ "ensemble_rmse": float(ensemble_rmse),
33
+ "mean_rmse": float(mean_rmse),
34
+ "spread_rmse": float(spread_rmse),
35
+ "finite": bool(torch.isfinite(ensemble).all()),
36
+ }
37
+ (ROOT / "result" / "metrics.json").write_text(json.dumps(summary, indent=2) + "\n")
38
+ fig, axes = plt.subplots(1, 4, figsize=(12, 3))
39
+ for ax, data, title in zip(axes, (target[0, 0], mean[0, 0], (mean - target)[0, 0].abs(), std[0, 0]), ("Target", "Mean", "Abs error", "Spread")):
40
+ image = ax.imshow(data.reshape(4, 4).numpy(), cmap="viridis")
41
+ ax.set_title(title)
42
+ plt.colorbar(image, ax=ax, shrink=0.75)
43
+ plt.tight_layout()
44
+ plt.savefig(ROOT / "result" / "comparison.png", dpi=150)
45
+ plt.close()
46
+ print(json.dumps(summary, indent=2))
scripts/train.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Train tiny AtmoRep with train/validation epochs and resumable checkpoints."""
2
+
3
+ import argparse, json, random, sys
4
+ from pathlib import Path
5
+ import numpy as np
6
+ import torch, yaml
7
+ from torch.utils.data import DataLoader
8
+
9
+ ROOT = Path(__file__).resolve().parents[1]; sys.path.insert(0, str(ROOT))
10
+ from model.fake_data import FakeAtmoRepDataset
11
+ from model.tiny_atmorep import TinyAtmoRep, TinyAtmoRepConfig, ensemble_statistical_loss
12
+
13
+
14
+ def args():
15
+ p0=argparse.ArgumentParser(add_help=False); p0.add_argument("--config",type=Path,default=ROOT/"conf/config.yaml"); k,_=p0.parse_known_args(); c=yaml.safe_load(k.config.read_text())["training"]
16
+ p=argparse.ArgumentParser(parents=[p0]);
17
+ for name, typ in (("epochs", int), ("batch-size", int), ("train-samples", int), ("validation-samples", int), ("seed", int), ("patience", int), ("num-workers", int), ("learning-rate", float), ("weight-decay", float), ("mask-fraction", float)):
18
+ p.add_argument("--" + name, type=typ, default=c[name.replace("-", "_")])
19
+ p.add_argument("--checkpoint-dir",type=Path,default=ROOT/c["checkpoint_dir"]); p.add_argument("--resume",type=Path); p.add_argument("--device",choices=("cpu","cuda"),default=c["device"]); return p.parse_args()
20
+
21
+
22
+ def run_epoch(model, loader, device, optimizer=None):
23
+ model.train(optimizer is not None); totals=[]
24
+ context=torch.enable_grad() if optimizer else torch.inference_mode()
25
+ with context:
26
+ for fields,mask in loader:
27
+ fields,mask=fields.to(device),mask.to(device); pred=model(fields,mask,level=137.0); target=model.tokenize(fields); loss,_=ensemble_statistical_loss(pred,target,mask)
28
+ if optimizer: optimizer.zero_grad(set_to_none=True); loss.backward(); torch.nn.utils.clip_grad_norm_(model.parameters(),1.0); optimizer.step()
29
+ totals.append(float(loss.detach()))
30
+ return float(np.mean(totals))
31
+
32
+
33
+ def main():
34
+ a=args(); device=torch.device(a.device); random.seed(a.seed); np.random.seed(a.seed); torch.manual_seed(a.seed); a.checkpoint_dir.mkdir(parents=True,exist_ok=True)
35
+ config=TinyAtmoRepConfig(); model=TinyAtmoRep(config).to(device); opt=torch.optim.AdamW(model.parameters(),lr=a.learning_rate,weight_decay=a.weight_decay); sched=torch.optim.lr_scheduler.ReduceLROnPlateau(opt,patience=max(1,a.patience//2),factor=.5)
36
+ train=DataLoader(FakeAtmoRepDataset(config,a.train_samples,a.seed,a.mask_fraction),batch_size=a.batch_size,shuffle=True,num_workers=a.num_workers); val=DataLoader(FakeAtmoRepDataset(config,a.validation_samples,a.seed+100000,a.mask_fraction),batch_size=a.batch_size,num_workers=a.num_workers)
37
+ start=0; best=float("inf"); history=[]
38
+ if a.resume:
39
+ q=torch.load(a.resume,map_location=device,weights_only=False); model.load_state_dict(q["model"]); opt.load_state_dict(q["optimizer"]); sched.load_state_dict(q["scheduler"]); start=q["epoch"]+1; best=q["best_val_loss"]; history=q["history"]
40
+ stale=0
41
+ for epoch in range(start,a.epochs):
42
+ tr=run_epoch(model,train,device,opt); va=run_epoch(model,val,device); sched.step(va); row={"epoch":epoch,"train_loss":tr,"validation_loss":va,"learning_rate":opt.param_groups[0]["lr"]}; history.append(row); improved=va<best; best=min(best,va); stale=0 if improved else stale+1
43
+ train_config = {key: str(value) if isinstance(value, Path) else value for key, value in vars(a).items()}
44
+ payload={"model":model.state_dict(),"optimizer":opt.state_dict(),"scheduler":sched.state_dict(),"config":config.to_dict(),"epoch":epoch,"step":len(train)*(epoch+1),"best_val_loss":best,"history":history,"train_config":train_config}
45
+ torch.save(payload,a.checkpoint_dir/"latest.pth"); torch.save(payload,ROOT/"weight/tiny_atmorep.pth")
46
+ if improved: torch.save(payload,a.checkpoint_dir/"best.pth")
47
+ (a.checkpoint_dir/"history.json").write_text(json.dumps(history,indent=2)+"\n"); print(json.dumps(row))
48
+ if stale>=a.patience: break
49
+ print(json.dumps({"status":"completed","epochs_completed":len(history),"best_validation_loss":best},indent=2))
50
+ if __name__=="__main__": main()
vendor/atmorep-official/.github/ISSUE_TEMPLATE/bug_report.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: "[BUG]"
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Activate '...'
16
+ 2. Change '....'
17
+ 3. Run '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Hardware and environment:**
27
+ - System: [e.g. ATOS, LUMI, JSC]
28
+ - Runtime: [e.g. interactive, sbatch]
29
+ - Special branch: [e.g. develop, main]
30
+ - Python Version: [e.g. 3.11]
31
+
32
+ **Additional context**
33
+ Add any other context about the problem here.
vendor/atmorep-official/.github/ISSUE_TEMPLATE/feature_request.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: "[FEATURE]"
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen. Please use bullet points and action items when possible
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
vendor/atmorep-official/.github/ISSUE_TEMPLATE/scientific-discussion.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Scientific discussion
3
+ about: discuss scientific developments
4
+ title: "[SCIENTIFIC]"
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the topic**
11
+ A clear and concise description of the scientific topic you want to discuss.
12
+
13
+ **Describe the technical developments**
14
+ A clear and concise description of the technical advancements needed to make this happen. Ideally as bullet points
15
+
16
+ **Additional context**
17
+ Add any other context or screenshots about the discussed topic here.
18
+
19
+ ** Related issues**
20
+ link here the issues related to this scientific discussion.
vendor/atmorep-official/.gitignore ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
161
+
162
+ # data files
163
+ *.zarr
164
+
165
+ # image files
166
+ *.png
167
+
vendor/atmorep-official/CITATION.cff ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This CITATION.cff file was generated with cffinit.
2
+ # Visit https://bit.ly/cffinit to generate yours today!
3
+
4
+ cff-version: 1.2.0
5
+ title: Atmorep
6
+ message: >-
7
+ If you use this software, please cite it using the
8
+ metadata from this file.
9
+ type: software
10
+ authors:
11
+ - given-names: Christian
12
+ family-names: Lessig
13
+ email: christian.lessig@ecmwf.int
14
+ affiliation: European Centre for Medium-Range Weather Forecasts (ECMWF)
15
+ - given-names: Ilaria
16
+ family-names: Luise
17
+ email: ilaria.luise@cern.ch
18
+ affiliation: European Organization for Nuclear Research (CERN)
19
+ - given-names: Martin
20
+ family-names: Schultz
21
+ email: m.schultz@fz-juelich.de
22
+ orcid: 'https://orcid.org/0000-0003-3455-774X'
23
+ affiliation: Forschungszentrum Jülich (FZJ)
24
+ - given-names: Michael
25
+ family-names: Langguth
26
+ email: m.langguth@fz-juelich.de
27
+ orcid: 'https://orcid.org/0000-0003-3354-5333'
28
+ affiliation: Forschungszentrum Jülich (FZJ)
29
+ identifiers:
30
+ - type: url
31
+ value: 'https://arxiv.org/abs/2308.13280'
32
+ description: corresponding Preprint
33
+ repository-code: 'https://isggit.cs.uni-magdeburg.de/atmorep/atmorep'
34
+ url: 'https://www.atmorep.org'
35
+ abstract: >-
36
+ AtmoRep is a novel, task-independent stochastic computer
37
+ model of atmospheric dynamics that can provide skillful
38
+ results for a wide range of applications. AtmoRep uses
39
+ large-scale representation learning from artificial
40
+ intelligence to determine a general description of the
41
+ highly complex, stochastic dynamics of the atmosphere
42
+ from the best available estimate of the system's historical
43
+ trajectory as constrained by observations. This is enabled
44
+ by a novel self-supervised learning objective and a unique
45
+ ensemble that samples from the stochastic model with a
46
+ variability informed by the one in the historical record.
47
+ Our work establishes that large-scale neural networks can
48
+ provide skillful, task-independent models of atmospheric
49
+ dynamics. With this, they provide a novel means to make
50
+ the large record of atmospheric observations accessible
51
+ for applications and for scientific inquiry, complementing
52
+ existing simulations based on first principles.
53
+ license: MIT
54
+ commit: b0da5b32ec70295914bbb486dbcb77885671dc45
55
+ version: 2.0 (preprint)
56
+ date-released: '2023-11-28'
vendor/atmorep-official/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Otto-von-Guericke-Universitaet Magdeburg, Forschungszentrum Juelich GmbH, European Organization for Nuclear Research
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.
vendor/atmorep-official/README.md ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # IMPORTANT NOTE:
3
+ <p align="center">
4
+ <img src="https://github.com/user-attachments/assets/50518092-5386-4ef5-98f3-e2abf147556f" />
5
+ </p>
6
+
7
+ Please note that that the folder is **not maintained anymore since March 1st**. \
8
+ Please use the **WeatherGenerator** code instead: https://github.com/ecmwf/WeatherGenerator
9
+
10
+
11
+
12
+ # AtmoRep
13
+
14
+ This repository contains the source code for the [AtmoRep](https://www.atmorep.org) models for large scale representation learning of atmospheric dynamics as well as links to the pre-trained models and the required model input data.
15
+
16
+ The pre-print for the work is available on ArXiv: https://arxiv.org/abs/2308.13280.
17
+
18
+ ```
19
+ @misc{Lessig2023atmorep,
20
+ title = {AtmoRep: A stochastic model of atmosphere dynamics using large scale representation learning},
21
+ author = {Christian Lessig and Ilaria Luise and Bing Gong and Michael Langguth and Scarlet Stadler and Martin Schultz},
22
+ eprint = {2308.13280},
23
+ primaryclass = {physics.ao-ph},
24
+ url = {https://arxiv.org/abs/2308.13280},
25
+ year = {2023},
26
+ ```
27
+
28
+ # Starter README
29
+
30
+ ## 1. Pull code
31
+
32
+ `````
33
+ %> wget git@github.com:clessig/atmorep.git
34
+ `````
35
+ This creates a directory ``atmorep`` with the code that contains the source code including the python scripts for model training and evaluation.
36
+
37
+ After following the steps described below, the final directory structure will look as follows:
38
+ ````
39
+ └── atmorep/
40
+ ├── atmorep/
41
+ │ └── ...
42
+ ├── data/ <- top level data directory
43
+ │ ├── normalisation/ <- directory for data normalisations
44
+ │ ├── vorticity/
45
+ │ │ ├── ml105/ <- model levels with monthly GRIB files
46
+ │ │ │ ├── era5_vorticity_y2021_m03_ml137.grib <- grib data file
47
+ │ │ │ ├── ...
48
+ │ │ ├── ml114/
49
+ │ │ ├── ml123/
50
+ │ │ ├── ml137/
51
+ │ │ ├── ml96/
52
+ . . .
53
+ │ ├── temperature/
54
+ . .
55
+ ├── models
56
+ │ ├── id4nvwbetz <- Directory containing model weights and config
57
+ │ │ ├── model_id4nvwbetz.json
58
+ │ │ └── AtmoRep_id4nvwbetz.mod
59
+ │ ├── id<model_id>
60
+ . .
61
+ └── results
62
+ ├── id4nvwbetz
63
+ ...
64
+ ````
65
+ The directories ``data``, ``models``, and ``results`` need to be created if they do not exist. All directories might be large and should thus be on a directory with sufficient storage space; in this case they can be soft-linked to the default ones above or they can be set in ``atmorep/config/config``.
66
+
67
+ ## 2. Download the data
68
+
69
+ ### 2.1 Download pre-trained models
70
+
71
+ Models can be downloaded from: https://datapub.fz-juelich.de/atmorep/trained-models.html
72
+
73
+ An example for downloading the pre-trained models is given here, in this case for the vorticity model.
74
+
75
+ `````
76
+ % atmorep/> mkdir models
77
+ % atmorep/> cd models
78
+ % atmorep/data/> wget https://datapub.fz-juelich.de/atmorep/models/model_id4nvwbetz.tar.gz
79
+ % atmorep/data/> tar xvzf model_id4nvwbetz.tar.gz
80
+ % atmorep/data/> ls id4nvwbetz
81
+ AtmoRep_id4nvwbetz.mod model_id4nvwbetz.json
82
+ `````
83
+
84
+
85
+ ### 2.2 Download model input data (ERA5)
86
+
87
+ The input data in the required structure can be downloaded from the [Jülich datapub](https://datapub.fz-juelich.de/atmorep/era5-data.html) server. Direct link to WebDAV [https://datapub.fz-juelich.de/atmorep/data/](https://datapub.fz-juelich.de/atmorep/data/). Alternatively, it can be directly downloaded from MARS using the following [script](https://www.atmorep.org/code/mars_era5_download.py).
88
+
89
+ #### Download a subset of files
90
+
91
+ All data files (fields and normalizations) should be downloaded into the ``data`` directory. Un-taring the files will generate the correct folder structure. For example (we will use the vorticity example also below to run the first model so it is recommended to download it as a first step):
92
+ `````
93
+ % atmorep/> mkdir data
94
+ % atmorep/> cd data
95
+ % atmorep/data/> wget https://datapub.fz-juelich.de/atmorep/data/vorticity/ml137/era5_vorticity_y2021_ml137.tar
96
+ % atmorep/data/> tar xvf era5_vorticity_y2021_ml137.tar
97
+ % atmorep/data/> ls -lah vorticity/ml137/
98
+ total 18G
99
+ era5_vorticity_y2021_m01_ml137.grib
100
+ era5_vorticity_y2021_m02_ml137.grib
101
+ ...
102
+ era5_vorticity_y2021_m12_ml137.grib
103
+ `````
104
+ For efficiency reasons, AtmoRep takes monthly ERA5 data as input. Therefore, each tar file contains 12 GRIB files of about 1.5 GBytes each.
105
+
106
+ Coefficients for data normalization per field and level can be downloaded here: https://datapub.fz-juelich.de/atmorep/data/normalization/. They should also be located in the ```data``` directory:
107
+ `````
108
+ % atmorep/data/> wget https://datapub.fz-juelich.de/atmorep/data/normalization/normalization_vorticity_ml137.tar.gz
109
+ % atmorep/data/> tar xvzf normalization_vorticity_ml137.tar.gz
110
+ `````
111
+
112
+ ## 3. Install python packages
113
+
114
+ Create a python environment, e.g.
115
+
116
+ `````
117
+ % atmorep/> python3 -m venv pyenv
118
+ `````
119
+
120
+ and activate the environment:
121
+
122
+ `````
123
+ % atmorep/> source pyenv/bin/activate
124
+ `````
125
+ conda is also possible, no environment is strictly required although we would recommend it. Please make sure to use a recent python version (we tested with python3.10).
126
+ Then install the AtmoRep package:
127
+ `````
128
+ % atmorep/>
129
+ % atmorep/> pip install -e .
130
+ `````
131
+
132
+ torch is currently not included (since it is often available or has particular dependencies, e.g. a specific Cuda version). In the simplest case, it can just be installed by:
133
+
134
+ `````
135
+ % atmorep/> pip install torch
136
+ `````
137
+ We require torch 2.x. (A container solution allows to run even on systems where torch 2.x is not available.)
138
+
139
+ ## 4. Run model:
140
+ Pre-trained models can normally be run by:
141
+ `````
142
+ % atmorep/> python atmorep/core/evaluate.py
143
+ `````
144
+ You can easily adapt the configuration by selecting the corresponding _model_id_ in ``evaluate.py`` (see below). It defaults to the single-field configuration of vorticity, of which we have downloaded the data above.
145
+
146
+ Depending on your compute hardware, you might also have to run the computations by submitting the job using a batch system or allocate a compute node in interactive mode (if an interactive seesion is possible, then this is recommended). If you run an interactive session you will likely need to use the following:
147
+ `````
148
+ % atmorep/> export CUDA_VISIBLE_DEVICES=0,1,2,3
149
+ % atmorep/> MASTER_ADDR="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1)"
150
+ `````
151
+
152
+ The default evaluation mode is currently global forecast. The output will be (similar to) this:
153
+ ````
154
+ devices : ['cuda:0', 'cuda:1', 'cuda:2', 'cuda:3']
155
+ Wandb run: atmorep-ztvyw7k6-8932958
156
+ Running Evaluate.evaluate with mode = global_forecast
157
+ Loaded AtmoRep id=4nvwbetz, ignoring/missing 2 elements.
158
+ Loaded model id = 4nvwbetz at epoch = -2.
159
+ Number of batches per global forecast: 14
160
+ INFO:: data stats vorticity : 5.374998363549821e-05 / 0.9978392720222473
161
+ num_accs_per_task : 1
162
+ with_hvd : True
163
+ hvd_rank : 0
164
+
165
+ ...
166
+
167
+ wandb_id : ztvyw7k6
168
+ dates : [[2021, 2, 10, 12]]
169
+ token_overlap : [0, 0]
170
+ forecast_num_tokens : 1
171
+ validation loss for strategy=forecast at epoch 0 : 0.12402566522359848
172
+ validation loss for vorticity : 0.12402566522359848
173
+ wandb: Waiting for W&B process to finish... (success).
174
+ wandb:
175
+ wandb: Run history:
176
+ wandb: val. loss forecast ▁
177
+ wandb: val., forecast, vorticity ▁
178
+ wandb:
179
+ wandb: Run summary:
180
+ wandb: val. loss forecast 0.12403
181
+ wandb: val., forecast, vorticity 0.12403
182
+ wandb:
183
+ wandb: You can sync this run to the cloud by running:
184
+ wandb: wandb sync /p/project/atmo-rep/lessig/atmorep/atmorep/lessig-cleanup/atmorep/wandb/offline-run-20231124_095428-ztvyw7k6
185
+ ````
186
+ For the vorticity example above, we evaluate with ``global_forecast`` for a specific date and using only a single model level:
187
+ ````
188
+ mode, options = 'global_forecast', { 'fields[0][2]' : [137],
189
+ 'dates' : [ [2021, 2, 10, 12] ],
190
+ 'token_overlap' : [0, 0],
191
+ 'forecast_num_tokens' : 1,
192
+ 'attention' : False}
193
+ ````
194
+ We perform a 3 hour forecast, since 1 token is 3 hours wide. Another mode is the BERT masked token model mode used for pre-training:
195
+ `````
196
+ mode, options = 'BERT', {'years_test' : [2021], 'fields[0][2]' : [123, 137]}
197
+ `````
198
+ Again, we chose some custom options by using two levels instead of the five ones that are default and were used during pre-training and by using 2021 as the test year (since we downloaded the data).
199
+
200
+ The generated model output (stored in ``./results/id{wandbid}``) for the ```global_forecast``` example can be post-processed into a spatial map with the [following code](https://www.atmorep.org/code/plot_forecast.py). The run_id at the top needs to be replaced by the wandb_id of your run, it can be read off from the console output. Results will be stored as ``example_0000{0,1,2}.png``. The code is also an as-simple-as-possible example with many parameters hard-coded, see our analysis code for a proper handling.
vendor/atmorep-official/atmorep/__init__.py ADDED
File without changes
vendor/atmorep-official/atmorep/config/__init__.py ADDED
File without changes
vendor/atmorep-official/atmorep/config/config.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+
4
+ fpath = os.path.dirname(os.path.realpath(__file__))
5
+
6
+ path_models = Path( fpath, '../../models/')
7
+ path_results = Path( fpath, '../../results')
8
+ path_plots = Path( fpath, '../results/plots/')
9
+
10
+ grib_index = { 'vorticity' : 'vo', 'divergence' : 'd', 'geopotential' : 'z',
11
+ 'orography' : 'z', 'temperature': 't', 'specific_humidity' : 'q',
12
+ 'mean_top_net_long_wave_radiation_flux' : 'mtnlwrf',
13
+ 'velocity_u' : 'u', 'velocity_v': 'v', 'velocity_z' : 'w',
14
+ 'total_precip' : 'tp', 'radar_precip' : 'yw_hourly',
15
+ 't2m' : 't_2m', 'u_10m' : 'u_10m', 'v_10m' : 'v_10m', }
vendor/atmorep-official/atmorep/core/__init__.py ADDED
File without changes
vendor/atmorep-official/atmorep/core/atmorep_model.py ADDED
@@ -0,0 +1,590 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import numpy as np
19
+ import code
20
+ import os
21
+ # code.interact(local=locals())
22
+
23
+ # import horovod.torch as hvd
24
+
25
+ import atmorep.utils.utils as utils
26
+ from atmorep.utils.utils import identity
27
+ from atmorep.utils.utils import NetMode
28
+ from atmorep.utils.utils import get_model_filename
29
+
30
+ from atmorep.transformer.transformer_base import prepare_token
31
+ from atmorep.transformer.transformer_base import checkpoint_wrapper
32
+
33
+ from atmorep.datasets.multifield_data_sampler import MultifieldDataSampler
34
+
35
+ from atmorep.transformer.transformer_encoder import TransformerEncoder
36
+ from atmorep.transformer.transformer_decoder import TransformerDecoder
37
+ from atmorep.transformer.tail_ensemble import TailEnsemble
38
+
39
+
40
+ ####################################################################################################
41
+ class AtmoRepData( torch.nn.Module) :
42
+
43
+ def __init__( self, net) :
44
+ '''Wrapper class for AtmoRep that handles data loading'''
45
+
46
+ super( AtmoRepData, self).__init__()
47
+
48
+ self.data_loader_test = None
49
+ self.data_loader_train = None
50
+ self.data_loader_iter = None
51
+
52
+ self.net = net
53
+
54
+ # ensure that all data loaders have the same seed and hence load the same data
55
+ self.rng_seed = net.cf.rng_seed
56
+ if not self.rng_seed :
57
+ self.rng_seed = int(torch.randint( 100000000, (1,)))
58
+
59
+ ###################################################
60
+ def set_data( self, mode : NetMode, times_pos, batch_size = -1, num_loader_workers = -1) :
61
+
62
+ cf = self.net.cf
63
+ if batch_size < 0 :
64
+ batch_size = cf.batch_size_train if mode == NetMode.train else cf.batch_size_test
65
+
66
+ dataset = self.dataset_train if mode == NetMode.train else self.dataset_test
67
+ dataset.set_data( times_pos, batch_size)
68
+
69
+ self._set_data( dataset, mode, batch_size, num_loader_workers)
70
+
71
+ ###################################################
72
+ def set_global( self, mode : NetMode, times, batch_size = -1, num_loader_workers = -1) :
73
+
74
+ cf = self.net.cf
75
+ if batch_size < 0 :
76
+ batch_size = cf.batch_size_train if mode == NetMode.train else cf.batch_size_test
77
+ dataset = self.dataset_train if mode == NetMode.train else self.dataset_test
78
+ dataset.set_global( times, batch_size, cf.token_overlap)
79
+
80
+ self._set_data( dataset, mode, batch_size, num_loader_workers)
81
+
82
+ ###################################################
83
+ def set_location( self, mode : NetMode, pos, years, months, num_t_samples_per_month,
84
+ batch_size = -1, num_loader_workers = -1) :
85
+
86
+ cf = self.net.cf
87
+ if batch_size < 0 :
88
+ batch_size = cf.batch_size_train if mode == NetMode.train else cf.batch_size_test
89
+
90
+ dataset = self.dataset_train if mode == NetMode.train else self.dataset_test
91
+ dataset.set_location( pos, years, months, num_t_samples_per_month, batch_size)
92
+
93
+ self._set_data( dataset, mode, batch_size, num_loader_workers)
94
+
95
+ ###################################################
96
+ def _set_data( self, dataset, mode : NetMode, batch_size = -1, loader_workers = -1) :
97
+ '''Private implementation for set_data, set_global'''
98
+
99
+ cf = self.net.cf
100
+ if loader_workers < 0 :
101
+ loader_workers = cf.num_loader_workers
102
+
103
+ loader_params = { 'batch_size': None, 'batch_sampler': None, 'shuffle': False,
104
+ 'num_workers': loader_workers, 'pin_memory': True}
105
+
106
+ if mode == NetMode.train :
107
+ self.data_loader_train = torch.utils.data.DataLoader( dataset, **loader_params,
108
+ sampler = None)
109
+ elif mode == NetMode.test :
110
+ self.data_loader_test = torch.utils.data.DataLoader( dataset, **loader_params,
111
+ sampler = None)
112
+ else :
113
+ assert False
114
+
115
+ ###################################################
116
+ def normalizer( self, field, vl_idx, lats_idx, lons_idx ) :
117
+
118
+ if isinstance( field, str) :
119
+ for fidx, field_info in enumerate(self.cf.fields) :
120
+ if field == field_info[0] :
121
+ break
122
+ assert fidx < len(self.cf.fields), 'invalid field'
123
+ normalizer = self.dataset_train.datasets[fidx].normalizer
124
+
125
+ elif isinstance( field, int) :
126
+ normalizer = self.dataset_train.normalizers[field][vl_idx]
127
+ if len(normalizer.shape) > 2:
128
+ normalizer = np.take( np.take( normalizer, lats_idx, -2), lons_idx, -1)
129
+ else :
130
+ assert False, 'invalid argument type (has to be index to cf.fields or field name)'
131
+
132
+ year_base = self.dataset_train.year_base
133
+
134
+ return normalizer, year_base
135
+
136
+ ###################################################
137
+ def mode( self, mode : NetMode) :
138
+
139
+ if mode == NetMode.train :
140
+ self.data_loader_iter = iter(self.data_loader_train)
141
+ self.net.train()
142
+ elif mode == NetMode.test :
143
+ self.data_loader_iter = iter(self.data_loader_test)
144
+ self.net.eval()
145
+ else :
146
+ assert False
147
+
148
+ self.cur_mode = mode
149
+
150
+ ###################################################
151
+ def len( self, mode : NetMode) :
152
+ if mode == NetMode.train :
153
+ return len(self.data_loader_train)
154
+ elif mode == NetMode.test :
155
+ return len(self.data_loader_test)
156
+ else :
157
+ assert False
158
+
159
+ ###################################################
160
+ def next( self) :
161
+ return next(self.data_loader_iter)
162
+
163
+ ###################################################
164
+ def forward( self, xin) :
165
+ pred = self.net.forward( xin)
166
+ return pred
167
+
168
+ ###################################################
169
+ def get_attention( self, xin) :
170
+ attn = self.net.get_attention( xin)
171
+ return attn
172
+
173
+ ###################################################
174
+ def create( self, pre_batch, devices, create_net = True, pre_batch_targets = None,
175
+ load_pretrained=True) :
176
+
177
+ if create_net :
178
+ self.net.create( devices, load_pretrained)
179
+
180
+ self.pre_batch = pre_batch
181
+ self.pre_batch_targets = pre_batch_targets
182
+
183
+ cf = self.net.cf
184
+ loader_params = { 'batch_size': None, 'batch_sampler': None, 'shuffle': False,
185
+ 'num_workers': cf.num_loader_workers, 'pin_memory': True}
186
+
187
+ self.dataset_train = MultifieldDataSampler( cf.file_path, cf.fields, cf.years_train,
188
+ cf.batch_size,
189
+ pre_batch, cf.n_size, cf.num_samples_per_epoch,
190
+ with_shuffle = (cf.BERT_strategy != 'global_forecast'),
191
+ with_source_idxs = True,
192
+ compute_weights = (cf.losses.count('weighted_mse') > 0) )
193
+ self.data_loader_train = torch.utils.data.DataLoader( self.dataset_train, **loader_params,
194
+ sampler = None)
195
+
196
+ self.dataset_test = MultifieldDataSampler( cf.file_path, cf.fields, cf.years_val,
197
+ cf.batch_size_validation,
198
+ pre_batch, cf.n_size, cf.num_samples_validate,
199
+ with_shuffle = (cf.BERT_strategy != 'global_forecast'),
200
+ with_source_idxs = True,
201
+ compute_weights = (cf.losses.count('weighted_mse') > 0) )
202
+ self.data_loader_test = torch.utils.data.DataLoader( self.dataset_test, **loader_params,
203
+ sampler = None)
204
+
205
+ return self
206
+
207
+ ####################################################################################################
208
+ class AtmoRep( torch.nn.Module) :
209
+
210
+ def __init__(self, cf) :
211
+ '''Constructor'''
212
+
213
+ super( AtmoRep, self).__init__()
214
+
215
+ self.cf = cf
216
+
217
+ ###################################################
218
+ def create( self, devices, load_pretrained=True) :
219
+ '''Create network'''
220
+
221
+ cf = self.cf
222
+ self.devices = devices
223
+ self.fields_coupling_idx = []
224
+
225
+ self.fields_index = {}
226
+ for ifield, field_info in enumerate(cf.fields) :
227
+ self.fields_index[ field_info[0] ] = ifield
228
+
229
+ # # embedding network for global/auxiliary token infos
230
+ # TODO: only for backward compatibility, remove
231
+ self.embed_token_info = torch.nn.Linear( cf.size_token_info, cf.size_token_info_net)
232
+ torch.nn.init.constant_( self.embed_token_info.weight, 0.0)
233
+
234
+ self.embeds_token_info = torch.nn.ModuleList()
235
+ for ifield, field_info in enumerate( cf.fields) :
236
+
237
+ self.embeds_token_info.append( torch.nn.Linear( cf.size_token_info, cf.size_token_info_net))
238
+
239
+ if len(field_info[1]) > 4 and load_pretrained :
240
+ # TODO: inconsistent with embeds_token_info -> version that can handle both
241
+ # we could imply use the file name: embed_token_info vs embeds_token_info
242
+ name = 'AtmoRep' + '_embeds_token_info'
243
+ if not os.path.exists(get_model_filename( name, field_info[1][4][0], field_info[1][4][1])):
244
+ name = 'AtmoRep' + '_embed_token_info'
245
+
246
+ mloaded = torch.load( get_model_filename( name, field_info[1][4][0], field_info[1][4][1]))
247
+
248
+ if "weight" not in mloaded.keys(): #TODO: get rid of this
249
+ mloaded["weight"] = mloaded["0.weight"]
250
+ mloaded["bias"] = mloaded["0.bias"]
251
+ del mloaded["0.weight"]
252
+ del mloaded["0.bias"]
253
+
254
+ self.embeds_token_info[-1].load_state_dict( mloaded)
255
+ print( 'Loaded embed_token_info from id = {}.'.format( field_info[1][4][0] ) )
256
+ else :
257
+ # initalization
258
+ torch.nn.init.constant_( self.embeds_token_info[-1].weight, 0.0)
259
+ self.embeds_token_info[-1].bias.data.fill_(0.0)
260
+
261
+ # embedding and encoder
262
+
263
+ self.embeds = torch.nn.ModuleList()
264
+ self.encoders = torch.nn.ModuleList()
265
+
266
+ for field_idx, field_info in enumerate(cf.fields) :
267
+
268
+ # encoder
269
+ self.encoders.append( TransformerEncoder( cf, field_idx, True).create())
270
+ # load pre-trained model if specified
271
+ if len(field_info[1]) > 4 and load_pretrained :
272
+ self.load_block( field_info, 'encoder', self.encoders[-1])
273
+ self.embeds.append( self.encoders[-1].embed)
274
+
275
+ # indices of coupled fields for efficient access in forward
276
+ self.fields_coupling_idx.append( [field_idx])
277
+ for field_coupled in field_info[1][2] :
278
+ if 'axial' in cf.encoder_att_type :
279
+ self.fields_coupling_idx[field_idx].append( self.fields_index[field_coupled] )
280
+ else :
281
+ for _ in range(cf.coupling_num_heads_per_field) :
282
+ self.fields_coupling_idx[field_idx].append( self.fields_index[field_coupled] )
283
+
284
+ # decoder
285
+
286
+ self.decoders = torch.nn.ModuleList()
287
+ self.field_pred_idxs = []
288
+ for field in cf.fields_prediction :
289
+
290
+ for ifield, field_info in enumerate(cf.fields) :
291
+ if field_info[0] == field[0] :
292
+ self.field_pred_idxs.append( ifield)
293
+ break
294
+
295
+ self.decoders.append( TransformerDecoder( cf, field_info ) )
296
+ # load pre-trained model if specified
297
+ if len(field_info[1]) > 4 and load_pretrained :
298
+ self.load_block( field_info, 'decoder', self.decoders[-1])
299
+
300
+ # tail networks
301
+
302
+ self.tails = torch.nn.ModuleList()
303
+ for ifield, field in enumerate(cf.fields_prediction) :
304
+
305
+ field_idx = self.field_pred_idxs[ifield]
306
+ field_info = cf.fields[field_idx]
307
+ self.tails.append( TailEnsemble( cf, field_info[1][1], np.prod(field_info[4]) ).create())
308
+ # load pre-trained model if specified
309
+ if len(field_info[1]) > 4 and load_pretrained:
310
+ self.load_block( field_info, 'tail', self.tails[-1])
311
+
312
+ # set devices
313
+
314
+ for field_idx, field_info in enumerate(cf.fields) :
315
+ # find determined device, use default if nothing specified
316
+ device = self.devices[0]
317
+ if len(field_info[1]) > 3 :
318
+ assert field_info[1][3] < 4, 'Only single node model parallelism supported'
319
+ print(devices, field_info[1][3])
320
+ assert field_info[1][3] < len(devices), 'Per field device id larger than max devices'
321
+ device = self.devices[ field_info[1][3] ]
322
+ # set device
323
+ self.embeds[field_idx].to(device)
324
+ self.encoders[field_idx].to(device)
325
+
326
+ for field_idx, field in enumerate(cf.fields_prediction) :
327
+ field_info = cf.fields[ self.field_pred_idxs[field_idx] ]
328
+ device = self.devices[0]
329
+ if len(field_info[1]) > 3 :
330
+ device = self.devices[ field_info[1][3] ]
331
+ self.decoders[field_idx].to(device)
332
+ self.tails[field_idx].to(device)
333
+
334
+ # embed_token_info on device[0] since it is shared by all fields, potentially sub-optimal
335
+ self.embed_token_info.to(devices[0]) # TODO: only for backward compatibility, remove
336
+ self.embeds_token_info.to(devices[0])
337
+
338
+ self.checkpoint = identity
339
+ if cf.grad_checkpointing :
340
+ self.checkpoint = checkpoint_wrapper
341
+
342
+ return self
343
+
344
+ ###################################################
345
+ def load_block( self, field_info, block_name, block ) :
346
+
347
+ # name = self.__class__.__name__ + '_' + block_name + '_' + field_info[0]
348
+ name = 'AtmoRep_' + block_name + '_' + field_info[0]
349
+
350
+ b_loaded = torch.load( get_model_filename(name, field_info[1][4][0], field_info[1][4][1]))
351
+
352
+ # in coupling mode, proj_out of attention heads needs separate treatment: only the pre-trained
353
+ # part can be loaded
354
+ keys_del = []
355
+ for name, param in block.named_parameters():
356
+ if 'proj_out' in name :
357
+ for k in b_loaded.keys() :
358
+ if name == k :
359
+ if param.shape[0] != param.shape[1] : # non-square proj_out indicate deviation from pre-training
360
+ with torch.no_grad() :
361
+ # load pre-trained part
362
+ param[ : , : b_loaded[k].shape[1] ] = b_loaded[k]
363
+ # initalize remaining part to small random value
364
+ param[ : , b_loaded[k].shape[1] : ] = 0.01 * torch.rand( param.shape[0],
365
+ param.shape[1] - b_loaded[k].shape[1])
366
+ keys_del += [ k ]
367
+
368
+ #for backward compatibility. solved in new runs
369
+ if 'proj_heads_other' in name:
370
+ for k in b_loaded.keys() :
371
+ if name == k :
372
+ if b_loaded[name].shape[0] == 0:
373
+ keys_del += [ name ]
374
+
375
+ for k in keys_del :
376
+ del b_loaded[k]
377
+
378
+ # use strict=False so that differing blocks, e.g. through coupling, are ignored
379
+ mkeys, _ = block.load_state_dict( b_loaded, False)
380
+
381
+ # missing keys = keys that are not pre-trained are initalized to small value
382
+ [mkeys.remove(k) for k in keys_del] # remove proj_out keys so that they are not over-written
383
+ [utils.init_weights_uniform( block.state_dict()[k], 0.01) for k in mkeys]
384
+
385
+ print( 'Loaded {} for {} from id = {} (ignoring/missing {} elements).'.format( block_name,
386
+ field_info[0], field_info[1][4][0], len(mkeys) ) )
387
+
388
+ ###################################################
389
+ def translate_weights(self, mloaded, mkeys, ukeys):
390
+ '''
391
+ Function used for backward compatibility
392
+ '''
393
+ cf = self.cf
394
+
395
+ #encoder:
396
+ for layer in range(cf.encoder_num_layers) :
397
+
398
+ #shape([16, 3, 128, 2048])
399
+ mw = torch.cat([mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_{k}.weight'] for head in range(cf.encoder_num_heads) for k in ["qs", "ks", "vs"]])
400
+ mloaded[f'encoders.0.heads.{layer}.proj_heads.weight'] = mw
401
+
402
+ for head in range(cf.encoder_num_heads):
403
+ del mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_qs.weight']
404
+ del mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_ks.weight']
405
+ del mloaded[f'encoders.0.heads.{layer}.heads_self.{head}.proj_vs.weight']
406
+
407
+ #cross attention
408
+ if f'encoders.0.heads.{layer}.heads_other.0.proj_qs.weight' in ukeys:
409
+ mw = torch.cat([mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_{k}.weight'] for head in range(cf.encoder_num_heads) for k in ["qs", "ks", "vs"]])
410
+
411
+ for i in range(cf.encoder_num_heads):
412
+ del mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_qs.weight']
413
+ del mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_ks.weight']
414
+ del mloaded[f'encoders.0.heads.{layer}.heads_other.{head}.proj_vs.weight']
415
+
416
+ mloaded[f'encoders.0.heads.{layer}.proj_heads_other.0.weight'] = mw
417
+
418
+ #decoder
419
+ for iblock in range(0, 19, 2) :
420
+ mw = torch.cat([mloaded[f'decoders.0.blocks.{iblock}.heads.{head}.proj_{k}.weight'] for head in range(8) for k in ["qs", "ks", "vs"]])
421
+ mloaded[f'decoders.0.blocks.{iblock}.proj_heads.weight'] = mw
422
+
423
+ qs = [mloaded[f'decoders.0.blocks.{iblock}.heads_other.{head}.proj_qs.weight'] for head in range(8)]
424
+ mw = torch.cat([mloaded[f'decoders.0.blocks.{iblock}.heads_other.{head}.proj_{k}.weight'] for head in range(8) for k in ["ks", "vs"]])
425
+
426
+ mloaded[f'decoders.0.blocks.{iblock}.proj_heads_o_q.weight'] = torch.cat([*qs])
427
+ mloaded[f'decoders.0.blocks.{iblock}.proj_heads_o_kv.weight'] = mw
428
+
429
+ #self.num_samples_validate
430
+ decoder_dim = self.decoders[0].blocks[iblock].ln_q.weight.shape #128
431
+ mloaded[f'decoders.0.blocks.{iblock}.ln_q.weight'] = torch.tensor(np.ones(decoder_dim))
432
+ mloaded[f'decoders.0.blocks.{iblock}.ln_k.weight'] = torch.tensor(np.ones(decoder_dim))
433
+ mloaded[f'decoders.0.blocks.{iblock}.ln_q.bias'] = torch.tensor(np.ones(decoder_dim))
434
+ mloaded[f'decoders.0.blocks.{iblock}.ln_k.bias'] = torch.tensor(np.ones(decoder_dim))
435
+
436
+ for i in range(8):
437
+ del mloaded[f'decoders.0.blocks.{iblock}.heads.{i}.proj_qs.weight']
438
+ del mloaded[f'decoders.0.blocks.{iblock}.heads.{i}.proj_ks.weight']
439
+ del mloaded[f'decoders.0.blocks.{iblock}.heads.{i}.proj_vs.weight']
440
+ del mloaded[f'decoders.0.blocks.{iblock}.heads_other.{i}.proj_qs.weight']
441
+ del mloaded[f'decoders.0.blocks.{iblock}.heads_other.{i}.proj_ks.weight']
442
+ del mloaded[f'decoders.0.blocks.{iblock}.heads_other.{i}.proj_vs.weight']
443
+
444
+ return mloaded
445
+
446
+ ###################################################
447
+ @staticmethod
448
+ def load( model_id, devices, cf = None, epoch = -2, load_pretrained=False) :
449
+ '''Load network from checkpoint'''
450
+
451
+ if not cf :
452
+ cf = utils.Config()
453
+ cf.load_json( model_id)
454
+
455
+ model = AtmoRep( cf).create( devices, load_pretrained=False)
456
+ mloaded = torch.load( utils.get_model_filename( model, model_id, epoch) )
457
+ mkeys, ukeys = model.load_state_dict( mloaded, False )
458
+ if (f'encoders.0.heads.0.proj_heads.weight') in mkeys:
459
+ mloaded = model.translate_weights(mloaded, mkeys, ukeys)
460
+ mkeys, ukeys = model.load_state_dict( mloaded, False )
461
+
462
+ if len(mkeys) > 0 :
463
+ print( f'Loaded AtmoRep: ignoring {len(mkeys)} elements: {mkeys}')
464
+
465
+ # TODO: remove, only for backward
466
+ if model.embeds_token_info[0].weight.abs().max() == 0. :
467
+ model.embeds_token_info = torch.nn.ModuleList()
468
+
469
+ return model
470
+
471
+ ###################################################
472
+ def save( self, epoch = -2) :
473
+ '''Save network '''
474
+
475
+ # save entire network
476
+ torch.save( self.state_dict(), utils.get_model_filename( self, self.cf.wandb_id, epoch) )
477
+
478
+ # save parts also separately
479
+
480
+ # name = self.__class__.__name__ + '_embed_token_info'
481
+ # torch.save( self.embed_token_info.state_dict(),
482
+ # utils.get_model_filename( name, self.cf.wandb_id, epoch) )
483
+ name = self.__class__.__name__ + '_embeds_token_info'
484
+ torch.save( self.embeds_token_info.state_dict(),
485
+ utils.get_model_filename( name, self.cf.wandb_id, epoch) )
486
+
487
+ for ifield, enc in enumerate(self.encoders) :
488
+ name = self.__class__.__name__ + '_encoder_' + self.cf.fields[ifield][0]
489
+ torch.save( enc.state_dict(), utils.get_model_filename( name, self.cf.wandb_id, epoch) )
490
+
491
+ for ifield, dec in enumerate(self.decoders) :
492
+ name = self.__class__.__name__ + '_decoder_' + self.cf.fields_prediction[ifield][0]
493
+ torch.save( dec.state_dict(), utils.get_model_filename( name, self.cf.wandb_id, epoch) )
494
+
495
+ for ifield, tail in enumerate(self.tails) :
496
+ name = self.__class__.__name__ + '_tail_' + self.cf.fields_prediction[ifield][0]
497
+ torch.save( tail.state_dict(), utils.get_model_filename( name, self.cf.wandb_id, epoch) )
498
+
499
+ ###################################################
500
+ def forward( self, xin) :
501
+ '''Evaluate network'''
502
+
503
+ # embedding
504
+ cf = self.cf
505
+
506
+ fields_embed = self.get_fields_embed(xin)
507
+
508
+ # attention maps (if requested)
509
+ atts = [ [] for _ in cf.fields ]
510
+
511
+ # encoder
512
+ embeds_layers = [[] for i in self.field_pred_idxs]
513
+ for ib in range(self.cf.encoder_num_layers) :
514
+ fields_embed, att = self.forward_encoder_block( ib, fields_embed)
515
+ [embeds_layers[idx].append( fields_embed[i]) for idx,i in enumerate(self.field_pred_idxs)]
516
+ [atts[i].append( att[i]) for i,_ in enumerate(cf.fields) ]
517
+
518
+ # encoder-decoder coupling / token transformations
519
+ (decoders_in, embeds_layers) = self.encoder_to_decoder( embeds_layers)
520
+
521
+ preds = []
522
+ for idx,i in enumerate(self.field_pred_idxs) :
523
+
524
+ # decoder
525
+ token_seq_embed, att = self.decoders[idx]( (decoders_in[idx], embeds_layers[idx]) )
526
+
527
+ # tail net
528
+ tail_in = self.decoder_to_tail( idx, token_seq_embed)
529
+ pred = self.checkpoint( self.tails[idx], tail_in)
530
+
531
+ preds.append( pred)
532
+ [atts[i].append( a) for a in att]
533
+
534
+ return preds, atts
535
+
536
+ ###################################################
537
+ def forward_encoder_block( self, iblock, fields_embed) :
538
+ ''' evaluate one block (attention and mlp) '''
539
+
540
+ # double buffer for commutation-invariant result (w.r.t evaluation order of transformers)
541
+ fields_embed_cur, atts = [], []
542
+
543
+ # attention heads
544
+ for ifield in range( len(fields_embed)) :
545
+ d = fields_embed[ifield].device
546
+ fields_in =[fields_embed[i].to(d,non_blocking=True) for i in self.fields_coupling_idx[ifield]]
547
+ # unpack list in argument for checkpointing
548
+ y, att = self.checkpoint( self.encoders[ifield].heads[iblock], *fields_in)
549
+ fields_embed_cur.append( y)
550
+ atts.append( att)
551
+
552
+ # MLPs
553
+ for ifield in range( len(fields_embed)) :
554
+ fields_embed_cur[ifield] = self.checkpoint( self.encoders[ifield].mlps[iblock],
555
+ fields_embed_cur[ifield] )
556
+
557
+ return fields_embed_cur, atts
558
+
559
+ ###################################################
560
+ def get_fields_embed( self, xin ) :
561
+ if 0 == len(self.embeds_token_info) : # TODO: only for backward compatibility, remove
562
+ emb_net_ti = self.embed_token_info
563
+ return [prepare_token( field_data, emb_net, emb_net_ti )
564
+ for fidx,(field_data,emb_net) in enumerate(zip( xin, self.embeds))]
565
+ else :
566
+ embs_net_ti = self.embeds_token_info
567
+ return [prepare_token( field_data, emb_net, embs_net_ti[fidx] )
568
+ for fidx,(field_data,emb_net) in enumerate(zip( xin, self.embeds))]
569
+
570
+ ###################################################
571
+
572
+ def get_attention( self, xin) :
573
+
574
+ cf = self.cf
575
+ attn = []
576
+ fields_embed = self.get_fields_embed(xin)
577
+ #either accumulated attention or last layer attention:
578
+ blocks = list(range(self.cf.encoder_num_layers)) if cf.attention_mode == 'accum' else [self.cf.encoder_num_layers-1]
579
+ for idx, ifield in enumerate(self.field_pred_idxs) :
580
+ d = fields_embed[ifield].device
581
+ fields_in =[fields_embed[i].to(d,non_blocking=True) for i in self.fields_coupling_idx[ifield]]
582
+ attn_field = self.encoders[ifield].heads[blocks[0]].get_attention(fields_in)
583
+ if cf.attention_mode == 'accum':
584
+ for iblock in blocks[1:]:
585
+ attn_layer = self.encoders[ifield].heads[iblock].get_attention(fields_in)
586
+ attn_field = attn_field + attn_layer
587
+ attn_field = torch.sum(attn_field, dim = 0, keepdim=True)
588
+ attn.append(attn_field)
589
+ # print("att FINAL", ifield, len(attn), attn[0].shape)
590
+ return attn
vendor/atmorep-official/atmorep/core/evaluate.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ from atmorep.core.evaluator import Evaluator
18
+ import time
19
+
20
+ if __name__ == '__main__':
21
+
22
+ # arXiv 2023: models for individual fields
23
+ #model_id = '4nvwbetz' # vorticity
24
+ #model_id = 'oxpycr7w' # divergence
25
+ #model_id = '1565pb1f' # specific_humidity
26
+ #model_id = '3kdutwqb' # total precip
27
+ #model_id = 'dys79lgw' # velocity_u
28
+ #model_id = '22j6gysw' # velocity_v
29
+ #model_id = '15oisw8d' # velocity_z
30
+ #model_id = '3qou60es' # temperature
31
+ #model_id = '2147fkco' # temperature (also 2147fkco)
32
+
33
+ # new runs 2024
34
+ #model_id='j8dwr5qj' #velocity_u
35
+ #model_id='0tlnm5up' #velocity_v
36
+ #model_id='v63l01zu' #specific humidity
37
+ #model_id='9l1errbo' #velocity_z
38
+ model_id='7ojls62c' #temperature 1024
39
+
40
+ # supported modes: test, forecast, fixed_location, temporal_interpolation, global_forecast,
41
+ # global_forecast_range
42
+ # options can be used to over-write parameters in config; some modes also have specific options,
43
+ # e.g. global_forecast where a start date can be specified
44
+
45
+ #Add 'attention' : True to options to store the attention maps. NB. supported only for single field runs.
46
+
47
+ # BERT masked token model
48
+ #mode, options = 'BERT', {'years_val' : [2021], 'num_samples_validate' : 128, 'with_pytest' : True}
49
+
50
+ # BERT forecast mode
51
+ #mode, options = 'forecast', {'forecast_num_tokens' : 2, 'num_samples_validate' : 128, 'with_pytest' : True }
52
+
53
+ #temporal interpolation
54
+ #idx_time_mask: list of relative time positions of the masked tokens within the cube wrt num_tokens[0]
55
+ #mode, options = 'temporal_interpolation', {'idx_time_mask': [5,6,7], 'num_samples_validate' : 128, 'with_pytest' : True}
56
+
57
+ # BERT forecast with patching to obtain global forecast
58
+ mode, options = 'global_forecast', {
59
+ #'dates' : [[2021, 2, 10, 12]]
60
+ 'dates' : [
61
+ [2021, 1, 10, 12] , [2021, 1, 11, 0], [2021, 1, 11, 12], [2021, 1, 12, 0], #[2021, 1, 12, 12], [2021, 1, 13, 0],
62
+ [2021, 4, 10, 12], [2021, 4, 11, 0], [2021, 4, 11, 12], [2021, 4, 12, 0], #[2021, 4, 12, 12], [2021, 4, 13, 0],
63
+ [2021, 7, 10, 12], [2021, 7, 11, 0], [2021, 7, 11, 12], [2021, 7, 12, 0], #[2021, 7, 12, 12], [2021, 7, 13, 0],
64
+ [2021, 10, 10, 12], [2021, 10, 11, 0], [2021, 10, 11, 12], #[2021, 10, 12, 0], [2021, 10, 12, 12], [2021, 10, 13, 0]
65
+ ],
66
+ 'token_overlap' : [0, 0],
67
+ 'forecast_num_tokens' : 2,
68
+ 'with_pytest' : True }
69
+
70
+ file_path = '/gpfs/scratch/ehpc03/era5_y1979_2021_res025_chunk8.zarr'
71
+
72
+ now = time.time()
73
+ Evaluator.evaluate( mode, model_id, file_path, options)
74
+ print("time", time.time() - now)
vendor/atmorep-official/atmorep/core/evaluator.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import numpy as np
18
+ import os
19
+ import code
20
+ import pytest
21
+ import datetime
22
+
23
+ import wandb
24
+
25
+ from atmorep.core.trainer import Trainer_BERT
26
+ from atmorep.utils.utils import Config
27
+ from atmorep.utils.utils import setup_ddp
28
+ from atmorep.utils.utils import setup_wandb
29
+ from atmorep.utils.utils import init_torch
30
+ from atmorep.utils.utils import NetMode
31
+ import atmorep.utils.utils as utils
32
+
33
+ import atmorep.config.config as config
34
+
35
+ class Evaluator( Trainer_BERT) :
36
+
37
+ ##############################################
38
+ def __init__( self, cf, devices) :
39
+ Trainer_BERT.__init__( self, cf, devices)
40
+
41
+ ##############################################
42
+ def parse_args( cf, args) :
43
+
44
+ # set/over-write options as desired
45
+ for (key,val) in args.items() :
46
+ if '[' in key : # handle lists, e.g. fields[0][2]
47
+ key_split = key.split( '[')
48
+ k, v = key_split[0], key_split[1:]
49
+ v = [int(a[0]) for a in v]
50
+ utils.list_replace_rec( getattr( cf, k), v, val)
51
+ else :
52
+ setattr( cf, key, val)
53
+
54
+ ##############################################
55
+ @staticmethod
56
+ def run( cf, model_id, model_epoch, devices) :
57
+
58
+ cf.with_mixed_precision = True
59
+
60
+ # set/over-write options as desired
61
+ evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
62
+
63
+ if 0 == cf.par_rank :
64
+ cf.print()
65
+ cf.write_json( wandb)
66
+ evaluator.validate( 0, cf.BERT_strategy)
67
+
68
+ ##############################################
69
+ @staticmethod
70
+ def evaluate( mode, model_id, file_path, args = {}, model_epoch=-2) :
71
+
72
+ devices = init_torch()
73
+ with_ddp = True
74
+ par_rank, par_size = setup_ddp( with_ddp)
75
+
76
+ cf = Config().load_json( model_id)
77
+
78
+ cf.num_accs_per_task = len(devices)
79
+ cf.file_path = file_path
80
+ cf.with_wandb = True
81
+ cf.with_ddp = with_ddp
82
+ cf.par_rank = par_rank
83
+ cf.par_size = par_size
84
+ cf.losses = cf.losses
85
+ # overwrite old config
86
+ cf.attention = False
87
+ setup_wandb( cf.with_wandb, cf, par_rank, '', mode='offline')
88
+ if 0 == cf.par_rank :
89
+ print( 'Running Evaluate.evaluate with mode =', mode)
90
+
91
+ # if not hasattr( cf, 'num_loader_workers'):
92
+ cf.num_loader_workers = 12 #cf.loader_num_workers
93
+ cf.rng_seed = None
94
+
95
+ #backward compatibility
96
+ if not hasattr( cf, 'n_size'):
97
+ cf.n_size = [36, 0.25*9*6, 0.25*9*12]
98
+ #cf.n_size = [36, 0.25*27*2, 0.25*27*4]
99
+ if not hasattr(cf, 'num_samples_per_epoch'):
100
+ cf.num_samples_per_epoch = 1024
101
+ if not hasattr(cf, 'with_mixed_precision'):
102
+ cf.with_mixed_precision = False
103
+ if not hasattr(cf, 'with_pytest'):
104
+ cf.with_pytest = False
105
+ if not hasattr(cf, 'batch_size'):
106
+ cf.batch_size = cf.batch_size_max
107
+ if not hasattr(cf, 'batch_size_validation'):
108
+ cf.batch_size_validation = cf.batch_size_max
109
+ if not hasattr(cf, 'years_val'):
110
+ cf.years_val = cf.years_test
111
+
112
+ func = getattr( Evaluator, mode)
113
+ func( cf, model_id, model_epoch, devices, args)
114
+
115
+ if cf.with_pytest:
116
+ fields = [field[0] for field in cf.fields_prediction]
117
+ for field in fields:
118
+ pytest.main(["-x", "-s", "./atmorep/tests/validation_test.py", "--field", field, "--model_id", cf.wandb_id, "--strategy", cf.BERT_strategy])
119
+
120
+ ##############################################
121
+ @staticmethod
122
+ def BERT( cf, model_id, model_epoch, devices, args = {}) :
123
+
124
+ cf.lat_sampling_weighted = False
125
+ cf.BERT_strategy = 'BERT'
126
+ cf.log_test_num_ranks = 4
127
+ cf.num_samples_validate = 10 #28 #1472
128
+ Evaluator.parse_args( cf, args)
129
+ utils.check_num_samples(cf.num_samples_validate, cf.batch_size)
130
+ Evaluator.run( cf, model_id, model_epoch, devices)
131
+
132
+ ##############################################
133
+ @staticmethod
134
+ def forecast( cf, model_id, model_epoch, devices, args = {}) :
135
+
136
+ cf.lat_sampling_weighted = False
137
+ cf.BERT_strategy = 'forecast'
138
+ cf.log_test_num_ranks = 4
139
+ cf.forecast_num_tokens = 1 # will be overwritten when user specified
140
+ cf.num_samples_validate = 128 #128
141
+ Evaluator.parse_args( cf, args)
142
+ utils.check_num_samples(cf.num_samples_validate, cf.batch_size)
143
+ Evaluator.run( cf, model_id, model_epoch, devices)
144
+
145
+ ##############################################
146
+ @staticmethod
147
+ def global_forecast( cf, model_id, model_epoch, devices, args = {}) :
148
+
149
+ cf.BERT_strategy = 'global_forecast'
150
+ cf.batch_size_test = 24
151
+ cf.num_loader_workers = 12 #1
152
+ cf.log_test_num_ranks = 1
153
+
154
+ #TODO: temporary solution. Add support for batch_size > 1
155
+ cf.batch_size_validation = 1 #64
156
+ cf.batch_size = 1
157
+
158
+ if not hasattr(cf, 'num_samples_validate'):
159
+ cf.num_samples_validate = 196
160
+ #if not hasattr(cf,'with_mixed_precision'):
161
+ cf.with_mixed_precision = True
162
+
163
+ Evaluator.parse_args( cf, args)
164
+
165
+ dates = args['dates']
166
+ evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
167
+ evaluator.model.set_global( NetMode.test, np.array( dates))
168
+ if 0 == cf.par_rank :
169
+ cf.print()
170
+ cf.write_json( wandb)
171
+ evaluator.validate( 0, cf.BERT_strategy)
172
+
173
+ ##############################################
174
+ @staticmethod
175
+ def global_forecast_range( cf, model_id, model_epoch, devices, args = {}) :
176
+
177
+ cf.forecast_num_tokens = 2
178
+ cf.BERT_strategy = 'global_forecast'
179
+ cf.token_overlap = [0, 0]
180
+
181
+ cf.batch_size_test = 24
182
+ cf.num_loader_workers = 1
183
+ cf.log_test_num_ranks = 1
184
+ cf.batch_size_start = 14
185
+ if not hasattr(cf, 'num_samples_validate'):
186
+ cf.num_samples_validate = 196
187
+
188
+ Evaluator.parse_args( cf, args)
189
+
190
+ if 0 == cf.par_rank :
191
+ cf.print()
192
+ cf.write_json( wandb)
193
+
194
+ # generate temporal sequence
195
+ dates = [ ]
196
+ num_steps = 31*2
197
+ cur_date = [2018, 1, 1, 0+6] #6h models
198
+ for _ in range(num_steps) :
199
+ tdate = datetime.datetime( cur_date[0], cur_date[1], cur_date[2], cur_date[3])
200
+ tdate += datetime.timedelta( hours = 12 )
201
+ cur_date = [tdate.year, tdate.month, tdate.day, tdate.hour]
202
+ dates += [cur_date]
203
+
204
+ evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
205
+ evaluator.model.set_global( NetMode.test, np.array( dates))
206
+ evaluator.evaluate( 0, cf.BERT_strategy)
207
+
208
+ ##############################################
209
+ @staticmethod
210
+ def temporal_interpolation( cf, model_id, model_epoch, devices, args = {}) :
211
+
212
+ # set/over-write options
213
+ cf.BERT_strategy = 'temporal_interpolation'
214
+ cf.log_test_num_ranks = 4
215
+ cf.num_samples_validate = 128
216
+ Evaluator.parse_args( cf, args)
217
+ utils.check_num_samples(cf.num_samples_validate, cf.batch_size)
218
+ Evaluator.run( cf, model_id, model_epoch, devices)
219
+
220
+ ##############################################
221
+ @staticmethod
222
+ def fixed_location( cf, model_id, model_epoch, devices, args = {}) :
223
+
224
+ # set/over-write options
225
+ cf.BERT_strategy = 'BERT'
226
+ cf.num_files_test = 2
227
+ cf.num_patches_per_t_test = 2
228
+ cf.log_test_num_ranks = 4
229
+
230
+ pos = [ 33.55 , 18.25 ]
231
+ years = [2018]
232
+ months = list(range(1,12+1))
233
+ num_t_samples_per_month = 2
234
+
235
+ evaluator = Evaluator.load( cf, model_id, model_epoch, devices)
236
+ evaluator.model.set_location( NetMode.test, pos, years, months, num_t_samples_per_month)
237
+ if 0 == cf.par_rank :
238
+ cf.print()
239
+ cf.write_json( wandb)
240
+ evaluator.evaluate( 0)
vendor/atmorep-official/atmorep/core/train.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import os
19
+ import sys
20
+ import traceback
21
+ import pdb
22
+ import wandb
23
+
24
+ from atmorep.core.trainer import Trainer_BERT
25
+ from atmorep.utils.utils import Config
26
+ from atmorep.utils.utils import setup_ddp
27
+ from atmorep.utils.utils import setup_wandb
28
+ from atmorep.utils.utils import init_torch
29
+
30
+
31
+ ####################################################################################################
32
+ def train_continue( wandb_id, epoch, Trainer, epoch_continue = -1) :
33
+
34
+ devices = init_torch()
35
+ with_ddp = True
36
+ par_rank, par_size = setup_ddp( with_ddp)
37
+
38
+ cf = Config().load_json( wandb_id)
39
+
40
+ cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
41
+ cf.with_ddp = with_ddp
42
+ cf.par_rank = par_rank
43
+ cf.par_size = par_size
44
+ cf.optimizer_zero = False
45
+ cf.attention = False
46
+ # name has changed but ensure backward compatibility
47
+ if hasattr( cf, 'loader_num_workers') :
48
+ cf.num_loader_workers = cf.loader_num_workers
49
+ if not hasattr( cf, 'n_size'):
50
+ cf.n_size = [36, 0.25*9*6, 0.25*9*12]
51
+ if not hasattr(cf, 'num_samples_per_epoch'):
52
+ cf.num_samples_per_epoch = 1024
53
+ if not hasattr(cf, 'num_samples_validate'):
54
+ cf.num_samples_validate = 128
55
+ if not hasattr(cf, 'with_mixed_precision'):
56
+ cf.with_mixed_precision = True
57
+ if not hasattr(cf, 'years_val'):
58
+ cf.years_val = cf.years_test
59
+
60
+ # any parameter in cf can be overwritten when training is continued, e.g. we can increase the
61
+ # masking rate
62
+ # cf.fields = [ [ 'specific_humidity', [ 1, 2048, [ ], 0 ],
63
+ # [ 96, 105, 114, 123, 137 ],
64
+ # [12, 6, 12], [3, 9, 9], [0.5, 0.9, 0.1, 0.05] ] ]
65
+
66
+ setup_wandb( cf.with_wandb, cf, par_rank, project_name='train', mode='offline')
67
+ # resuming a run requires online mode, which is not available everywhere
68
+ #setup_wandb( cf.with_wandb, cf, par_rank, wandb_id = wandb_id)
69
+
70
+ if cf.with_wandb and 0 == cf.par_rank :
71
+ cf.write_json( wandb)
72
+ cf.print()
73
+
74
+ if -1 == epoch_continue :
75
+ epoch_continue = epoch
76
+
77
+ # run
78
+ trainer = Trainer.load( cf, wandb_id, epoch, devices)
79
+ print( 'Loaded run \'{}\' at epoch {}.'.format( wandb_id, epoch))
80
+ trainer.run( epoch_continue)
81
+
82
+ ####################################################################################################
83
+ def train() :
84
+
85
+ devices = init_torch()
86
+ with_ddp = True
87
+ par_rank, par_size = setup_ddp( with_ddp)
88
+
89
+ # torch.cuda.set_sync_debug_mode(1)
90
+ torch.backends.cuda.matmul.allow_tf32 = True
91
+
92
+ cf = Config()
93
+ # parallelization
94
+ cf.with_ddp = with_ddp
95
+ cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
96
+ cf.par_rank = par_rank
97
+ cf.par_size = par_size
98
+
99
+ # format: list of fields where for each field the list is
100
+ # [ name ,
101
+ # [ dynamic or static field { 1, 0 }, embedding dimension, , device id ],
102
+ # [ vertical levels ],
103
+ # [ num_tokens],
104
+ # [ token size],
105
+ # [ total masking rate, rate masking, rate noising, rate for multi-res distortion]
106
+ # ]
107
+
108
+ # cf.fields = [ [ 'temperature', [ 1, 1024, [ ], 0 ],
109
+ # [ 96, 105, 114, 123, 137 ],
110
+ # [12, 2, 4], [3, 27, 27], [0.5, 0.9, 0.2, 0.05], 'local' ] ]
111
+ # cf.fields_prediction = [ [cf.fields[0][0], 1.] ]
112
+
113
+ cf.fields = [ [ 'velocity_u', [ 1, 1024, [ ], 0 ],
114
+ [ 96, 105, 114, 123, 137 ],
115
+ [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ] ]
116
+
117
+ cf.fields_prediction = [ [cf.fields[0][0], 1.] ]
118
+
119
+
120
+ # cf.fields = [ [ 'velocity_v', [ 1, 1024, [ ], 0 ],
121
+ # [ 96, 105, 114, 123, 137 ],
122
+ # [12, 3, 6], [3, 18, 18], [0.25, 0.9, 0.1, 0.05] ] ]
123
+
124
+ # cf.fields = [ [ 'velocity_z', [ 1, 1024, [ ], 0 ],
125
+ # [ 96, 105, 114, 123, 137 ],
126
+ # [12, 3, 6], [3, 18, 18], [0.25, 0.9, 0.1, 0.05] ] ]
127
+
128
+ # cf.fields = [ [ 'specific_humidity', [ 1, 1024, [ ], 0 ],
129
+ # [ 96, 105, 114, 123, 137 ],
130
+ # [12, 3, 6], [3, 18, 18], [0.25, 0.9, 0.1, 0.05] ] ]
131
+
132
+ cf.fields_targets = []
133
+
134
+ cf.years_train = list( range( 1979, 2021))
135
+ cf.years_val = [2021] #[2018]
136
+ cf.month = None
137
+ cf.geo_range_sampling = [[ -90., 90.], [ 0., 360.]]
138
+ cf.time_sampling = 1 # sampling rate for time steps
139
+ # random seeds
140
+ cf.torch_seed = torch.initial_seed()
141
+ # training params
142
+ cf.batch_size_validation = 1 #64
143
+ cf.batch_size = 96
144
+ cf.num_epochs = 400 #128
145
+ cf.num_samples_per_epoch = 4096*12
146
+ cf.num_samples_validate = 128*12
147
+ cf.num_loader_workers = 8
148
+
149
+ # additional infos
150
+ cf.size_token_info = 8
151
+ cf.size_token_info_net = 16
152
+ cf.grad_checkpointing = True
153
+ cf.with_cls = False
154
+ # network config
155
+ cf.with_mixed_precision = True
156
+ cf.with_layernorm = True
157
+ cf.coupling_num_heads_per_field = 1
158
+ cf.dropout_rate = 0.05
159
+ cf.with_qk_lnorm = False
160
+ # encoder
161
+ cf.encoder_num_layers = 6
162
+ cf.encoder_num_heads = 16
163
+ cf.encoder_num_mlp_layers = 2
164
+ cf.encoder_att_type = 'dense'
165
+ # decoder
166
+ cf.decoder_num_layers = 6
167
+ cf.decoder_num_heads = 16
168
+ cf.decoder_num_mlp_layers = 2
169
+ cf.decoder_self_att = False
170
+ cf.decoder_cross_att_ratio = 0.5
171
+ cf.decoder_cross_att_rate = 1.0
172
+ cf.decoder_att_type = 'dense'
173
+ # tail net
174
+ cf.net_tail_num_nets = 16
175
+ cf.net_tail_num_layers = 0
176
+ # loss
177
+ cf.losses = ['mse_ensemble', 'stats'] # mse, mse_ensemble, stats, crps, weighted_mse
178
+ # training
179
+ cf.optimizer_zero = False
180
+ cf.lr_start = 5. * 10e-7
181
+ cf.lr_max = 0.00005*3
182
+ cf.lr_min = 0.00004 #0.00002
183
+ cf.weight_decay = 0.05 #0.1
184
+ cf.lr_decay_rate = 1.025
185
+ cf.lr_start_epochs = 3
186
+ cf.model_log_frequency = 256 #save checkpoint every X batches
187
+ # BERT
188
+ # strategies: 'BERT', 'forecast', 'temporal_interpolation'
189
+ cf.BERT_strategy = 'BERT'
190
+ cf.forecast_num_tokens = 2 # only needed / used for BERT_strategy 'forecast
191
+ cf.BERT_fields_synced = False # apply synchronized / identical masking to all fields
192
+ # (fields need to have same BERT params for this to have effect)
193
+ cf.BERT_mr_max = 2 # maximum reduction rate for resolution
194
+
195
+ # debug / output
196
+ cf.log_test_num_ranks = 0
197
+ cf.save_grads = False
198
+ cf.profile = False
199
+ cf.test_initial = False
200
+ cf.attention = False
201
+
202
+ cf.rng_seed = None
203
+
204
+ # usually use %>wandb offline to switch to disable syncing with server
205
+ cf.with_wandb = True
206
+ setup_wandb( cf.with_wandb, cf, par_rank, 'train', mode='offline')
207
+
208
+ # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk32.zarr'
209
+ # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk32.zarr'
210
+ # # # in steps x lat_degrees x lon_degrees
211
+ # cf.n_size = [36, 1*9*6, 1.*9*12]
212
+
213
+ # # # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk16.zarr'
214
+ # # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk32.zarr'
215
+ # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk32.zarr'
216
+ # # #
217
+ # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk8.zarr'
218
+ # # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk8_lat180_lon180.zarr'
219
+ # # # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk16.zarr'
220
+ cf.file_path = '/gpfs/scratch/ehpc03/era5_y1979_2021_res025_chunk8.zarr/'
221
+ # # # in steps x lat_degrees x lon_degrees
222
+ cf.n_size = [36, 0.25*9*6, 0.25*9*12]
223
+
224
+ # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk16.zarr'
225
+ #cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk16.zarr'
226
+ #cf.n_size = [36, 1*9*6, 1.*9*12]
227
+
228
+ if cf.with_wandb and 0 == cf.par_rank :
229
+ cf.write_json( wandb)
230
+ cf.print()
231
+
232
+ trainer = Trainer_BERT( cf, devices).create()
233
+ trainer.run()
234
+
235
+ ####################################################################################################
236
+ if __name__ == '__main__':
237
+
238
+ try :
239
+
240
+ train()
241
+
242
+ # wandb_id, epoch, epoch_continue = 'gxfywjzl', 127, 127
243
+ # Trainer = Trainer_BERT
244
+ # train_continue( wandb_id, epoch, Trainer, epoch_continue)
245
+
246
+ except :
247
+
248
+ extype, value, tb = sys.exc_info()
249
+ traceback.print_exc()
250
+ pdb.post_mortem(tb)
251
+
vendor/atmorep-official/atmorep/core/train_multi.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import os
19
+ import sys
20
+ import traceback
21
+ import pdb
22
+ import wandb
23
+
24
+ import atmorep.config.config as config
25
+ from atmorep.core.trainer import Trainer_BERT
26
+ from atmorep.utils.utils import Config
27
+ from atmorep.utils.utils import setup_ddp
28
+ from atmorep.utils.utils import setup_wandb
29
+ from atmorep.utils.utils import init_torch
30
+
31
+
32
+ ####################################################################################################
33
+ def train_continue( wandb_id, epoch, Trainer, epoch_continue = -1) :
34
+
35
+ devices = init_torch()
36
+ with_ddp = True
37
+ par_rank, par_size = setup_ddp( with_ddp)
38
+
39
+ cf = Config().load_json( wandb_id)
40
+
41
+ cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
42
+ cf.with_ddp = with_ddp
43
+ cf.par_rank = par_rank
44
+ cf.par_size = par_size
45
+ cf.optimizer_zero = False
46
+ cf.attention = False
47
+ # name has changed but ensure backward compatibility
48
+ if hasattr( cf, 'loader_num_workers') :
49
+ cf.num_loader_workers = cf.loader_num_workers
50
+ if not hasattr( cf, 'n_size'):
51
+ cf.n_size = [36, 0.25*9*6, 0.25*9*12]
52
+ if not hasattr(cf, 'num_samples_per_epoch'):
53
+ cf.num_samples_per_epoch = 1024
54
+ if not hasattr(cf, 'num_samples_validate'):
55
+ cf.num_samples_validate = 128
56
+ if not hasattr(cf, 'with_mixed_precision'):
57
+ cf.with_mixed_precision = True
58
+
59
+ if not hasattr(cf, 'years_val'):
60
+ cf.years_val = cf.years_test
61
+
62
+ #cf.with_mixed_precision = False
63
+ # any parameter in cf can be overwritten when training is continued, e.g. we can increase the
64
+ # masking rate
65
+ # cf.fields = [ [ 'specific_humidity', [ 1, 2048, [ ], 0 ],
66
+ # [ 96, 105, 114, 123, 137 ],
67
+ # [12, 6, 12], [3, 9, 9], [0.5, 0.9, 0.1, 0.05] ] ]
68
+
69
+ setup_wandb( cf.with_wandb, cf, par_rank, project_name='train', mode='offline')
70
+ # resuming a run requires online mode, which is not available everywhere
71
+ #setup_wandb( cf.with_wandb, cf, par_rank, wandb_id = wandb_id)
72
+
73
+ if cf.with_wandb and 0 == cf.par_rank :
74
+ cf.write_json( wandb)
75
+ cf.print()
76
+
77
+ if -1 == epoch_continue :
78
+ epoch_continue = epoch
79
+
80
+ # run
81
+ trainer = Trainer.load( cf, wandb_id, epoch, devices)
82
+ print( 'Loaded run \'{}\' at epoch {}.'.format( wandb_id, epoch))
83
+ trainer.run( epoch_continue)
84
+
85
+ ####################################################################################################
86
+ def train() :
87
+
88
+ devices = init_torch()
89
+ with_ddp = True
90
+ par_rank, par_size = setup_ddp( with_ddp)
91
+
92
+ torch.backends.cuda.matmul.allow_tf32 = True
93
+
94
+ cf = Config()
95
+ # parallelization
96
+ cf.with_ddp = with_ddp
97
+ cf.num_accs_per_task = len(devices) # number of GPUs / accelerators per task
98
+ cf.par_rank = par_rank
99
+ cf.par_size = par_size
100
+
101
+ # format: list of fields where for each field the list is
102
+ # [ name ,
103
+ # [ dynamic or static field { 1, 0 }, embedding dimension, , device id ],
104
+ # [ vertical levels ],
105
+ # [ num_tokens],
106
+ # [ token size],
107
+ # [ total masking rate, rate masking, rate noising, rate for multi-res distortion]
108
+ # ]
109
+
110
+ # cf.fields = [
111
+ # [ 'velocity_u', [ 1, 1024, ['velocity_v', 'temperature'], 0 ],
112
+ # [ 96, 105, 114, 123, 137 ],
113
+ # [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
114
+ # [ 'velocity_v', [ 1, 1024, ['velocity_u', 'temperature'], 1 ],
115
+ # [ 96, 105, 114, 123, 137 ],
116
+ # [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
117
+ # [ 'specific_humidity', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 2 ],
118
+ # [ 96, 105, 114, 123, 137 ],
119
+ # [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
120
+ # [ 'velocity_z', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 3 ],
121
+ # [ 96, 105, 114, 123, 137 ],
122
+ # [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
123
+ # [ 'temperature', [ 1, 512, ['velocity_u', 'velocity_v', 'specific_humidity'], 3 ],
124
+ # [ 96, 105, 114, 123, 137 ],
125
+ # [12, 2, 4], [3, 27, 27], [0.5, 0.9, 0.2, 0.05], 'local' ],
126
+ # ]
127
+
128
+ cf.fields = [
129
+ [ 'velocity_u', [ 1, 1024, ['velocity_v', 'temperature'], 0, ['j8dwr5qj', -2] ],
130
+ [ 96, 105, 114, 123, 137 ],
131
+ [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
132
+ [ 'velocity_v', [ 1, 1024, ['velocity_u', 'temperature'], 1, ['0tlnm5up', -2] ],
133
+ [ 96, 105, 114, 123, 137 ],
134
+ [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
135
+ [ 'specific_humidity', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 2, ['v63l01zu', -2] ],
136
+ [ 96, 105, 114, 123, 137 ],
137
+ [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
138
+ [ 'velocity_z', [ 1, 1024, ['velocity_u', 'velocity_v', 'temperature'], 3, ['9l1errbo', -2] ],
139
+ [ 96, 105, 114, 123, 137 ],
140
+ [12, 3, 6], [3, 18, 18], [0.5, 0.9, 0.2, 0.05] ],
141
+ [ 'temperature', [ 1, 1024, ['velocity_u', 'velocity_v', 'specific_humidity'], 3, ['7ojls62c', -2] ],
142
+ [ 96, 105, 114, 123, 137 ],
143
+ [12, 2, 4], [3, 27, 27], [0.5, 0.9, 0.2, 0.05], 'local' ],
144
+ # ['total_precip', [1, 1536, ['velocity_u', 'velocity_v', 'velocity_z', 'specific_humidity'], 3, ['3kdutwqb', 900]],
145
+ # [0],
146
+ # [12, 6, 12], [3, 9, 9], [0.25, 0.9, 0.1, 0.05]]
147
+ ]
148
+
149
+ cf.fields_prediction = [
150
+ ['velocity_u', 0.225], ['velocity_v', 0.225],
151
+ ['specific_humidity', 0.15], ['velocity_z', 0.1], ['temperature', 0.2]
152
+ # ['total_precip', 0.1]
153
+ ]
154
+
155
+ cf.fields_targets = []
156
+
157
+ cf.years_train = list( range( 1979, 2021))
158
+ cf.years_val = [2021] #[2018]
159
+ cf.month = None
160
+ cf.geo_range_sampling = [[ -90., 90.], [ 0., 360.]]
161
+ cf.time_sampling = 1 # sampling rate for time steps
162
+ # random seeds
163
+ cf.torch_seed = torch.initial_seed()
164
+ # training params
165
+ cf.batch_size_validation = 1 #64
166
+
167
+ cf.batch_size = 96
168
+ cf.num_epochs = 128
169
+ cf.num_samples_per_epoch = 4096*12
170
+ cf.num_samples_validate = 128*12
171
+ cf.num_loader_workers = 5
172
+
173
+ # additional infos
174
+ cf.size_token_info = 8
175
+ cf.size_token_info_net = 16
176
+ cf.grad_checkpointing = True
177
+ cf.with_cls = False
178
+ # network config
179
+ cf.with_mixed_precision = True
180
+ cf.with_layernorm = True
181
+ cf.coupling_num_heads_per_field = 1
182
+ cf.dropout_rate = 0.05
183
+ cf.with_qk_lnorm = True
184
+ # encoder
185
+ cf.encoder_num_layers = 6
186
+ cf.encoder_num_heads = 16
187
+ cf.encoder_num_mlp_layers = 2
188
+ cf.encoder_att_type = 'dense'
189
+ # decoder
190
+ cf.decoder_num_layers = 6
191
+ cf.decoder_num_heads = 16
192
+ cf.decoder_num_mlp_layers = 2
193
+ cf.decoder_self_att = False
194
+ cf.decoder_cross_att_ratio = 0.5
195
+ cf.decoder_cross_att_rate = 1.0
196
+ cf.decoder_att_type = 'dense'
197
+ # tail net
198
+ cf.net_tail_num_nets = 16
199
+ cf.net_tail_num_layers = 0
200
+ # loss
201
+ cf.losses = ['mse_ensemble', 'stats'] # mse, mse_ensemble, stats, crps, weighted_mse
202
+ # training
203
+ cf.optimizer_zero = False
204
+
205
+ cf.lr_start = 0.00001 #5. * 10e-7
206
+ cf.lr_max = 0.00002
207
+ cf.lr_min = 0.00001
208
+ cf.weight_decay = 0.025 #0.1
209
+ cf.lr_decay_rate = 1.025
210
+ cf.lr_start_epochs = 3
211
+ cf.model_log_frequency = 256 #save checkpoint every X batches
212
+
213
+ # BERT
214
+ # strategies: 'BERT', 'forecast', 'temporal_interpolation'
215
+ cf.BERT_strategy = 'BERT' #'BERT'
216
+ cf.forecast_num_tokens = 2 # only needed / used for BERT_strategy 'forecast
217
+
218
+ cf.BERT_fields_synced = False # apply synchronized / identical masking to all fields
219
+ # (fields need to have same BERT params for this to have effect)
220
+ cf.BERT_mr_max = 2 # maximum reduction rate for resolution
221
+
222
+ # debug / output
223
+ cf.log_test_num_ranks = 0
224
+ cf.save_grads = False
225
+ cf.profile = False
226
+ cf.test_initial = False
227
+ cf.attention = False
228
+
229
+ cf.rng_seed = None
230
+
231
+ # usually use %>wandb offline to switch to disable syncing with server
232
+ cf.with_wandb = True
233
+ setup_wandb( cf.with_wandb, cf, par_rank, 'train', mode='offline')
234
+
235
+ # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk32.zarr'
236
+ # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk32.zarr'
237
+ # # # in steps x lat_degrees x lon_degrees
238
+ # cf.n_size = [36, 1*9*6, 1.*9*12]
239
+
240
+ # # # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk16.zarr'
241
+ # # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk32.zarr'
242
+ # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk32.zarr'
243
+ # # #
244
+ # # # cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res025_chunk8.zarr'
245
+ # # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk8_lat180_lon180.zarr'
246
+ # # # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res025_chunk16.zarr'
247
+ cf.file_path = '/gpfs/scratch/ehpc03/era5_y1979_2021_res025_chunk8.zarr/'
248
+ # # # in steps x lat_degrees x lon_degrees
249
+ cf.n_size = [36, 0.25*9*6, 0.25*9*12]
250
+
251
+ # cf.file_path = '/ec/res4/scratch/nacl/atmorep/era5_y2021_res100_chunk16.zarr'
252
+ #cf.file_path = '/p/scratch/atmo-rep/data/era5_1deg/months/era5_y2021_res100_chunk16.zarr'
253
+ #cf.n_size = [36, 1*9*6, 1.*9*12]
254
+
255
+ if cf.with_wandb and 0 == cf.par_rank :
256
+ cf.write_json( wandb)
257
+ cf.print()
258
+
259
+ trainer = Trainer_BERT( cf, devices).create()
260
+ trainer.run()
261
+
262
+ ####################################################################################################
263
+ if __name__ == '__main__':
264
+
265
+ try :
266
+
267
+ train()
268
+
269
+ # wandb_id, epoch, epoch_continue = 'uvrdtc0a', 95, 95 #multiformer from scratch
270
+ # Trainer = Trainer_BERT
271
+ # train_continue( wandb_id, epoch, Trainer, epoch_continue)
272
+
273
+ except :
274
+
275
+ extype, value, tb = sys.exc_info()
276
+ traceback.print_exc()
277
+ pdb.post_mortem(tb)
vendor/atmorep-official/atmorep/core/trainer.py ADDED
@@ -0,0 +1,863 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import torchinfo
19
+ import numpy as np
20
+ import time
21
+ import code
22
+
23
+ from pathlib import Path
24
+ import os
25
+ import datetime
26
+ import functools
27
+
28
+ import wandb
29
+ # import horovod.torch as hvd
30
+ import torch.distributed as dist
31
+ from torch.distributed.optim import ZeroRedundancyOptimizer
32
+ import torch.utils.data.distributed
33
+
34
+ import atmorep.config.config as config
35
+
36
+ from atmorep.core.atmorep_model import AtmoRep
37
+ from atmorep.core.atmorep_model import AtmoRepData
38
+ from atmorep.training.bert import prepare_batch_BERT_multifield
39
+ from atmorep.transformer.transformer_base import positional_encoding_harmonic
40
+
41
+ import atmorep.utils.token_infos_transformations as token_infos_transformations
42
+
43
+ from atmorep.utils.utils import Gaussian, CRPS, kernel_crps, weighted_mse, NetMode, tokenize, detokenize
44
+ from atmorep.datasets.data_writer import write_forecast, write_BERT, write_attention
45
+ from atmorep.datasets.normalizer import denormalize
46
+
47
+ ####################################################################################################
48
+ class Trainer_Base() :
49
+
50
+ def __init__( self, cf, devices ) :
51
+
52
+ self.cf = cf
53
+ self.devices = devices
54
+ self.device_in = devices[0]
55
+ self.device_out = devices[-1]
56
+
57
+ self.fields_prediction_idx = []
58
+ self.loss_weights = torch.zeros( len(cf.fields_prediction) )
59
+ for ifield, field in enumerate(cf.fields_prediction) :
60
+ self.loss_weights[ifield] = self.cf.fields_prediction[ifield][1]
61
+ for idx, field_info in enumerate(cf.fields) :
62
+ if field_info[0] == field[0] :
63
+ self.fields_prediction_idx.append( idx)
64
+ break
65
+ self.loss_weights = self.loss_weights.to( self.device_out)
66
+
67
+ self.MSELoss = torch.nn.MSELoss()
68
+
69
+ # transformation for token infos
70
+ if hasattr( cf, 'token_infos_transformation') :
71
+ self.tok_infos_trans = getattr( token_infos_transformations, cf.token_infos_transformation)
72
+ else :
73
+ self.tok_infos_trans = getattr( token_infos_transformations, 'identity')
74
+
75
+ if 0 == cf.par_rank :
76
+ directory = Path( config.path_results, 'id{}'.format( cf.wandb_id))
77
+ if not os.path.exists(directory):
78
+ os.makedirs( directory)
79
+ directory = Path( config.path_models, 'id{}'.format( cf.wandb_id))
80
+ if not os.path.exists(directory):
81
+ os.makedirs( directory)
82
+
83
+ ###################################################
84
+ def create( self, load_embeds=True) :
85
+ net = AtmoRep( self.cf)
86
+ self.model = AtmoRepData( net)
87
+
88
+ self.model.create( self.pre_batch, self.devices, load_embeds)
89
+
90
+ # TODO: pass the properly to model / net
91
+ self.model.net.encoder_to_decoder = self.encoder_to_decoder
92
+ self.model.net.decoder_to_tail = self.decoder_to_tail
93
+ return self
94
+
95
+ ###################################################
96
+ @classmethod
97
+ def load( Typename, cf, model_id, epoch, devices) :
98
+ trainer = Typename( cf, devices).create( load_embeds=False)
99
+ trainer.model.net = trainer.model.net.load( model_id, devices, cf, epoch)
100
+
101
+ # TODO: pass the properly to model / net
102
+ trainer.model.net.encoder_to_decoder = trainer.encoder_to_decoder
103
+ trainer.model.net.decoder_to_tail = trainer.decoder_to_tail
104
+
105
+ str = 'Loaded model id = {}{}.'.format( model_id, f' at epoch = {epoch}' if epoch> -2 else '')
106
+ print( str)
107
+ return trainer
108
+
109
+ ###################################################
110
+ def save( self, epoch) :
111
+ self.model.net.save( epoch)
112
+
113
+ ###################################################
114
+ def get_learn_rates( self) :
115
+
116
+ cf = self.cf
117
+ size_padding = 5
118
+ learn_rates = np.zeros( cf.num_epochs + size_padding)
119
+
120
+ learn_rates[:cf.lr_start_epochs] = np.linspace( cf.lr_start, cf.lr_max, num = cf.lr_start_epochs)
121
+ lr = learn_rates[cf.lr_start_epochs-1]
122
+ ic = 0
123
+ for epoch in range( cf.lr_start_epochs, cf.num_epochs + size_padding) :
124
+ lr = max( lr / cf.lr_decay_rate, cf.lr_min)
125
+ learn_rates[epoch] = lr
126
+ if ic > 9999 : # sanity check
127
+ assert "Maximum number of epochs exceeded."
128
+
129
+ return learn_rates
130
+
131
+ ###################################################
132
+ def run( self, epoch = -1) :
133
+
134
+ cf = self.cf
135
+ model = self.model
136
+
137
+ learn_rates = self.get_learn_rates()
138
+
139
+ if cf.with_ddp :
140
+ self.model_ddp = torch.nn.parallel.DistributedDataParallel( model, static_graph=True)
141
+ if not cf.optimizer_zero :
142
+ self.optimizer = torch.optim.AdamW( self.model_ddp.parameters(), lr=cf.lr_start,
143
+ weight_decay=cf.weight_decay)
144
+ else :
145
+ self.optimizer = ZeroRedundancyOptimizer(self.model_ddp.parameters(),
146
+ optimizer_class=torch.optim.AdamW,
147
+ lr=cf.lr_start )
148
+ else :
149
+ self.optimizer = torch.optim.AdamW( self.model.parameters(), lr=cf.lr_start,
150
+ weight_decay=cf.weight_decay)
151
+
152
+ self.grad_scaler = torch.cuda.amp.GradScaler(enabled=cf.with_mixed_precision)
153
+
154
+ if 0 == cf.par_rank :
155
+ # print( self.model.net)
156
+ model_parameters = filter(lambda p: p.requires_grad, self.model_ddp.parameters())
157
+ num_params = sum([np.prod(p.size()) for p in model_parameters])
158
+ print( f'Number of trainable parameters: {num_params:,}')
159
+
160
+ if cf.test_initial :
161
+ cur_test_loss = self.validate( epoch, cf.BERT_strategy).cpu().numpy()
162
+ test_loss = np.array( [cur_test_loss])
163
+ else :
164
+ # generic value based on data normalization
165
+ test_loss = np.array( [1.0])
166
+ epoch += 1
167
+
168
+ if cf.profile :
169
+ lr = learn_rates[epoch]
170
+ for g in self.optimizer.param_groups:
171
+ g['lr'] = lr
172
+ self.profile()
173
+
174
+ # training loop
175
+ while True :
176
+
177
+ if epoch >= cf.num_epochs :
178
+ break
179
+
180
+ lr = learn_rates[epoch]
181
+ for g in self.optimizer.param_groups:
182
+ g['lr'] = lr
183
+
184
+ tstr = datetime.datetime.now().strftime("%H:%M:%S")
185
+ print( '{} : {} :: batch_size = {}, lr = {}'.format( epoch, tstr, cf.batch_size, lr) )
186
+
187
+ self.train( epoch)
188
+
189
+ if cf.with_wandb and 0 == cf.par_rank :
190
+ self.save( epoch)
191
+
192
+ cur_test_loss = self.validate( epoch, cf.BERT_strategy).cpu().numpy()
193
+ # self.validate( epoch, 'forecast')
194
+
195
+ # save model
196
+ if cur_test_loss < test_loss.min() :
197
+ self.save( -2)
198
+ test_loss = np.append( test_loss, [cur_test_loss])
199
+
200
+ epoch += 1
201
+
202
+ tstr = datetime.datetime.now().strftime("%H:%M:%S")
203
+ print( 'Finished training at {} with test loss = {}.'.format( tstr, test_loss[-1]) )
204
+
205
+ # save final network
206
+ if cf.with_wandb and 0 == cf.par_rank :
207
+ self.save( -2)
208
+
209
+ ###################################################
210
+ def train( self, epoch):
211
+
212
+ model = self.model
213
+ cf = self.cf
214
+
215
+ model.mode( NetMode.train)
216
+ self.optimizer.zero_grad()
217
+
218
+ loss_total = [[] for i in range(len(cf.losses)) ]
219
+ std_dev_total = [[] for i in range(len(self.fields_prediction_idx)) ]
220
+ mse_loss_total = []
221
+ grad_loss_total = []
222
+ ctr = 0
223
+
224
+ self.optimizer.zero_grad()
225
+ time_start = time.time()
226
+
227
+ for batch_idx in range( model.len( NetMode.train)) :
228
+
229
+ batch_data = self.model.next()
230
+ _, _, _, tmksd_list, weight_list = batch_data[0]
231
+ with torch.autocast(device_type='cuda', dtype=torch.float16, enabled=cf.with_mixed_precision):
232
+ batch_data = self.prepare_batch( batch_data)
233
+ preds, _ = self.model_ddp( batch_data)
234
+ loss, mse_loss, losses = self.loss( preds, batch_idx, tmksd_list, weight_list)
235
+
236
+ self.grad_scaler.scale(loss).backward()
237
+ self.grad_scaler.step(self.optimizer)
238
+ self.grad_scaler.update()
239
+
240
+ self.optimizer.zero_grad()
241
+
242
+ [loss_total[idx].append( losses[key]) for idx, key in enumerate(losses)]
243
+ mse_loss_total.append( mse_loss.detach().cpu() )
244
+ grad_loss_total.append( loss.detach().cpu() )
245
+ [std_dev_total[idx].append( pred[1].detach().cpu()) for idx, pred in enumerate(preds)]
246
+
247
+ # logging
248
+
249
+ if int((batch_idx * cf.batch_size) / 8) > ctr :
250
+
251
+ # wandb logging
252
+ if cf.with_wandb and (0 == cf.par_rank) :
253
+ loss_dict = { "training loss": torch.mean( torch.tensor( mse_loss_total)),
254
+ "gradient loss": torch.mean( torch.tensor( grad_loss_total)) }
255
+ # log individual loss terms for individual fields
256
+ for idx, cur_loss in enumerate(loss_total) :
257
+ loss_name = self.cf.losses[idx]
258
+ lt = torch.tensor(cur_loss)
259
+ for i, field in enumerate(cf.fields_prediction) :
260
+ idx_name = loss_name + ', ' + field[0]
261
+ idx_std_name = 'stddev, ' + field[0]
262
+ loss_dict[idx_name] = torch.mean( lt[:,i]).cpu().detach()
263
+ loss_dict[idx_std_name] = torch.mean(torch.cat(std_dev_total[i],0)).cpu().detach()
264
+ wandb.log( loss_dict )
265
+
266
+ # console output
267
+ samples_sec = cf.batch_size / (time.time() - time_start)
268
+ str = 'epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:1.5f} : {:1.5f} :: {:1.5f} ({:2.2f} s/sec)'
269
+ print( str.format( epoch, batch_idx, model.len( NetMode.train),
270
+ 100. * batch_idx/model.len(NetMode.train),
271
+ torch.mean( torch.tensor( grad_loss_total)),
272
+ torch.mean(torch.tensor(mse_loss_total)),
273
+ torch.mean( preds[0][1]), samples_sec ), flush=True)
274
+
275
+ # save model (use -2 as epoch to indicate latest, stored without epoch specification)
276
+ if batch_idx % cf.model_log_frequency == 0 :
277
+ self.save( -2)
278
+
279
+ # reset
280
+ loss_total = [[] for i in range(len(cf.losses)) ]
281
+ mse_loss_total = []
282
+ grad_loss_total = []
283
+ std_dev_total = [[] for i in range(len(self.fields_prediction_idx)) ]
284
+
285
+ ctr += 1
286
+ time_start = time.time()
287
+
288
+ # save gradients
289
+ if cf.save_grads and cf.with_wandb and (0 == cf.par_rank) :
290
+
291
+ dir_name = './grads/id{}'.format( cf.wandb_id)
292
+ if not os.path.exists(dir_name):
293
+ os.makedirs(dir_name)
294
+
295
+ rmsprop_ws = []
296
+ for k in range( len(self.optimizer.state_dict()['state']) ) :
297
+ rmsprop_ws.append(self.optimizer.state_dict()['state'][k]['exp_avg_sq'].mean().unsqueeze(0))
298
+ rmsprop_ws = torch.cat( rmsprop_ws)
299
+ fname = '{}/{}_epoch{}_rmsprop.npy'.format( dir_name, cf.wandb_id, epoch)
300
+ np.save( fname, rmsprop_ws.cpu().detach().numpy() )
301
+
302
+ idx = 0
303
+ for name, param in self.model.named_parameters():
304
+ if param.requires_grad :
305
+ fname = '{}/{}_epoch{}_{:05d}_{}_grad.npy'.format( dir_name, cf.wandb_id, epoch, idx,name)
306
+ np.save( fname, param.grad.cpu().detach().numpy() )
307
+ idx += 1
308
+
309
+ # clean memory
310
+ self.optimizer.zero_grad()
311
+ del batch_data, loss, loss_total, mse_loss_total, grad_loss_total, std_dev_total
312
+
313
+ ###################################################
314
+ def profile( self):
315
+
316
+ model = self.model
317
+ cf = self.cf
318
+
319
+ model.mode( NetMode.train)
320
+ self.optimizer.zero_grad()
321
+
322
+ # See https://pytorch.org/tutorials/intermediate/tensorboard_profiler_tutorial.html
323
+ # for details on how to load and analyse report
324
+ # https://pytorch.org/blog/trace-analysis-for-masses/
325
+
326
+ # do for all par_ranks to avoid that they run out of sync
327
+ print( '---------------------------------')
328
+ print( 'Profiling:')
329
+ pname = './logs/profile_par_rank' + str(cf.par_rank) + '_' + cf.wandb_id + '/profile'
330
+ with torch.profiler.profile( activities=[torch.profiler.ProfilerActivity.CPU,
331
+ torch.profiler.ProfilerActivity.CUDA],
332
+ schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=2),
333
+ on_trace_ready=torch.profiler.tensorboard_trace_handler(pname),
334
+ profile_memory=True, record_shapes=True, with_stack=True) as prof:
335
+ for batch_idx in range( 2 * (1+1+3) ) :
336
+
337
+ batch_data = self.model.next()
338
+
339
+ with torch.autocast(device_type='cuda',dtype=torch.float16, enabled=cf.with_mixed_precision):
340
+ batch_data = self.prepare_batch( batch_data)
341
+ preds, _ = self.model_ddp( batch_data)
342
+ loss, mse_loss, losses = self.loss( preds, batch_idx)
343
+
344
+ self.grad_scaler.scale(loss).backward()
345
+ self.grad_scaler.step(self.optimizer)
346
+ self.grad_scaler.update()
347
+ self.optimizer.zero_grad()
348
+
349
+ prof.step()
350
+
351
+ print( 'Profiling finished.')
352
+ print( '---------------------------------')
353
+
354
+ ###################################################
355
+ def validate( self, epoch, BERT_test_strategy = 'BERT'):
356
+
357
+ cf = self.cf
358
+ BERT_strategy_train = cf.BERT_strategy
359
+ cf.BERT_strategy = BERT_test_strategy
360
+ self.model.mode( NetMode.test)
361
+ total_loss = 0.
362
+ total_losses = torch.zeros( len(self.fields_prediction_idx) )
363
+ test_len = 0
364
+
365
+ self.mode_test = True
366
+
367
+ # run test set evaluation
368
+ with torch.no_grad() :
369
+ for it in range( self.model.len( NetMode.test)) :
370
+ batch_data = self.model.next()
371
+ if cf.par_rank < cf.log_test_num_ranks :
372
+ # keep on cpu since it will otherwise clog up GPU memory
373
+ (sources, _ , targets, tmis_list, _) = batch_data[0]
374
+ log_sources = ( [source.detach().clone().cpu() for source in sources ],
375
+ [target.detach().clone().cpu() for target in targets ],
376
+ tmis_list)
377
+
378
+ with torch.autocast(device_type='cuda',dtype=torch.float16,enabled=cf.with_mixed_precision):
379
+ batch_data = self.prepare_batch( batch_data)
380
+ preds, atts = self.model( batch_data)
381
+ loss = torch.tensor( 0.)
382
+ ifield = 0
383
+ for pred, idx in zip( preds, self.fields_prediction_idx) :
384
+
385
+ target = self.targets[idx]
386
+ # hook for custom test loss
387
+ self.test_loss( pred, target)
388
+ # base line loss
389
+ cur_loss = self.MSELoss( pred[0], target = target ).cpu().item()
390
+
391
+ loss += cur_loss
392
+ total_losses[ifield] += cur_loss
393
+ ifield += 1
394
+
395
+ total_loss += loss
396
+ test_len += 1
397
+
398
+ # store detailed results on current test set for book keeping
399
+ if cf.par_rank < cf.log_test_num_ranks :
400
+ log_preds = [[p.detach().clone().cpu() for p in pred] for pred in preds]
401
+ self.log_validate( epoch, it, log_sources, log_preds)
402
+ if cf.attention:
403
+ self.log_attention( epoch, it, atts)
404
+
405
+ # average over all nodes
406
+ total_loss /= test_len * len(self.cf.fields_prediction)
407
+ total_losses /= test_len
408
+
409
+ if cf.with_ddp :
410
+ total_loss_cuda = total_loss.cuda()
411
+ total_losses_cuda = total_losses.cuda()
412
+ dist.all_reduce( total_loss_cuda, op=torch.distributed.ReduceOp.AVG )
413
+ dist.all_reduce( total_losses_cuda, op=torch.distributed.ReduceOp.AVG )
414
+ total_loss = total_loss_cuda.cpu()
415
+ total_losses = total_losses_cuda.cpu()
416
+
417
+ if 0 == cf.par_rank :
418
+ print( 'validation loss for strategy={} at epoch {} : {}'.format( BERT_test_strategy,
419
+ epoch, total_loss),
420
+ flush=True)
421
+ if cf.with_wandb and (0 == cf.par_rank) :
422
+ loss_dict = {"val. loss {}".format(BERT_test_strategy) : total_loss}
423
+ total_losses = total_losses.cpu().detach()
424
+ for i, field in enumerate(cf.fields_prediction) :
425
+ idx_name = 'val., {}, '.format(BERT_test_strategy) + field[0]
426
+ loss_dict[idx_name] = total_losses[i]
427
+ print( 'validation loss for {} : {}'.format( field[0], total_losses[i] ))
428
+ wandb.log( loss_dict)
429
+ batch_data = []
430
+ torch.cuda.empty_cache()
431
+
432
+ cf.BERT_strategy = BERT_strategy_train
433
+ self.mode_test = False
434
+
435
+ return total_loss
436
+
437
+ ###################################################
438
+ def test_loss( self, pred, target) :
439
+ '''Hook for custom test loss'''
440
+ pass
441
+
442
+ ###################################################
443
+ def loss( self, preds, batch_idx = 0, tmidx_list = None, weights_list = None) :
444
+
445
+ # TODO: move implementations to individual files
446
+
447
+ cf = self.cf
448
+ mse_loss_total = torch.tensor( 0.,)
449
+ losses = dict(zip(cf.losses,[[] for loss in cf.losses ]))
450
+
451
+ for pred, idx in zip( preds, self.fields_prediction_idx) :
452
+ target = self.targets[idx]
453
+
454
+ mse_loss = self.MSELoss( pred[0], target = target)
455
+ mse_loss_total += mse_loss.cpu().detach()
456
+
457
+ # MSE loss
458
+ if 'mse' in self.cf.losses :
459
+ losses['mse'].append( mse_loss)
460
+
461
+ # MSE loss
462
+ if 'mse_ensemble' in self.cf.losses :
463
+ loss_en = torch.tensor( 0., device=target.device)
464
+ for en in torch.transpose( pred[2], 1, 0) :
465
+ loss_en += self.MSELoss( en, target = target)
466
+ losses['mse_ensemble'].append( loss_en / pred[2].shape[1])
467
+
468
+ if 'weighted_mse' in self.cf.losses :
469
+ loss_en = torch.tensor( 0., device=target.device)
470
+ field_info = cf.fields[idx]
471
+ token_size = field_info[4]
472
+
473
+ weights = torch.Tensor(np.array([w for batch in weights_list[idx] for w in batch]))
474
+ weights = weights.view(*weights.shape, 1, 1).repeat(1, 1, token_size[0], token_size[2]).swapaxes(1, 2)
475
+ weights = weights.reshape([weights.shape[0], -1]).to(target.get_device())
476
+
477
+ for en in torch.transpose( pred[2], 1, 0) :
478
+ loss_en += weighted_mse( en, target, weights)
479
+
480
+ losses['weighted_mse'].append( loss_en / pred[2].shape[1])
481
+
482
+ # Generalized cross entroy loss for continuous distributions
483
+ if 'stats' in self.cf.losses :
484
+ stats_loss = Gaussian( target, pred[0], pred[1])
485
+ diff = (stats_loss-1.)
486
+ # stats_loss = 0.01 * torch.mean( diff * diff) + torch.mean( torch.sqrt(torch.abs( pred[1])) )
487
+ stats_loss = torch.mean( diff * diff) + torch.mean( torch.sqrt( torch.abs( pred[1])) )
488
+ losses['stats'].append( stats_loss)
489
+
490
+ # Generalized cross entroy loss for continuous distributions
491
+ if 'stats_area' in self.cf.losses :
492
+ diff = torch.abs( torch.special.erf( (target - pred[0]) / (pred[1] * pred[1])) )
493
+ stats_area = 0.2 * torch.mean( diff * diff) + torch.mean( torch.sqrt(torch.abs( pred[1])) )
494
+ losses['stats_area'].append( stats_area)
495
+
496
+ # CRPS score
497
+ if 'crps' in self.cf.losses :
498
+ crps_loss = torch.mean( CRPS( target, pred[0], pred[1]))
499
+ losses['crps'].append( crps_loss)
500
+
501
+ if 'kernel_crps' in self.cf.losses :
502
+ kcrps_loss = torch.mean( kernel_crps( target,torch.transpose( pred[2], 1, 0)))
503
+ losses['kernel_crps'].append( kcrps_loss)
504
+
505
+ #TODO: uncomment it and add it when running in debug mode
506
+ # field_losses = ""
507
+ # for ifield, field in enumerate(cf.fields):
508
+ # ifield_loss = 0
509
+ # for key in losses :
510
+ # ifield_loss += losses[key][ifield].to(self.device_out)
511
+ # ifield_loss /= len(losses.keys())
512
+ # field_losses += f"{field[0]}: {ifield_loss}; "
513
+ # print(field_losses, flush = True)
514
+
515
+ loss = torch.tensor( 0., device=self.device_out)
516
+ tot_weight = torch.tensor( 0., device=self.device_out)
517
+ for key in losses :
518
+ #print( 'LOSS : {} :: {}'.format( key, losses[key]))
519
+ for ifield, val in enumerate(losses[key]) :
520
+ loss += self.loss_weights[ifield] * val.to( self.device_out)
521
+ tot_weight += self.loss_weights[ifield]
522
+ loss /= tot_weight
523
+ mse_loss = mse_loss_total / len(self.cf.fields_prediction)
524
+
525
+ return loss, mse_loss, losses
526
+
527
+ ####################################################################################################
528
+ class Trainer_BERT( Trainer_Base) :
529
+
530
+ ###################################################
531
+ def __init__( self, cf, devices) :
532
+
533
+ Trainer_Base.__init__( self, cf, devices)
534
+
535
+ self.rng_seed = cf.rng_seed
536
+ if not self.rng_seed :
537
+ self.rng_seed = int(torch.randint( 100000000, (1,)))
538
+ # TODO: generate only rngs that are needed
539
+ ll = len(cf.fields) * 8 #len(cf.vertical_levels)
540
+ if cf.BERT_fields_synced :
541
+ self.rngs = [np.random.default_rng(self.rng_seed) for _ in range(ll)]
542
+ else :
543
+ self.rngs = [np.random.default_rng(self.rng_seed+i) for i in range(ll)]
544
+
545
+ # batch preprocessing to be done in loader (mainly for performance reasons since it's
546
+ # parallelized there)
547
+ self.pre_batch = functools.partial( prepare_batch_BERT_multifield, self.cf, self.rngs,
548
+ self.cf.fields, self.cf.BERT_strategy )
549
+
550
+ ###################################################
551
+ def prepare_batch( self, xin) :
552
+ '''Move data to device and some additional final preprocessing before model eval'''
553
+
554
+ cf = self.cf
555
+ devs = self.devices
556
+
557
+ # unpack loader output
558
+ # xin[0] since BERT does not have targets
559
+ (sources, token_infos, targets, fields_tokens_masked_idx_list, _) = xin[0]
560
+ (self.sources_idxs, self.sources_info) = xin[2]
561
+
562
+ # network input
563
+ batch_data = [ ( sources[i].to( devs[ cf.fields[i][1][3] ], non_blocking=True),
564
+ self.tok_infos_trans(token_infos[i]).to( self.devices[0], non_blocking=True))
565
+ for i in range(len(sources)) ]
566
+
567
+ # store token number since BERT selects sub-cube (optionally)
568
+ self.num_tokens = []
569
+ for field_idx in range(len(batch_data)) :
570
+ self.num_tokens.append( list(batch_data[field_idx][0].shape[2:5]))
571
+
572
+ # target
573
+ self.targets = []
574
+ for ifield in self.fields_prediction_idx :
575
+ self.targets.append( targets[ifield].to( devs[cf.fields[ifield][1][3]], non_blocking=True ))
576
+
577
+ # idxs of masked tokens
578
+ tmi_out = [ ]
579
+ for i,tmi in enumerate(fields_tokens_masked_idx_list) :
580
+ cdev = devs[cf.fields[i][1][3]]
581
+ tmi_out += [ [torch.cat(tmi_l).to( cdev, non_blocking=True) for tmi_l in tmi] ]
582
+ self.tokens_masked_idx = tmi_out
583
+
584
+ return batch_data
585
+
586
+ ###################################################
587
+ def encoder_to_decoder( self, embeds_layers) :
588
+ return ([embeds_layers[i][-1] for i in range(len(embeds_layers))] , embeds_layers )
589
+
590
+ ###################################################
591
+ def decoder_to_tail( self, idx_pred, pred) :
592
+ '''Positional encoding of masked tokens for tail network evaluation'''
593
+
594
+ field_idx = self.fields_prediction_idx[idx_pred]
595
+ dev = self.devices[ self.cf.fields[field_idx][1][3] ]
596
+ target_idx = self.tokens_masked_idx[field_idx]
597
+ assert len(target_idx) > 0, 'no masked tokens but target variable'
598
+
599
+ # select "fixed" masked tokens for loss computation
600
+
601
+ # flatten token dimensions: remove space-time separation
602
+ pred = torch.flatten( pred, 2, 3).to( dev)
603
+ # extract masked token level by level
604
+ pred_masked = []
605
+ for lidx, level in enumerate(self.cf.fields[field_idx][2]) :
606
+ # select masked tokens, flattened along batch dimension for easier indexing and processing
607
+ pred_l = torch.flatten( pred[:,lidx], 0, 1)
608
+ pred_masked.append( pred_l[ target_idx[lidx] ])
609
+
610
+ # flatten along level dimension, for loss evaluation we effectively have level, batch, ...
611
+ # as ordering of dimensions
612
+ pred_masked = torch.cat( pred_masked, 0)
613
+
614
+ return pred_masked
615
+
616
+ ###################################################
617
+ def log_validate( self, epoch, bidx, log_sources, log_preds) :
618
+ '''Hook for logging: output associated with concrete training strategy.'''
619
+
620
+ if not hasattr( self.cf, 'wandb_id') :
621
+ return
622
+
623
+ if 'forecast' in self.cf.BERT_strategy :
624
+ self.log_validate_forecast( epoch, bidx, log_sources, log_preds)
625
+ elif 'BERT' in self.cf.BERT_strategy or 'temporal_interpolation' == self.cf.BERT_strategy :
626
+ self.log_validate_BERT( epoch, bidx, log_sources, log_preds)
627
+ else :
628
+ assert False
629
+
630
+ ###################################################
631
+ def log_validate_forecast( self, epoch, batch_idx, log_sources, log_preds) :
632
+ '''Logging for BERT_strategy=forecast.'''
633
+
634
+ cf = self.cf
635
+
636
+ # save source: remains identical so just save ones
637
+ (sources, targets, _) = log_sources
638
+
639
+ sources_out, targets_out, preds_out, ensembles_out = [ ], [ ], [ ], [ ]
640
+ batch_size = len(self.sources_info)
641
+ # reconstruct geo-coords (identical for all fields)
642
+ forecast_num_tokens = 1
643
+ if hasattr( cf, 'forecast_num_tokens') :
644
+ forecast_num_tokens = cf.forecast_num_tokens
645
+
646
+ coords = []
647
+ for fidx, field_info in enumerate(cf.fields) :
648
+ # reshape from tokens to contiguous physical field
649
+ num_levels = len(field_info[2])
650
+ source = detokenize( sources[fidx].cpu().detach().numpy())
651
+ # recover tokenized shape
652
+ target = detokenize( targets[fidx].cpu().detach().numpy().reshape( [ num_levels, -1,
653
+ forecast_num_tokens, *field_info[3][1:], *field_info[4] ]).swapaxes(0,1))
654
+
655
+ coords_b = []
656
+
657
+ for bidx in range(batch_size):
658
+ dates = self.sources_info[bidx][0]
659
+ lats = self.sources_info[bidx][1]
660
+ lons = self.sources_info[bidx][2]
661
+ dates_t = self.sources_info[bidx][0][ -forecast_num_tokens*field_info[4][0] : ]
662
+
663
+ lats_idx = self.sources_idxs[bidx][1]
664
+ lons_idx = self.sources_idxs[bidx][2]
665
+
666
+ for vidx, _ in enumerate(field_info[2]) :
667
+ normalizer, year_base = self.model.normalizer( fidx, vidx, lats_idx, lons_idx)
668
+ source[bidx,vidx] = denormalize( source[bidx,vidx], normalizer, dates, year_base)
669
+ target[bidx,vidx] = denormalize( target[bidx,vidx], normalizer, dates_t, year_base)
670
+
671
+ coords_b += [[dates, 90.-lats, lons, dates_t]]
672
+
673
+ # append
674
+ sources_out.append( [field_info[0], source])
675
+ targets_out.append( [field_info[0], target])
676
+ coords.append(coords_b)
677
+
678
+ # process predicted fields
679
+ for fidx, fn in enumerate(cf.fields_prediction) :
680
+ field_info = cf.fields[ self.fields_prediction_idx[fidx] ]
681
+ num_levels = len(field_info[2])
682
+ # predictions
683
+ pred = log_preds[fidx][0].cpu().detach().numpy()
684
+ pred = detokenize( pred.reshape( [ num_levels, -1,
685
+ forecast_num_tokens, *field_info[3][1:], *field_info[4] ]).swapaxes(0,1))
686
+ # ensemble
687
+ ensemble = log_preds[fidx][2].cpu().detach().numpy().swapaxes(0,1)
688
+ ensemble = detokenize( ensemble.reshape( [ cf.net_tail_num_nets, num_levels, -1,
689
+ forecast_num_tokens, *field_info[3][1:], *field_info[4] ]).swapaxes(1, 2)).swapaxes(0,1)
690
+
691
+ # denormalize
692
+ for bidx in range(batch_size) :
693
+ lats = self.sources_info[bidx][1]
694
+ lons = self.sources_info[bidx][2]
695
+ dates_t = self.sources_info[bidx][0][ -forecast_num_tokens*field_info[4][0] : ]
696
+
697
+ for vidx, vl in enumerate(field_info[2]) :
698
+ normalizer, year_base = self.model.normalizer( self.fields_prediction_idx[fidx], vidx, lats_idx, lons_idx)
699
+ pred[bidx,vidx] = denormalize( pred[bidx,vidx], normalizer, dates_t, year_base)
700
+ ensemble[bidx,:,vidx] = denormalize(ensemble[bidx,:,vidx], normalizer, dates_t, year_base)
701
+
702
+ # append
703
+ preds_out.append( [fn[0], pred])
704
+ ensembles_out.append( [fn[0], ensemble])
705
+
706
+ levels = np.array(cf.fields[0][2])
707
+
708
+ write_forecast( cf.wandb_id, epoch, batch_idx,
709
+ levels, sources_out,
710
+ targets_out, preds_out,
711
+ ensembles_out, coords)
712
+
713
+ ###################################################
714
+
715
+ def split_data(self, data, idx_list, token_size) :
716
+ lens_batches = [[len(t) for t in tt] for tt in idx_list]
717
+ lens_levels = [torch.tensor( tt).sum() for tt in lens_batches]
718
+ data_b = torch.split( data, lens_levels)
719
+ # split according to batch
720
+ return [torch.split( data_b[vidx], lens) for vidx,lens in enumerate(lens_batches)]
721
+
722
+ def get_masked_data(self, field_info, data, idx_list, ensemble = False):
723
+
724
+ cf = self.cf
725
+ batch_size = len(self.sources_info)
726
+ num_levels = len(field_info[2])
727
+ num_tokens = field_info[3]
728
+ token_size = field_info[4]
729
+ data_b = self.split_data(data, idx_list, token_size)
730
+
731
+ # recover token shape
732
+ if ensemble:
733
+ return [[data_b[vidx][bidx].reshape([-1, cf.net_tail_num_nets, *token_size])
734
+ for bidx in range(batch_size)]
735
+ for vidx in range(num_levels)]
736
+ else:
737
+ return [[data_b[vidx][bidx].reshape([-1, *token_size]) for bidx in range(batch_size)]
738
+ for vidx in range(num_levels)]
739
+
740
+ ###################################################
741
+ def log_validate_BERT( self, epoch, batch_idx, log_sources, log_preds) :
742
+ '''Logging for BERT_strategy=BERT.'''
743
+
744
+ cf = self.cf
745
+ batch_size = len(self.sources_info)
746
+
747
+ # save source: remains identical so just save ones
748
+ (sources, targets, tokens_masked_idx_list) = log_sources
749
+
750
+ sources_out, targets_out, preds_out, ensembles_out = [ ], [ ], [ ], [ ]
751
+ coords = []
752
+
753
+ for fidx, field_info in enumerate(cf.fields) :
754
+
755
+ # reconstruct coordinates
756
+ is_predicted = fidx in self.fields_prediction_idx
757
+ num_levels = len(field_info[2])
758
+ num_tokens = field_info[3]
759
+ token_size = field_info[4]
760
+ sources_b = detokenize( sources[fidx].numpy())
761
+
762
+ if is_predicted :
763
+ targets_b = self.get_masked_data(field_info, targets[fidx], tokens_masked_idx_list[fidx])
764
+ preds_mu_b = self.get_masked_data(field_info, log_preds[fidx][0], tokens_masked_idx_list[fidx])
765
+ preds_ens_b = self.get_masked_data(field_info, log_preds[fidx][2], tokens_masked_idx_list[fidx], ensemble = True)
766
+
767
+ # for all batch items
768
+ coords_b = []
769
+ for bidx in range(batch_size):
770
+ dates = self.sources_info[bidx][0]
771
+ lats = self.sources_info[bidx][1]
772
+ lons = self.sources_info[bidx][2]
773
+
774
+ lats_idx = self.sources_idxs[bidx][1]
775
+ lons_idx = self.sources_idxs[bidx][2]
776
+
777
+ # target etc are aliasing targets_b which simplifies bookkeeping below
778
+ if is_predicted :
779
+ target = [targets_b[vidx][bidx] for vidx in range(num_levels)]
780
+ pred_mu = [preds_mu_b[vidx][bidx] for vidx in range(num_levels)]
781
+ pred_ens = [preds_ens_b[vidx][bidx] for vidx in range(num_levels)]
782
+
783
+ coords_mskd_l = []
784
+ for vidx, _ in enumerate(field_info[2]) :
785
+
786
+ normalizer, year_base = self.model.normalizer( fidx, vidx, lats_idx, lons_idx)
787
+ sources_b[bidx,vidx] = denormalize(sources_b[bidx,vidx], normalizer, dates, year_base = 2021)
788
+
789
+ if is_predicted :
790
+ idx = tokens_masked_idx_list[fidx][vidx][bidx]
791
+ grid = np.flip(np.array( np.meshgrid( lons, lats)), axis = 0) #flip to have lat on pos 0 and lon on pos 1
792
+ grid_idx = np.flip(np.array( np.meshgrid( lons_idx, lats_idx)), axis = 0) #flip to have lat on pos 0 and lon on pos 1
793
+
794
+ # recover time dimension since idx assumes the full space-time cube
795
+ grid = torch.from_numpy( np.array( np.broadcast_to( grid,
796
+ shape = [token_size[0]*num_tokens[0], *grid.shape])).swapaxes(0,1))
797
+ grid_lats_toked = tokenize( grid[0], token_size).flatten( 0, 2)
798
+ grid_lons_toked = tokenize( grid[1], token_size).flatten( 0, 2)
799
+
800
+ idx_loc = idx - np.prod(num_tokens) * bidx
801
+ #save only useful info for each bidx. shape e.g. [n_bidx, lat_token_size*lat_num_tokens]
802
+ lats_mskd = np.array([np.unique(t) for t in grid_lats_toked[ idx_loc ].numpy()])
803
+ lons_mskd = np.array([np.unique(t) for t in grid_lons_toked[ idx_loc ].numpy()])
804
+
805
+ #time: idx ranges from 0->863 12x6x12
806
+ t_idx = (idx_loc // (num_tokens[1]*num_tokens[2])) * token_size[0]
807
+ #create range from t_idx-2 to t_idx
808
+ t_idx = np.array([np.arange(t, t + token_size[0]) for t in t_idx])
809
+ dates_mskd = dates[t_idx]
810
+
811
+ for ii,(t,p,e,da,la,lo) in enumerate(zip( target[vidx], pred_mu[vidx], pred_ens[vidx],
812
+ dates_mskd, lats_mskd, lons_mskd)) :
813
+ normalizer_ii = normalizer
814
+ if len(normalizer.shape) > 2: #local normalization
815
+ lats_mskd_idx = np.where(np.isin(lats,la))[0]
816
+ lons_mskd_idx = np.where(np.isin(lons,lo))[0]
817
+ #normalizer_ii = normalizer[:, :, lats_mskd_idx, lons_mskd_idx] problems in python 3.9
818
+ normalizer_ii = normalizer[:, :, lats_mskd_idx[0]:lats_mskd_idx[-1]+1, lons_mskd_idx[0]:lons_mskd_idx[-1]+1]
819
+
820
+ targets_b[vidx][bidx][ii] = denormalize(t, normalizer_ii, da, year_base)
821
+ preds_mu_b[vidx][bidx][ii] = denormalize(p, normalizer_ii, da, year_base)
822
+ preds_ens_b[vidx][bidx][ii] = denormalize(e, normalizer_ii, da, year_base)
823
+
824
+ coords_mskd_l += [[dates_mskd, 90.-lats_mskd, lons_mskd] ]
825
+
826
+ coords_b += [ [dates, 90. - lats, lons] + coords_mskd_l ]
827
+
828
+ coords += [ coords_b ]
829
+ fn = field_info[0]
830
+ sources_out.append( [fn, sources_b])
831
+
832
+ targets_out.append([fn, [[t.numpy(force=True) for t in t_v] for t_v in targets_b]] if is_predicted else [fn, []])
833
+ preds_out.append( [fn, [[p.numpy(force=True) for p in p_v] for p_v in preds_mu_b]] if is_predicted else [fn, []] )
834
+ ensembles_out.append( [fn, [[p.numpy(force=True) for p in p_v] for p_v in preds_ens_b]] if is_predicted else [fn, []] )
835
+
836
+ levels = [[np.array(l) for l in field[2]] for field in cf.fields]
837
+ write_BERT( cf.wandb_id, epoch, batch_idx,
838
+ levels, sources_out, targets_out,
839
+ preds_out, ensembles_out, coords )
840
+
841
+ ######################################################
842
+
843
+ def log_attention( self, epoch, bidx, attention) :
844
+ '''Hook for logging: output attention maps.'''
845
+ cf = self.cf
846
+
847
+ attn_out = []
848
+ for fidx, field_info in enumerate(cf.fields) :
849
+
850
+ # coordinates
851
+ coords_b = []
852
+ for bidx in range(batch_size):
853
+ dates = self.sources_info[bidx][0]
854
+ lats = 90. - self.sources_info[bidx][1]
855
+ lons = self.sources_info[bidx][2]
856
+ coords_b += [ [dates, lats, lons] ]
857
+
858
+ is_predicted = fidx in self.fields_prediction_idx
859
+ attn_out.append([field_info[0], attention[fidx]] if is_predicted else [fn, []])
860
+
861
+ levels = [[np.array(l) for l in field[2]] for field in cf.fields]
862
+ write_attention(cf.wandb_id, epoch,
863
+ bidx, levels, attn_out, coords_b )
vendor/atmorep-official/atmorep/datasets/__init__.py ADDED
File without changes
vendor/atmorep-official/atmorep/datasets/data_writer.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import numpy as np
18
+ import xarray as xr
19
+ import zarr
20
+ import atmorep.config.config as config
21
+
22
+ def write_item(ds_field, name_idx, data, levels, coords, name = 'sample' ):
23
+ ds_batch_item = ds_field.create_group( f'{name}={name_idx:05d}' )
24
+ ds_batch_item.create_dataset( 'data', data=data)
25
+ ds_batch_item.create_dataset( 'ml', data=levels)
26
+ ds_batch_item.create_dataset( 'datetime', data=coords[0].astype('datetime64[ns]'))
27
+ ds_batch_item.create_dataset( 'lat', data=np.array(coords[1]).astype(np.float32))
28
+ ds_batch_item.create_dataset( 'lon', data=np.array(coords[2]).astype(np.float32))
29
+ return ds_batch_item
30
+
31
+ ####################################################################################################
32
+ def write_forecast( model_id, epoch, batch_idx, levels, sources,
33
+ targets, preds, ensembles, coords,
34
+ zarr_store_type = 'ZipStore' ) :
35
+ '''
36
+ sources : num_fields x [field name , data]
37
+ targets :
38
+ preds, ensemble share coords with targets
39
+ '''
40
+ sources_coords = [[c[:3] for c in coord_field ] for coord_field in coords]
41
+ targets_coords = [[[c[-1], c[1], c[2]] for c in coord_field ] for coord_field in coords]
42
+ fname = f'{config.path_results}/id{model_id}/results_id{model_id}_epoch{epoch:05d}' + '_{}.zarr'
43
+
44
+ zarr_store = getattr( zarr, zarr_store_type)
45
+
46
+ store_source = zarr_store( fname.format( 'source'))
47
+ exp_source = zarr.group(store=store_source)
48
+
49
+ for fidx, field in enumerate(sources) :
50
+ ds_field = exp_source.require_group( f'{field[0]}')
51
+ batch_size = field[1].shape[0]
52
+ for bidx in range( field[1].shape[0]) :
53
+ sample = batch_idx * batch_size + bidx
54
+ write_item(ds_field, sample, field[1][bidx], levels, sources_coords[fidx][bidx])
55
+ store_source.close()
56
+
57
+ store_target = zarr_store( fname.format( 'target'))
58
+ exp_target = zarr.group(store=store_target)
59
+ for fidx, field in enumerate(targets) :
60
+ ds_field = exp_target.require_group( f'{field[0]}')
61
+ batch_size = field[1].shape[0]
62
+ for bidx in range( field[1].shape[0]) :
63
+ sample = batch_idx * batch_size + bidx
64
+ write_item(ds_field, sample, field[1][bidx], levels, targets_coords[fidx][bidx])
65
+ store_target.close()
66
+
67
+ store_pred = zarr_store( fname.format( 'pred'))
68
+ exp_pred = zarr.group(store=store_pred)
69
+ for fidx, field in enumerate(preds) :
70
+ ds_field = exp_pred.require_group( f'{field[0]}')
71
+ batch_size = field[1].shape[0]
72
+ for bidx in range( field[1].shape[0]) :
73
+ sample = batch_idx * batch_size + bidx
74
+ write_item(ds_field, sample, field[1][bidx], levels, targets_coords[fidx][bidx])
75
+ store_pred.close()
76
+
77
+ store_ens = zarr_store( fname.format( 'ens'))
78
+ exp_ens = zarr.group(store=store_ens)
79
+ for fidx, field in enumerate(ensembles) :
80
+ ds_field = exp_ens.require_group( f'{field[0]}')
81
+ batch_size = field[1].shape[0]
82
+ for bidx in range( field[1].shape[0]) :
83
+ sample = batch_idx * batch_size + bidx
84
+ write_item(ds_field, sample, field[1][bidx], levels, targets_coords[fidx][bidx])
85
+ store_ens.close()
86
+
87
+ ####################################################################################################
88
+ def write_BERT( model_id, epoch, batch_idx, levels, sources,
89
+ targets, preds, ensembles, coords,
90
+ zarr_store_type = 'ZipStore' ) :
91
+
92
+ '''
93
+ sources : num_fields x [field name , data]
94
+ targets :
95
+ preds, ensemble share coords with targets
96
+ '''
97
+
98
+ sources_coords = [[c[:3] for c in coord_field ] for coord_field in coords]
99
+ targets_coords = [[c[3:] for c in coord_field ] for coord_field in coords]
100
+
101
+ fname = f'{config.path_results}/id{model_id}/results_id{model_id}_epoch{epoch:05d}' + '_{}.zarr'
102
+
103
+ zarr_store = getattr( zarr, zarr_store_type)
104
+
105
+ store_source = zarr_store( fname.format( 'source'))
106
+ exp_source = zarr.group(store=store_source)
107
+ for fidx, field in enumerate(sources) :
108
+ ds_field = exp_source.require_group( f'{field[0]}')
109
+ batch_size = field[1].shape[0]
110
+ for bidx in range( field[1].shape[0]) :
111
+ sample = batch_idx * batch_size + bidx
112
+ write_item(ds_field, sample, field[1][bidx], levels[fidx], sources_coords[fidx][bidx] )
113
+ store_source.close()
114
+
115
+ store_target = zarr_store( fname.format( 'target'))
116
+ exp_target = zarr.group(store=store_target)
117
+ for fidx, field in enumerate(targets) :
118
+ if 0 == len(field[1]) : # skip fields that were not predicted
119
+ continue
120
+ batch_size = len(field[1][0])
121
+ ds_field = exp_target.require_group( f'{field[0]}')
122
+ for bidx in range( len(field[1][0])) :
123
+ sample = batch_idx * batch_size + bidx
124
+ ds_target_b = ds_field.create_group( f'sample={sample:05d}')
125
+ for vidx in range(len(levels[fidx])) :
126
+ write_item(ds_target_b, levels[fidx][vidx], field[1][vidx][bidx], levels[fidx][vidx], targets_coords[fidx][bidx][vidx], name = 'ml' )
127
+ store_target.close()
128
+
129
+ store_pred = zarr_store( fname.format( 'pred'))
130
+ exp_pred = zarr.group(store=store_pred)
131
+ for fidx, field in enumerate(preds) :
132
+ if 0 == len(field[1]) : # skip fields that were not predicted
133
+ continue
134
+ batch_size = len(field[1][0])
135
+ ds_pred = exp_pred.require_group( f'{field[0]}')
136
+ for bidx in range( len(field[1][0])) :
137
+ sample = batch_idx * batch_size + bidx
138
+ ds_pred_b = ds_pred.create_group( f'sample={sample:05d}')
139
+ for vidx in range(len(levels[fidx])) :
140
+ write_item(ds_pred_b, levels[fidx][vidx], field[1][vidx][bidx], levels[fidx][vidx],
141
+ targets_coords[fidx][bidx][vidx], name = 'ml' )
142
+ store_pred.close()
143
+
144
+ store_ens = zarr_store( fname.format( 'ens'))
145
+ exp_ens = zarr.group(store=store_ens)
146
+ for fidx, field in enumerate(ensembles) :
147
+ if 0 == len(field[1]) : # skip fields that were not predicted
148
+ continue
149
+ batch_size = len(field[1][0])
150
+ ds_ens = exp_ens.require_group( f'{field[0]}')
151
+ for bidx in range( len(field[1][0])) :
152
+ sample = batch_idx * batch_size + bidx
153
+ ds_ens_b = ds_ens.create_group( f'sample={sample:05d}')
154
+ for vidx in range(len(levels[fidx])) :
155
+ write_item(ds_ens_b, levels[fidx][vidx], field[1][vidx][bidx], levels[fidx][vidx],
156
+ targets_coords[fidx][bidx][vidx], name = 'ml' )
157
+ store_ens.close()
158
+
159
+ ####################################################################################################
160
+ def write_attention(model_id, epoch, batch_idx, levels, attn, coords, zarr_store_type = 'ZipStore' ) :
161
+
162
+ fname = f'{config.path_results}/id{model_id}/results_id{model_id}_epoch{epoch:05d}' + '_{}.zarr'
163
+ zarr_store = getattr( zarr, zarr_store_type)
164
+
165
+ store_attn = zarr_store( fname.format( 'attention'))
166
+ exp_attn = zarr.group(store=store_attn)
167
+
168
+ for fidx, atts_f in enumerate(attn) :
169
+ ds_field = exp_attn.require_group( f'{atts_f[0]}')
170
+ ds_field_b = ds_field.require_group( f'batch={batch_idx:05d}')
171
+ for lidx, atts_f_l in enumerate(atts_f[1]) : # layer in the network
172
+ ds_f_l = ds_field_b.require_group( f'layer={lidx:05d}')
173
+ ds_f_l.create_dataset( 'ml', data=levels[fidx])
174
+ ds_f_l.create_dataset( 'datetime', data=coords[0][fidx])
175
+ ds_f_l.create_dataset( 'lat', data=coords[1][fidx])
176
+ ds_f_l.create_dataset( 'lon', data=coords[2][fidx])
177
+ ds_f_l_h = ds_f_l.require_group('heads')
178
+ for hidx, atts_f_l_head in enumerate(atts_f_l) : # number of attention head
179
+ if atts_f_l_head != None :
180
+ ds_f_l_h.create_dataset(f'{hidx}', data=atts_f_l_head.numpy() )
181
+ store_attn.close()
vendor/atmorep-official/atmorep/datasets/multifield_data_sampler.py ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import dask.config as dc
18
+ import dask.array as da
19
+ import torch
20
+ import numpy as np
21
+ import zarr
22
+ import pandas as pd
23
+ from datetime import datetime
24
+ import time
25
+ import os
26
+
27
+ from atmorep.datasets.normalizer import normalize
28
+ from atmorep.utils.utils import tokenize, get_weights
29
+
30
+ class MultifieldDataSampler( torch.utils.data.IterableDataset):
31
+
32
+ ###################################################
33
+ def __init__( self, file_path, fields, years, batch_size, pre_batch, n_size,
34
+ num_samples, with_shuffle = False, time_sampling = 1, with_source_idxs = False, compute_weights = False,
35
+ fields_targets = None, pre_batch_targets = None ) :
36
+ '''
37
+ Data set for single dynamic field at an arbitrary number of vertical levels
38
+
39
+ nsize : neighborhood in (tsteps, deg_lat, deg_lon)
40
+ '''
41
+ super( MultifieldDataSampler).__init__()
42
+
43
+ self.fields = fields
44
+ self.batch_size = batch_size
45
+ self.n_size = n_size
46
+ self.num_samples = num_samples
47
+ self.with_source_idxs = with_source_idxs
48
+ self.compute_weights = compute_weights
49
+ self.with_shuffle = with_shuffle
50
+ self.pre_batch = pre_batch
51
+
52
+ assert os.path.exists(file_path), f"File path {file_path} does not exist"
53
+ self.ds = zarr.open( file_path)
54
+
55
+ self.dask_array_data = da.from_zarr(self.ds['data'])
56
+ self.dask_array_sfc = da.from_zarr(self.ds['data_sfc'])
57
+
58
+ self.ds_global = self.ds.attrs['is_global']
59
+
60
+ self.lats = np.array( self.ds['lats'])
61
+ self.lons = np.array( self.ds['lons'])
62
+
63
+ sh = self.ds['data'].shape
64
+ st = self.ds['time'].shape
65
+ self.ds_len = st[0]
66
+ print( f'self.ds[\'data\'] : {sh} :: {st}')
67
+ print( f'self.lats : {self.lats.shape}', flush=True)
68
+ print( f'self.lons : {self.lons.shape}', flush=True)
69
+ self.fields_idxs = []
70
+
71
+ self.time_sampling = time_sampling
72
+ self.range_lat = np.array( self.lats[ [0,-1] ])
73
+ self.range_lon = np.array( self.lons[ [0,-1] ])
74
+ self.res = np.array(self.ds.attrs['res'])
75
+ self.year_base = self.ds['time'][0].astype(datetime).year
76
+
77
+ # ensure neighborhood does not exceed domain (either at pole or for finite domains)
78
+ self.range_lat += np.array([n_size[1] / 2., -n_size[1] / 2.])
79
+ # lon: no change for periodic case
80
+ if self.ds_global < 1.:
81
+ self.range_lon += np.array([n_size[2]/2., -n_size[2]/2.])
82
+
83
+ # data normalizers
84
+ self.normalizers = []
85
+ for ifield, field_info in enumerate(fields) :
86
+ corr_type = 'global' if len(field_info) <= 6 else field_info[6]
87
+ nf_name = 'global_norm' if corr_type == 'global' else 'norm'
88
+ self.normalizers.append( [] )
89
+ for vl in field_info[2]:
90
+ if vl == 0:
91
+ field_idx = self.ds.attrs['fields_sfc'].index( field_info[0])
92
+ n_name = f'normalization/{nf_name}_sfc'
93
+ self.normalizers[ifield] += [self.ds[n_name].oindex[ :, :, field_idx]]
94
+ else:
95
+ vl_idx = self.ds.attrs['levels'].index(vl)
96
+ field_idx = self.ds.attrs['fields'].index( field_info[0])
97
+ n_name = f'normalization/{nf_name}'
98
+ self.normalizers[ifield] += [self.ds[n_name].oindex[ :, :, field_idx, vl_idx]]
99
+
100
+ # extract indices for selected years
101
+ self.times = pd.DatetimeIndex( self.ds['time'])
102
+ idxs_years = self.times.year == years[0]
103
+ for year in years[1:] :
104
+ idxs_years = np.logical_or( idxs_years, self.times.year == year)
105
+ self.idxs_years = np.where( idxs_years)[0]
106
+
107
+ self.num_samples = min( self.num_samples, self.idxs_years.shape[0])
108
+
109
+ ###################################################
110
+ def shuffle( self) :
111
+
112
+ worker_info = torch.utils.data.get_worker_info()
113
+ rng_seed = None
114
+ if worker_info is not None :
115
+ rng_seed = int(time.time()) // (worker_info.id+1) + worker_info.id
116
+
117
+ rng = np.random.default_rng( rng_seed)
118
+ self.idxs_perm_t = rng.permutation( self.idxs_years)[ : self.num_samples // self.batch_size]
119
+
120
+ lats = rng.random(self.num_samples) * (self.range_lat[1] - self.range_lat[0]) +self.range_lat[0]
121
+ lons = rng.random(self.num_samples) * (self.range_lon[1] - self.range_lon[0]) +self.range_lon[0]
122
+
123
+ # align with grid
124
+ res_inv = 1.0 / self.res * 1.00001
125
+ lats = self.res[0] * np.round( lats * res_inv[0])
126
+ lons = self.res[1] * np.round( lons * res_inv[1])
127
+
128
+ self.idxs_perm = np.stack( [lats, lons], axis=1)
129
+
130
+ ###################################################
131
+ def __iter__(self):
132
+
133
+ if self.with_shuffle :
134
+ self.shuffle()
135
+
136
+ lats, lons = self.lats, self.lons
137
+ ts, n_size = self.time_sampling, self.n_size
138
+ ns_2 = np.array(self.n_size) / 2.
139
+ res = self.res
140
+
141
+ iter_start, iter_end = self.worker_workset()
142
+
143
+ for bidx in range( iter_start, iter_end) :
144
+
145
+ sources, token_infos = [[] for _ in self.fields], [[] for _ in self.fields]
146
+ sources_infos, source_idxs = [], []
147
+
148
+ i_bidx = self.idxs_perm_t[bidx]
149
+ idxs_t = list(np.arange( i_bidx - n_size[0]*ts, i_bidx, ts, dtype=np.int64))
150
+ # data_tt_sfc = self.ds['data_sfc'].oindex[idxs_t]
151
+ # data_tt = self.ds['data'].oindex[idxs_t]
152
+ with dc.set(**{'array.slicing.split_large_chunks': True}):
153
+ data_tt_sfc = self.dask_array_sfc[idxs_t].compute()
154
+ data_tt = self.dask_array_data[idxs_t].compute()
155
+
156
+
157
+ for sidx in range(self.batch_size) :
158
+
159
+ idx = self.idxs_perm[bidx*self.batch_size+sidx]
160
+ # slight asymetry with offset by res/2 is required to match desired token count
161
+ lat_ran = np.where(np.logical_and(lats>idx[0]-ns_2[1]-res[0]/2.,lats<idx[0]+ns_2[1]))[0]
162
+ # handle periodicity of lon
163
+ assert not ((idx[1]-ns_2[2]) < 0. and (idx[1]+ns_2[2]) > 360.)
164
+ il, ir = (idx[1]-ns_2[2]-res[1]/2., idx[1]+ns_2[2])
165
+ if il < 0. :
166
+ lon_ran = np.concatenate( [np.where( lons > il+360)[0], np.where(lons < ir)[0]], 0)
167
+ elif ir > 360. :
168
+ lon_ran = np.concatenate( [np.where( lons > il)[0], np.where(lons < ir-360)[0]], 0)
169
+ else :
170
+ lon_ran = np.where(np.logical_and( lons > il, lons < ir))[0]
171
+
172
+ sources_infos += [ [ self.ds['time'][ idxs_t ].astype(datetime),
173
+ self.lats[lat_ran], self.lons[lon_ran], self.res ] ]
174
+
175
+ if self.with_source_idxs :
176
+ source_idxs += [ (idxs_t, lat_ran, lon_ran) ]
177
+
178
+ # extract data
179
+ for ifield, field_info in enumerate(self.fields):
180
+ source_lvl, tok_info_lvl = [], []
181
+ tok_size = field_info[4]
182
+ num_tokens = field_info[3]
183
+ corr_type = 'global' if len(field_info) <= 6 else field_info[6]
184
+
185
+ for ilevel, vl in enumerate(field_info[2]):
186
+ if vl == 0 : #surface level
187
+ field_idx = self.ds.attrs['fields_sfc'].index( field_info[0])
188
+ data_t = data_tt_sfc[ :, field_idx ]
189
+ else :
190
+ field_idx = self.ds.attrs['fields'].index( field_info[0])
191
+ vl_idx = self.ds.attrs['levels'].index(vl)
192
+ data_t = data_tt[ :, field_idx, vl_idx ]
193
+
194
+ source_data, tok_info = [], []
195
+ # extract data, normalize and tokenize
196
+ cdata = data_t[ ... , lat_ran[:,np.newaxis], lon_ran[np.newaxis,:]]
197
+
198
+ normalizer = self.normalizers[ifield][ilevel]
199
+
200
+ if corr_type != 'global':
201
+ #normalizer = normalizer[ ... , lat_ran[:,np.newaxis], lon_ran[np.newaxis,:]]
202
+ if lat_ran[0] < lat_ran[-1] and lon_ran[0] < lon_ran[-1]:
203
+ lat_max, lat_min = max(lat_ran), min(lat_ran)
204
+ lon_max, lon_min = max(lon_ran), min(lon_ran)
205
+ normalizer = normalizer[:,:,lat_min:lat_max+1,lon_min:lon_max+1]
206
+ #normalizer_vu = normalizer[:,:,lat_min:lat_max+1,lon_min:lon_max+1]
207
+ #cdata = normalize(cdata, normalizer_vu, sources_infos[-1][0], year_base = self.year_base)
208
+ else:
209
+ normalizer = normalizer[ ... , lat_ran[:,np.newaxis], lon_ran[np.newaxis,:]]
210
+ #cdata = normalize(cdata, normalizer, sources_infos[-1][0], year_base = self.year_base)
211
+ #else:
212
+ cdata = normalize(cdata, normalizer, sources_infos[-1][0], year_base = self.year_base)
213
+
214
+ source_data = tokenize( torch.from_numpy( cdata), tok_size )
215
+ # token_infos uses center of the token: *last* datetime and center in space
216
+ dates = self.ds['time'][ idxs_t ].astype(datetime)
217
+ cdates = dates[tok_size[0]-1::tok_size[0]]
218
+ # use -1 is to start days from 0
219
+ dates = [(d.year, d.timetuple().tm_yday-1, d.hour) for d in cdates]
220
+ lats_sidx = self.lats[lat_ran][ tok_size[1]//2 :: tok_size[1] ]
221
+ lons_sidx = self.lons[lon_ran][ tok_size[2]//2 :: tok_size[2] ]
222
+ # tensor product for token_infos
223
+ tok_info += [[[[[ year, day, hour, vl, lat, lon, vl, self.res[0]] for lon in lons_sidx]
224
+ for lat in lats_sidx]
225
+ for (year, day, hour) in dates]]
226
+
227
+ source_lvl += [ source_data ]
228
+ tok_info_lvl += [ torch.tensor(tok_info, dtype=torch.float32).flatten( 1, -2)]
229
+ sources[ifield] += [ torch.stack(source_lvl, 0) ]
230
+ token_infos[ifield] += [ torch.stack(tok_info_lvl, 0) ]
231
+
232
+ # concatenate batches
233
+ sources = [torch.stack(sources_field).transpose(1,0) for sources_field in sources]
234
+ token_infos = [torch.stack(tis_field).transpose(1,0) for tis_field in token_infos]
235
+ sources = self.pre_batch( sources, token_infos )
236
+
237
+ tmidx_list = sources[-1]
238
+ weights_idx_list = []
239
+ if self.compute_weights:
240
+ for ifield, field_info in enumerate(self.fields):
241
+ weights = []
242
+ for ilevel, vl in enumerate(field_info[2]):
243
+ for ibatch in range(self.batch_size):
244
+
245
+ lats_idx = source_idxs[ibatch][1]
246
+ lons_idx = source_idxs[ibatch][2]
247
+
248
+ idx_base = tmidx_list[ifield][ilevel][ibatch]
249
+ idx_loc = idx_base - np.prod(num_tokens) * ibatch
250
+
251
+ grid = np.flip(np.array( np.meshgrid( lons_idx, lats_idx)), axis = 0) #flip to have lat on pos 0 and lon on pos 1
252
+ grid = torch.from_numpy( np.array( np.broadcast_to( grid,
253
+ shape = [tok_size[0]*num_tokens[0], *grid.shape])).swapaxes(0,1))
254
+
255
+ grid_lats_toked = tokenize( grid[0], tok_size).flatten( 0, 2)
256
+
257
+ lats_mskd_b = np.array([np.unique(t) for t in grid_lats_toked[ idx_loc ].numpy()])
258
+
259
+ weights.append([get_weights(la) for la in lats_mskd_b])
260
+
261
+ weights_idx_list.append(weights)
262
+ sources = (*sources, weights_idx_list)
263
+
264
+ # TODO: implement (only required when prediction target comes from different data stream)
265
+ targets, target_info = None, None
266
+ target_idxs = None
267
+
268
+ yield ( sources, targets, (source_idxs, sources_infos), (target_idxs, target_info))
269
+
270
+ ###################################################
271
+ def set_data( self, times_pos, batch_size = None) :
272
+ '''
273
+ times_pos = np.array( [ [year, month, day, hour, lat, lon], ...] )
274
+ - lat \in [90,-90] = [90N, 90S]
275
+ - lon \in [0,360]
276
+ - (year,month) pairs should be a limited number since all data for these is loaded
277
+ '''
278
+ # generate all the data
279
+ self.idxs_perm = np.zeros( (len(times_pos), 2))
280
+ self.idxs_perm_t = []
281
+ self.num_samples = len(times_pos)
282
+ for idx, item in enumerate( times_pos) :
283
+
284
+ assert item[2] >= 1 and item[2] <= 31
285
+ assert item[3] >= 0 and item[3] < int(24 / self.time_sampling)
286
+ assert item[4] >= -90. and item[4] <= 90.
287
+
288
+ tstamp = pd.to_datetime( f'{item[0]}-{item[1]}-{item[2]}-{item[3]}', format='%Y-%m-%d-%H')
289
+
290
+ self.idxs_perm_t += [ np.where( self.times == tstamp)[0]+1 ] #The +1 assures that tsamp is included in the range
291
+
292
+ # work with mathematical lat coordinates from here on
293
+ self.idxs_perm[idx] = np.array( [90. - item[4], item[5]])
294
+
295
+ self.idxs_perm_t = np.array(self.idxs_perm_t).squeeze()
296
+
297
+ ###################################################
298
+ def set_global( self, times, batch_size = None, token_overlap = [0, 0]) :
299
+ ''' generate patch/token positions for global grid '''
300
+ token_overlap = np.array( token_overlap).astype(np.int64)
301
+
302
+ # assumed that sanity checking that field data is consistent has been done
303
+ ifield = 0
304
+ field = self.fields[ifield]
305
+
306
+ res = self.res
307
+ side_len = np.array( [field[3][1] * field[4][1]*res[0], field[3][2] * field[4][2]*res[1]] )
308
+ overlap = np.array([token_overlap[0]*field[4][1]*res[0],token_overlap[1]*field[4][2]*res[1]])
309
+ side_len_2 = side_len / 2.
310
+ assert all( overlap <= side_len_2), 'token_overlap too large for #tokens, reduce if possible'
311
+
312
+ # generate tiles
313
+ times_pos = []
314
+ for ctime in times :
315
+
316
+ lat = side_len_2[0].item()
317
+ num_tiles_lat = 0
318
+ while (lat + side_len_2[0].item()) < 180. :
319
+ num_tiles_lat += 1
320
+ lon = side_len_2[1].item() - overlap[1].item()/2.
321
+ num_tiles_lon = 0
322
+ while (lon - side_len_2[1]) < 360. :
323
+ times_pos += [[*ctime, -lat + 90., np.mod(lon,360.) ]]
324
+ lon += side_len[1].item() - overlap[1].item()
325
+ num_tiles_lon += 1
326
+ lat += side_len[0].item() - overlap[0].item()
327
+
328
+ # add one additional row if no perfect tiling (sphere is toric in longitude so no special
329
+ # handling necessary but not in latitude)
330
+ # the added row is such that it goes exaclty down to the South pole and the offset North-wards
331
+ # is computed based on this
332
+ lat -= side_len[0] - overlap[0]
333
+ if lat - side_len_2[0] < 180. :
334
+ num_tiles_lat += 1
335
+ lat = 180. - side_len_2[0].item() + res[0]
336
+ lon = side_len_2[1].item() - overlap[1].item()/2.
337
+ while (lon - side_len_2[1]) < 360. :
338
+ times_pos += [[*ctime, -lat + 90., np.mod(lon,360.) ]]
339
+ lon += side_len[1].item() - overlap[1].item()
340
+
341
+ # adjust batch size if necessary so that the evaluations split up across batches of equal size
342
+ batch_size = len(times_pos) #num_tiles_lon
343
+
344
+ print( 'Number of batches per global forecast: {}'.format( num_tiles_lat) )
345
+
346
+ self.set_data( times_pos, batch_size)
347
+
348
+ ###################################################
349
+ def __len__(self):
350
+ return self.num_samples // self.batch_size
351
+
352
+ ###################################################
353
+ def worker_workset( self) :
354
+
355
+ worker_info = torch.utils.data.get_worker_info()
356
+
357
+ if worker_info is None:
358
+ iter_start = 0
359
+ iter_end = self.num_samples
360
+
361
+ else:
362
+ # split workload
363
+ per_worker = len(self) // worker_info.num_workers
364
+ worker_id = worker_info.id
365
+ iter_start = int(worker_id * per_worker)
366
+ iter_end = int(iter_start + per_worker)
367
+ if worker_info.id+1 == worker_info.num_workers :
368
+ iter_end = len(self)
369
+
370
+ return iter_start, iter_end
371
+
vendor/atmorep-official/atmorep/datasets/normalizer.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import code
18
+ import numpy as np
19
+ import xarray as xr
20
+ import atmorep.config.config as config
21
+
22
+ ######################################################
23
+ # Normalize #
24
+ ######################################################
25
+
26
+ def normalize( data, norm, dates, year_base = 1979) :
27
+ corr_data = np.array([norm[12*(dt.year-year_base) + dt.month-1] for dt in dates])
28
+ mean, var = corr_data[:, 0], corr_data[:, 1]
29
+ if (var == 0.).all() :
30
+ print( f'Warning: var == 0')
31
+ assert False
32
+ if len(norm.shape) > 2 : #global norm
33
+ return normalize_local(data, mean, var)
34
+ else:
35
+ return normalize_global( data, mean, var)
36
+
37
+ ######################################################
38
+ def normalize_local( data, mean, var) :
39
+ data = (data - mean) / var
40
+ return data
41
+
42
+ ######################################################
43
+ def normalize_global( data, mean, var) :
44
+ for i in range( data.shape[0]) :
45
+ data[i] = (data[i] - mean[i]) / var[i]
46
+ return data
47
+
48
+
49
+ ######################################################
50
+ # Denormalize #
51
+ ######################################################
52
+ def denormalize(data, norm, dates, year_base = 1979) :
53
+ corr_data = np.array([norm[12*(dt.year-year_base) + dt.month-1] for dt in dates])
54
+ mean, var = corr_data[:, 0], corr_data[:, 1]
55
+ if len(norm.shape) > 2 :
56
+ return denormalize_local(data, mean, var)
57
+ else:
58
+ return denormalize_global(data, mean, var)
59
+
60
+ ######################################################
61
+
62
+ def denormalize_local(data, mean, var) :
63
+ if len(data.shape) > 3: #ensemble
64
+ for i in range( data.shape[0]) :
65
+ data[i] = (data[i] * var) + mean
66
+ else:
67
+ data = (data * var) + mean
68
+ return data
69
+
70
+ ######################################################
71
+
72
+ def denormalize_global(data, mean, var) :
73
+ if len(data.shape) > 3: #ensemble
74
+ data = data.swapaxes(0,1)
75
+ for i in range( data.shape[0]) :
76
+ data[i] = ((data[i] * var[i]) + mean[i])
77
+ data = data.swapaxes(0,1)
78
+ else:
79
+ for i in range( data.shape[0]) :
80
+ data[i] = (data[i] * var[i]) + mean[i]
81
+
82
+ return data
vendor/atmorep-official/atmorep/tests/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
vendor/atmorep-official/atmorep/tests/conftest.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ def pytest_addoption(parser):
2
+ parser.addoption("--field", action="store", help="field to run the test on")
3
+ parser.addoption("--model_id", action="store", help="wandb ID of the atmorep model")
4
+ parser.addoption("--epoch", action="store", help="field to run the test on", default = "0")
5
+ parser.addoption("--strategy", action="store", help="BERT or forecast")
6
+
7
+
8
+
vendor/atmorep-official/atmorep/tests/test_utils.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ def era5_fname():
5
+ return "/gpfs/scratch/ehpc03/data/{}/ml{}/era5_{}_y{}_m{}_ml{}.grib"
6
+
7
+ def atmorep_pred():
8
+ return "./results/id{}/results_id{}_epoch{}_pred.zarr"
9
+
10
+ def atmorep_target():
11
+ return "./results/id{}/results_id{}_epoch{}_target.zarr"
12
+
13
+ def grib_index(field):
14
+ grib_idxs = {"velocity_u": "u",
15
+ "temperature": "t",
16
+ "total_precip": "tp",
17
+ "velocity_v": "v",
18
+ "velocity_z": "z",
19
+ "vorticity" : "vo",
20
+ "divergence" : "d",
21
+ "specific_humidity": "q"}
22
+
23
+ return grib_idxs[field]
24
+
25
+ ##################################################################
26
+
27
+ def get_BERT(atmorep, field, sample, level):
28
+ atmorep_sample = atmorep[f"{field}/sample={sample:05d}/ml={level:05d}"]
29
+ data = atmorep_sample.data[0,0]
30
+ datetime = pd.Timestamp(atmorep_sample.datetime[0,0])
31
+ lats = atmorep_sample.lat[0]
32
+ lons = atmorep_sample.lon[0]
33
+ return data, datetime, lats, lons
34
+
35
+ def get_forecast(atmorep, field, sample,level_idx):
36
+ atmorep_sample = atmorep[f"{field}/sample={sample:05d}"]
37
+ data = atmorep_sample.data[level_idx, 0]
38
+ datetime = pd.Timestamp(atmorep_sample.datetime[0])
39
+ lats = atmorep_sample.lat
40
+ lons = atmorep_sample.lon
41
+ return data, datetime, lats, lons
42
+
43
+ ######################################
44
+
45
+ def check_lats(lats_pred, lats_target):
46
+ assert (lats_pred[:] == lats_target[:]).all(), "Mismatch between latitudes"
47
+ assert (lats_pred[:] <= 90.).all(), f"latitudes are between {np.amin(lats_pred)}- {np.amax(lats_pred)}"
48
+ assert (lats_pred[:] >= -90.).all(), f"latitudes are between {np.amin(lats_pred)}- {np.amax(lats_pred)}"
49
+
50
+ def check_lons(lons_pred, lons_target):
51
+ assert (lons_pred[:] == lons_target[:]).all(), "Mismatch between longitudes"
52
+ assert (lons_pred[:] >= 0.).all(), "longitudes are between {np.amin(lons_pred)}- {np.amax(lons_pred)}"
53
+ assert (lons_pred[:] <= 360.).all(), "longitudes are between {np.amin(lons_pred)}- {np.amax(lons_pred)}"
54
+
55
+ def check_datetimes(datetimes_pred, datetimes_target):
56
+ assert (datetimes_pred == datetimes_target), "Mismatch between datetimes"
57
+
58
+ ######################################
59
+
60
+ #calculate RMSE
61
+ def compute_RMSE(pred, target):
62
+ return np.sqrt(np.mean((pred-target)**2))
63
+
64
+
65
+ def get_max_RMSE(field):
66
+ #TODO: optimize thresholds
67
+ values = {"temperature" : 3,
68
+ "velocity_u" : 0.2, #????
69
+ "velocity_v": 0.2, #????
70
+ "velocity_z": 0.2, #????
71
+ "vorticity" : 0.2, #????
72
+ "divergence": 0.2, #????
73
+ "specific_humidity": 0.2, #????
74
+ "total_precip": 1, #?????
75
+ }
76
+
77
+ return values[field]
vendor/atmorep-official/atmorep/tests/validation_test.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ import zarr
3
+ import cfgrib
4
+ import xarray as xr
5
+ import numpy as np
6
+ import random as rnd
7
+ import warnings
8
+ import os
9
+
10
+ from atmorep.tests.test_utils import *
11
+
12
+ # run it with e.g. pytest -s atmorep/tests/validation_test.py --field temperature --model_id ztsut0mr --strategy BERT
13
+
14
+ @pytest.fixture
15
+ def field(request):
16
+ return request.config.getoption("field")
17
+
18
+ @pytest.fixture
19
+ def model_id(request):
20
+ return request.config.getoption("model_id")
21
+
22
+ @pytest.fixture
23
+ def epoch(request):
24
+ request.config.getoption("epoch")
25
+
26
+ @pytest.fixture(autouse = True)
27
+ def BERT(request):
28
+ strategy = request.config.getoption("strategy")
29
+ return (strategy == 'BERT' or strategy == 'temporal_interpolation')
30
+
31
+ @pytest.fixture(autouse = True)
32
+ def strategy(request):
33
+ return request.config.getoption("strategy")
34
+
35
+ #TODO: add test for global_forecast vs ERA5
36
+
37
+ def test_datetime(field, model_id, BERT, epoch = 0):
38
+
39
+ """
40
+ Check against ERA5 timestamps.
41
+ Loop over all levels individually. 50 random samples for each level.
42
+ """
43
+
44
+ store = zarr.ZipStore(atmorep_target().format(model_id, model_id, str(epoch).zfill(5)))
45
+ atmorep = zarr.group(store)
46
+
47
+ nsamples = min(len(atmorep[field]), 50)
48
+ samples = rnd.sample(range(len(atmorep[field])), nsamples)
49
+ levels = [int(f.split("=")[1]) for f in atmorep[f"{field}/sample=00000"]] if BERT else atmorep[f"{field}/sample=00000"].ml[:]
50
+
51
+ get_data = get_BERT if BERT else get_forecast
52
+
53
+ for level in levels:
54
+ #TODO: make it more elegant
55
+ level_idx = level if BERT else np.where(levels == level)[0].tolist()[0]
56
+
57
+ for s in samples:
58
+ data, datetime, lats, lons = get_data(atmorep, field, s, level_idx)
59
+ year, month = datetime.year, str(datetime.month).zfill(2)
60
+
61
+ era5_path = era5_fname().format(field, level, field, year, month, level)
62
+ if not os.path.isfile(era5_path):
63
+ warnings.warn(UserWarning((f"Timestamp {datetime} not found in ERA5. Skipping")))
64
+ continue
65
+ era5 = xr.open_dataset(era5_path, engine = "cfgrib")[grib_index(field)].sel(time = datetime, latitude = lats, longitude = lons)
66
+
67
+ #assert (data[0] == era5.values[0]).all(), "Mismatch between ERA5 and AtmoRep Timestamps"
68
+ assert np.isclose(data[0], era5.values[0],rtol=1e-04, atol=1e-07).all(), "Mismatch between ERA5 and AtmoRep Timestamps"
69
+
70
+ #############################################################################
71
+
72
+ def test_coordinates(field, model_id, BERT, epoch = 0):
73
+ """
74
+ Check that coordinates match between target and prediction.
75
+ Check also that latitude and longitudes are in geographical coordinates
76
+ 50 random samples.
77
+ """
78
+
79
+ store_t = zarr.ZipStore(atmorep_target().format(model_id, model_id, str(epoch).zfill(5)))
80
+ target = zarr.group(store_t)
81
+
82
+ store_p = zarr.ZipStore(atmorep_pred().format(model_id, model_id, str(epoch).zfill(5)))
83
+ pred = zarr.group(store_p)
84
+
85
+ nsamples = min(len(target[field]), 50)
86
+ samples = rnd.sample(range(len(target[field])), nsamples)
87
+ levels = [int(f.split("=")[1]) for f in target[f"{field}/sample=00000"]] if BERT else target[f"{field}/sample=00000"].ml[:]
88
+
89
+ get_data = get_BERT if BERT else get_forecast
90
+
91
+ for level in levels:
92
+ level_idx = level if BERT else np.where(levels == level)[0].tolist()[0]
93
+ for s in samples:
94
+ _, datetime_target, lats_target, lons_target = get_data(target,field, s, level_idx)
95
+ _, datetime_pred, lats_pred, lons_pred = get_data(pred, field, s, level_idx)
96
+
97
+ check_lats(lats_pred, lats_target)
98
+ check_lons(lons_pred, lons_target)
99
+ check_datetimes(datetime_pred, datetime_target)
100
+
101
+ #########################################################################
102
+
103
+ def test_rmse(field, model_id, BERT, epoch = 0):
104
+ """
105
+ Test that for each field the RMSE does not exceed a certain value.
106
+ 50 random samples.
107
+ """
108
+ store_t = zarr.ZipStore(atmorep_target().format(model_id, model_id, str(epoch).zfill(5)))
109
+ target = zarr.group(store_t)
110
+
111
+ store_p = zarr.ZipStore(atmorep_pred().format(model_id, model_id, str(epoch).zfill(5)))
112
+ pred = zarr.group(store_p)
113
+
114
+ nsamples = min(len(target[field]), 50)
115
+ samples = rnd.sample(range(len(target[field])), nsamples)
116
+ levels = [int(f.split("=")[1]) for f in target[f"{field}/sample=00000"]] if BERT else target[f"{field}/sample=00000"].ml[:]
117
+
118
+ get_data = get_BERT if BERT else get_forecast
119
+
120
+ for level in levels:
121
+ level_idx = level if BERT else np.where(levels == level)[0].tolist()[0]
122
+ for s in samples:
123
+ sample_target, _, _, _ = get_data(target,field, s, level_idx)
124
+ sample_pred, _, _, _ = get_data(pred,field, s, level_idx)
125
+
126
+ assert compute_RMSE(sample_target, sample_pred).mean() < get_max_RMSE(field)
vendor/atmorep-official/atmorep/training/__init__.py ADDED
File without changes
vendor/atmorep-official/atmorep/training/bert.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import numpy as np
19
+ from functools import partial
20
+ import code
21
+
22
+ ####################################################################################################
23
+ def prepare_batch_BERT_multifield( cf, rngs, fields, BERT_strategy, fields_data, fields_infos) :
24
+
25
+ fields_tokens_masked_idx_list = [[] for _ in fields_data]
26
+ fields_targets = [[] for _ in fields_data]
27
+ sources = [[] for _ in fields_data]
28
+ token_infos = [[] for _ in fields_data]
29
+
30
+ if not BERT_strategy :
31
+ BERT_strategy = cf.BERT_strategy
32
+
33
+ if BERT_strategy == 'BERT' :
34
+ bert_f = prepare_batch_BERT_field
35
+ elif BERT_strategy == 'global_forecast' :
36
+ bert_f = prepare_batch_BERT_forecast_field
37
+ elif BERT_strategy == 'forecast' :
38
+ bert_f = prepare_batch_BERT_forecast_field
39
+ elif BERT_strategy == 'temporal_interpolation' :
40
+ bert_f = prepare_batch_BERT_temporal_field
41
+ else :
42
+ assert False
43
+
44
+ rng_idx = 1
45
+ for ifield, (field, infos) in enumerate(zip(fields_data, fields_infos)) :
46
+ for ilevel, (field_data, token_info) in enumerate(zip(field, infos)) :
47
+
48
+ # no masking for static fields or if masking rate = 0
49
+ if fields[ifield][1][0] > 0 and fields[ifield][5][0] > 0. :
50
+
51
+ ret = bert_f( cf, ifield, field_data, token_info, rngs[rng_idx])
52
+ (field_data, token_info, target, tokens_masked_idx_list) = ret
53
+
54
+ if target is not None :
55
+ fields_targets[ifield].append( target)
56
+ fields_tokens_masked_idx_list[ifield].append( tokens_masked_idx_list)
57
+
58
+ rng_idx += 1
59
+
60
+ sources[ifield].append( field_data.unsqueeze(1) )
61
+ token_infos[ifield].append( token_info )
62
+
63
+ # merge along vertical level
64
+ sources[ifield] = torch.cat( sources[ifield], 1)
65
+ token_infos[ifield] = torch.cat( token_infos[ifield], 1)
66
+ # merge along vertical level, for target we have level, batch, ... ordering
67
+ fields_targets[ifield] = torch.cat( fields_targets[ifield],0) \
68
+ if len(fields_targets[ifield]) > 0 else fields_targets[ifield]
69
+
70
+ return (sources, token_infos, fields_targets, fields_tokens_masked_idx_list)
71
+
72
+ ####################################################################################################
73
+ def prepare_batch_BERT_field( cf, ifield, source, token_info, rng) :
74
+
75
+ # shortcuts
76
+ mr = partial( torch.nn.functional.interpolate, mode='trilinear')
77
+ fl = torch.flatten
78
+ tr = torch.transpose
79
+ sq = torch.squeeze
80
+ usq = torch.unsqueeze
81
+ cnt_nz = torch.count_nonzero
82
+
83
+ # collapse token dimensions
84
+ source_shape0 = source.shape
85
+ source = torch.flatten( torch.flatten( source, 1, 3), 2, 4)
86
+
87
+ # select random token in the selected space-time cube to be masked/deleted
88
+ BERT_frac = cf.fields[ifield][5][0]
89
+ BERT_frac_mask = cf.fields[ifield][5][1]
90
+ BERT_frac_rndm = cf.fields[ifield][5][2]
91
+ BERT_frac_mr = cf.fields[ifield][5][3]
92
+ BERT_mr_max = 2
93
+ token_size = cf.fields[ifield][4]
94
+ batch_dim = source.shape[0]
95
+ num_tokens = source.shape[1]
96
+
97
+ masking_ratios = rng.random( batch_dim) * BERT_frac
98
+ # number of tokens masked per batch entry
99
+ nums_masked = np.ceil( num_tokens * masking_ratios).astype(np.int64)
100
+ tokens_masked_idx_list = [ torch.tensor(rng.permutation(num_tokens)[:nms]) for nms in nums_masked]
101
+
102
+ # linear indices for masking
103
+ tokens_masked_idx_list = [tokens_masked_idx_list[i] + num_tokens * i for i in range(batch_dim)]
104
+ idx = torch.cat( tokens_masked_idx_list)
105
+
106
+ # flatten along first two dimension to simplify linear indexing (which then requires an
107
+ # easily computable row offset)
108
+ source_shape = source.shape
109
+ source = torch.flatten( source, 0, 1)
110
+
111
+ # keep masked tokens for loss computation
112
+ target = source[idx].clone()
113
+
114
+ # climatological mean of normalized data
115
+ global_mean = 0. * torch.mean(source, 0)
116
+ global_std = torch.std(source, 0)
117
+
118
+ # Conditional masking (all are sampled independently so the fractions are only amortized)
119
+ # BERT_frac_mask = fraction masked (80%)
120
+ # BERT_frac_rndm = fraction noisy tokens (10%)
121
+ # face_mr_BERT = fraction of multi-res "masked" tokens (downsampled instead of fully masked)
122
+ # remainder = untouched (10%)
123
+ #
124
+ # conditional idx for tokens to be masked with mean or random values
125
+ idx_mask_cond = torch.tensor( rng.random( idx.shape[0])) < BERT_frac_mask
126
+ idx_rndm_cond = torch.tensor( rng.random( idx.shape[0])) < BERT_frac_rndm
127
+ idx_mr_cond = torch.tensor( rng.random( idx.shape[0])) < BERT_frac_mr
128
+
129
+ # set mask
130
+ source[ idx[idx_mask_cond] ] = global_mean
131
+
132
+ # set noisy tokens (noise is field independent)
133
+ # TODO: fudge factor of 0.1
134
+ cnt_nz_rndm = cnt_nz(idx_rndm_cond)
135
+ dim_embed = source.shape[1]
136
+ rnd_w = tr( torch.tensor( rng.random( cnt_nz_rndm, dtype=np.float32)).repeat((dim_embed,1)), 0,1 )
137
+ source[ idx[idx_rndm_cond] ] = rnd_w * global_mean + (1.-rnd_w) * source[ idx[idx_rndm_cond] ] + \
138
+ 0.1 * global_std * torch.randn( (cnt_nz_rndm, source.shape[1]))
139
+
140
+ # randomly coarsened tokens
141
+ if BERT_frac_mr > 0. and idx_mr_cond.shape[0] > 0 and idx_mr_cond.any().item() :
142
+ # version with uniform multi-res downsampling per batch for computational efficiency
143
+ # (in particular on GPU)
144
+ ts = torch.tensor( [token_size[1], token_size[2]], dtype=torch.int)
145
+ mrs = ((rng.random() * (ts - BERT_mr_max*ts)) + BERT_mr_max*ts).int()
146
+ mrs = (token_size[0], mrs[0], mrs[1])
147
+ ts = token_size
148
+ # interpolate to smaller size and then interpolate up -> coarsened version with same size
149
+ # unsqueeze(usq()) is required since channel dimension is expected
150
+ temp = mr( mr( usq( source[ idx[idx_mr_cond] ].reshape( (-1,ts[0],ts[1],ts[2])), 1), mrs), ts)
151
+ source[ idx[idx_mr_cond] ] = sq( fl( temp, -3, -1))
152
+
153
+ # recover batch dimension which was flattend for easier indexing and also token dimensions
154
+ source = torch.reshape( torch.reshape( source, source_shape), source_shape0)
155
+
156
+ return (source, token_info, target, tokens_masked_idx_list)
157
+
158
+ ####################################################################################################
159
+ def prepare_batch_BERT_forecast_field( cf, ifield, source, token_info, rng) :
160
+
161
+ nt = cf.forecast_num_tokens
162
+ num_tokens = source.shape[-6:-3]
163
+ num_tokens_space = num_tokens[1] * num_tokens[2]
164
+ idxs = (num_tokens[0]-nt) * num_tokens_space + torch.arange(nt * num_tokens_space)
165
+
166
+ # collapse token dimensions
167
+ source_shape0 = source.shape
168
+ source = torch.flatten( torch.flatten( source, 1, 3), 2, 4)
169
+
170
+ # linear indices for masking
171
+ num_tokens = source.shape[1]
172
+ tokens_masked_idx_list = [idxs + num_tokens * i for i in range( source.shape[0] )]
173
+ idx = torch.cat( tokens_masked_idx_list)
174
+
175
+ source_shape = source.shape
176
+ # flatten along first two dimension to simplify linear indexing (which then requires an
177
+ # easily computable row offset)
178
+ source = torch.flatten( source, 0, 1)
179
+
180
+ # keep masked tokens for loss computation
181
+ target = source[idx].clone()
182
+
183
+ # masking
184
+ global_mean = 0. * torch.mean(source, 0)
185
+ source[ idx ] = global_mean
186
+
187
+ # recover batch dimension which was flattend for easier indexing
188
+ source = torch.reshape( torch.reshape( source, source_shape), source_shape0)
189
+
190
+ return (source, token_info, target, tokens_masked_idx_list)
191
+
192
+ ####################################################################################################
193
+ def prepare_batch_BERT_temporal_field( cf, ifield, source, token_info, rng) :
194
+
195
+ num_tokens = source.shape[-6:-3]
196
+ num_tokens_space = num_tokens[1] * num_tokens[2]
197
+
198
+ #backward compatibility: mask only middle token
199
+ if not hasattr( cf, 'idx_time_mask'):
200
+ idx_time_mask = int( np.floor(num_tokens[0] / 2.))
201
+ idxs = idx_time_mask * num_tokens_space + torch.arange(num_tokens_space)
202
+ else: #list of idx_time_mask
203
+ idxs = torch.concat([i*num_tokens_space + torch.arange(num_tokens_space) for i in cf.idx_time_mask])
204
+
205
+ # collapse token dimensions
206
+ source_shape0 = source.shape
207
+ source = torch.flatten( torch.flatten( source, 1, 3), 2, 4)
208
+
209
+ # linear indices for masking
210
+ num_tokens = source.shape[1]
211
+ idx = torch.cat( [idxs + num_tokens * i for i in range( source.shape[0] )] )
212
+ tokens_masked_idx_list = [idxs + num_tokens * i for i in range( source.shape[0] )]
213
+ source_shape = source.shape
214
+ # flatten along first two dimension to simplify linear indexing (which then requires an
215
+ # easily computable row offset)
216
+ source = torch.flatten( source, 0, 1)
217
+
218
+ # keep masked tokens for loss computation
219
+ target = source[idx].clone()
220
+
221
+ # masking
222
+ global_mean = 0. * torch.mean(source, 0)
223
+ source[ idx ] = global_mean
224
+
225
+ # recover batch dimension which was flattend for easier indexing
226
+ source = torch.reshape( torch.reshape( source, source_shape), source_shape0)
227
+
228
+ return (source, token_info, target, tokens_masked_idx_list)
vendor/atmorep-official/atmorep/transformer/__init__.py ADDED
File without changes
vendor/atmorep-official/atmorep/transformer/axial_attention.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration / lucidrains@github.com
10
+ #
11
+ # description : Based on:
12
+ # https://github.com/lucidrains/axial-attention/blob/master/axial_attention/axial_attention.py
13
+ #
14
+ # license :
15
+ #
16
+ ####################################################################################################
17
+
18
+ #
19
+ import torch
20
+ from torch import nn
21
+ from operator import itemgetter
22
+ import code
23
+ # code.interact(local=locals())
24
+
25
+ ####################################################################################################
26
+
27
+ # helper functions
28
+
29
+ def exists(val):
30
+ return val is not None
31
+
32
+ def map_el_ind(arr, ind):
33
+ return list(map(itemgetter(ind), arr))
34
+
35
+ def sort_and_return_indices(arr):
36
+ indices = [ind for ind in range(len(arr))]
37
+ arr = zip(arr, indices)
38
+ arr = sorted(arr)
39
+ return map_el_ind(arr, 0), map_el_ind(arr, 1)
40
+
41
+ # calculates the permutation to bring the input tensor to something attend-able
42
+ # also calculates the inverse permutation to bring the tensor back to its original shape
43
+
44
+ def calculate_permutations(num_dimensions, emb_dim, axial_dims = None) :
45
+
46
+ total_dimensions = num_dimensions + 2
47
+ emb_dim = emb_dim if emb_dim > 0 else (emb_dim + total_dimensions)
48
+ if not axial_dims :
49
+ axial_dims = [ind for ind in range(1, total_dimensions) if ind != emb_dim]
50
+
51
+ permutations = []
52
+
53
+ for axial_dim in axial_dims:
54
+ last_two_dims = [axial_dim, emb_dim]
55
+ dims_rest = set(range(0, total_dimensions)) - set(last_two_dims)
56
+ permutation = [*dims_rest, *last_two_dims]
57
+ permutations.append(permutation)
58
+
59
+ return permutations
60
+
61
+ ####################################################################################################
62
+
63
+ # helper classes
64
+
65
+ class PermuteToFrom(nn.Module):
66
+
67
+ def __init__(self, permutation, fn):
68
+
69
+ super().__init__()
70
+ self.fn = fn
71
+ _, inv_permutation = sort_and_return_indices(permutation)
72
+ self.permutation = permutation
73
+ self.inv_permutation = inv_permutation
74
+
75
+ def forward(self, x, kv = None, **kwargs):
76
+
77
+ if None == kv :
78
+
79
+ axial = x.permute(*self.permutation).contiguous()
80
+
81
+ shape = axial.shape
82
+ *_, t, d = shape
83
+
84
+ # merge all but axial dimension
85
+ axial = axial.reshape(-1, t, d)
86
+
87
+ # attention
88
+ axial = self.fn(axial, **kwargs)
89
+
90
+ # restore to original shape and permutation
91
+ axial = axial.reshape(*shape)
92
+ axial = axial.permute(*self.inv_permutation).contiguous()
93
+
94
+ else :
95
+
96
+ axial = x.permute(*self.permutation)
97
+ axial_kv = kv.permute(*self.permutation)
98
+
99
+ shape = list(axial.shape)
100
+ shape_kv = axial_kv.shape
101
+ *_, t, d = shape
102
+ *_, t_kv, d_kv = shape_kv
103
+
104
+ # adjust token number along non-axial dimensions for efficient axial attention
105
+ # physically consistent by inserting the missing tokens for a lower res version
106
+ # axial dimension does not have to be matched; repeat_interleave is noop if ratio = 1
107
+ ratio1, ratio2 = shape[1] / shape_kv[1], shape[2] / shape_kv[2]
108
+ if int(ratio1) != 1 :
109
+ axial_kv = torch.repeat_interleave( axial_kv, max(1,int(ratio1)), dim=1 ).contiguous()
110
+ axial = torch.repeat_interleave( axial, max(1,int(1./ratio1)), dim=1 ).contiguous()
111
+ if int(ratio2) != 1 :
112
+ axial_kv = torch.repeat_interleave( axial_kv, max(1,int(ratio2)), dim=2 ).contiguous()
113
+ axial = torch.repeat_interleave( axial, max(1,int(1./ratio2)), dim=2 ).contiguous()
114
+ assert axial.shape[1:3] == axial_kv.shape[1:3], 'axial attn requires matching token numbers'
115
+
116
+
117
+ # merge all but axial dimension
118
+ axial_shape_perm = list(axial.shape)
119
+ axial = axial.reshape(-1, t, d)
120
+ axial_kv = axial_kv.reshape(-1, t_kv, d_kv)
121
+
122
+ # attention
123
+ axial = self.fn( axial, axial_kv, **kwargs)
124
+
125
+ # embedding dimension does not match input since not un-embedded in head
126
+ axial_shape_perm[-1] = -1
127
+ shape[-1] = -1
128
+
129
+ # restore original, permuted shape modulo embedding dimension
130
+ axial = axial.reshape( *axial_shape_perm)
131
+
132
+ # recover original token numbers for q if necessary by taking mean over inserted
133
+ if shape[1] < shape_kv[1] :
134
+ axial = axial.reshape( shape[:2] + [int(1./ratio1)] + shape[2:] ).mean( dim=2)
135
+ if shape[2] < shape_kv[2] :
136
+ axial = axial.reshape( shape[:3] + [int(1./ratio2)] + shape[3:] ).mean( dim=3)
137
+
138
+ # restore to original shape modulo embedding dimension
139
+ axial = axial.reshape( *shape)
140
+ axial = axial.permute( *self.inv_permutation).contiguous()
141
+
142
+ return axial
143
+
144
+ ####################################################################################################
145
+ # self attention
146
+
147
+ class SelfAttention(nn.Module):
148
+
149
+ #############################################
150
+ def __init__(self, dim, heads, dim_heads = None):
151
+
152
+ super().__init__()
153
+
154
+ self.dim_heads = (dim // heads) if dim_heads is None else dim_heads
155
+ dim_hidden = self.dim_heads * heads
156
+
157
+ self.heads = heads
158
+ self.to_q = nn.Linear(dim, dim_hidden, bias = False)
159
+ self.to_kv = nn.Linear(dim, 2 * dim_hidden, bias = False)
160
+ self.to_out = nn.Linear(dim_hidden, dim)
161
+
162
+ #############################################
163
+ def forward(self, x, kv = None):
164
+
165
+ kv = x if kv is None else kv
166
+ q, k, v = (self.to_q(x), *self.to_kv(kv).chunk(2, dim=-1))
167
+
168
+ b, t, d, h, e = *q.shape, self.heads, self.dim_heads
169
+
170
+ merge_heads = lambda x: x.reshape(b, -1, h, e).transpose(1, 2).reshape(b * h, -1, e)
171
+ q, k, v = map(merge_heads, (q, k, v))
172
+
173
+ dots = torch.einsum('bie,bje->bij', q, k) * (e ** -0.5)
174
+ dots = dots.softmax(dim=-1)
175
+ out = torch.einsum('bij,bje->bie', dots, v)
176
+
177
+ out = out.reshape(b, h, -1, e).transpose(1, 2).reshape(b, -1, d)
178
+ out = self.to_out(out)
179
+
180
+ return out
181
+
182
+ ####################################################################################################
183
+ # axial attention class
184
+
185
+ class AxialAttention( nn.Module):
186
+
187
+ #############################################
188
+ def __init__(self, dim, num_dimensions = 2, heads = 8, dim_heads = None, dim_index = -1,
189
+ sum_axial_out = True):
190
+
191
+ assert (dim % heads) == 0, 'hidden dimension must be divisible by number of heads'
192
+ super().__init__()
193
+
194
+ self.dim = dim
195
+ self.total_dimensions = num_dimensions + 2
196
+ self.dim_index = dim_index if dim_index > 0 else (dim_index + self.total_dimensions)
197
+
198
+ attentions = []
199
+ for permutation in calculate_permutations(num_dimensions, dim_index):
200
+ attentions.append(PermuteToFrom(permutation, SelfAttention(dim, heads,dim_heads)))
201
+
202
+ self.axial_attentions = nn.ModuleList(attentions)
203
+ self.sum_axial_out = sum_axial_out
204
+
205
+ #############################################
206
+ def forward(self, x, kv = None):
207
+
208
+ assert len(x.shape) == self.total_dimensions, 'input does not have correct number of dimensions'
209
+ assert x.shape[self.dim_index] == self.dim, 'input does not have correct input dimension'
210
+
211
+ if self.sum_axial_out:
212
+ return sum(map(lambda axial_attn: axial_attn(x, kv), self.axial_attentions))
213
+
214
+ out = x
215
+ for axial_attn in self.axial_attentions:
216
+ out = axial_attn(out)
217
+
218
+ return out
219
+
220
+ ####################################################################################################
221
+
222
+ class CrossAttention(nn.Module):
223
+
224
+ #############################################
225
+ def __init__(self, dims_embed, num_heads, dim_heads):
226
+
227
+ super().__init__()
228
+
229
+ assert 2 == len(dims_embed)
230
+ self.dim_heads = dim_heads
231
+ dim_hidden = self.dim_heads * num_heads
232
+
233
+ self.num_heads = num_heads
234
+ self.to_q = nn.Linear( dims_embed[0], dim_hidden, bias = False)
235
+ self.to_kv = nn.Linear( dims_embed[1], 2 * dim_hidden, bias = False)
236
+
237
+ #############################################
238
+ def forward(self, x, kv):
239
+
240
+ # per head projection
241
+ q, k, v = (self.to_q(x), *self.to_kv(kv).chunk(2, dim=-1))
242
+
243
+ # extract require shape / dimension parameters
244
+ b, t, d, h, e = *q.shape, self.num_heads, self.dim_heads
245
+
246
+ # reshape and transpose so that axial dimension is second but last
247
+ mh = lambda x: x.reshape(b, -1, h, e).transpose(1, 2).reshape(b * h, -1, e)
248
+ q, k, v = map( mh, (q, k, v))
249
+
250
+ # compute attention
251
+ dots = torch.einsum('bie,bje->bij', q, k) * (e ** -0.5)
252
+ dots = dots.softmax(dim=-1)
253
+ out = torch.einsum('bij,bje->bie', dots, v)
254
+
255
+ # recover input shape
256
+ out = out.reshape(b, h, -1, e).transpose(1, 2).reshape(b, -1, d)
257
+
258
+ return out
259
+
260
+ ####################################################################################################
261
+
262
+ class MultiFieldAxialAttention( nn.Module):
263
+
264
+ #############################################
265
+ def __init__(self, att_dims, dims_embed, num_heads_self, num_heads_cross,
266
+ sum_axial_out = False, dropout_rate = 0.0):
267
+
268
+ super().__init__()
269
+
270
+ assert 0 == (dims_embed[0] % (num_heads_self + num_heads_cross))
271
+ dim_embed_heads = int(dims_embed[0] / (num_heads_self + num_heads_cross))
272
+
273
+ self.num_fields = len(dims_embed)
274
+ self.dims_embed = dims_embed
275
+ self.att_dims = att_dims
276
+ num_dimensions = 3 # vertical, time, space (folded together)
277
+ idx_embed = -1
278
+
279
+ self.sum_axial_out = sum_axial_out
280
+
281
+ self.lnorms = torch.nn.ModuleList()
282
+ for ifield in range( self.num_fields) :
283
+ self.lnorms.append( nn.LayerNorm( dims_embed[ifield], elementwise_affine=False) )
284
+
285
+ # for all axes, create self and cross attention heads
286
+ self.axial_attentions = nn.ModuleList( [nn.ModuleList() for _ in att_dims] )
287
+ for ip, permutation in enumerate(calculate_permutations( num_dimensions, idx_embed, att_dims)) :
288
+
289
+ # self-attention
290
+ self.axial_attentions[ip].append( PermuteToFrom( permutation, CrossAttention( [dims_embed[0],dims_embed[0]], num_heads_self, dim_embed_heads)))
291
+
292
+ # cross attention
293
+ for n in range( 1, len(dims_embed)) :
294
+ hs = PermuteToFrom( permutation, CrossAttention( [dims_embed[0], dims_embed[n]],
295
+ num_heads_cross, dim_embed_heads))
296
+ self.axial_attentions[ip].append( hs)
297
+
298
+ dim_hidden = dim_embed_heads * (num_heads_self + (num_heads_cross * (self.num_fields-1)) )
299
+ to_outs = [nn.Linear( dim_hidden, dims_embed[0]) for _ in att_dims]
300
+ self.to_outs = torch.nn.ModuleList( to_outs)
301
+
302
+ self.dropout = torch.nn.Dropout( p=dropout_rate)
303
+
304
+ #############################################
305
+ def forward(self, *args):
306
+
307
+ pass_through = args[0]
308
+ fields_lnormed = [self.lnorms[ifield](field) for ifield, field in enumerate( args)]
309
+
310
+ out = []
311
+ for i_ax, axial_attns in enumerate( self.axial_attentions):
312
+ outs_axis = []
313
+ for i, attn_heads in enumerate(axial_attns) :
314
+ outs_axis.append( attn_heads( fields_lnormed[0], fields_lnormed[i]) )
315
+ ax_out = self.to_outs[i_ax]( torch.cat( outs_axis, -1) )
316
+ # parallel processing with sum_axial_out and then summation or serial otherwise
317
+ if self.sum_axial_out :
318
+ out.append( ax_out)
319
+ else :
320
+ fields_lnormed[0] = ax_out
321
+
322
+ if self.sum_axial_out :
323
+ out = sum( out)
324
+ else :
325
+ out = fields_lnormed[0]
326
+
327
+ out = self.dropout( out)
328
+
329
+ return pass_through + out
vendor/atmorep-official/atmorep/transformer/decoder.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import numpy as np
19
+ import math
20
+
21
+ from atmorep.transformer.transformer_base import MLP
22
+ from atmorep.transformer.transformer_attention import MultiSelfAttentionHead, MultiCrossAttentionHead
23
+
24
+
25
+ class Decoder(torch.nn.Module) :
26
+
27
+ ###################################
28
+ def __init__(self, num_layers, dim_embed = 2048,
29
+ num_heads = 8, num_mlp_layers = 2,
30
+ self_att = False, cross_att_ratio = 0.5 ):
31
+ '''
32
+ Vaswani transformer corresponds to self_att = True and cross_att_ratio = 1.
33
+ '''
34
+ super( Decoder, self).__init__()
35
+
36
+ self.num_layers = num_layers
37
+ self.dim_embed = dim_embed
38
+
39
+ self.len_block = 2
40
+ if self_att :
41
+ self.len_block = 3
42
+
43
+ num_heads_other = int(num_heads * cross_att_ratio)
44
+ num_heads_self = num_heads - num_heads_other
45
+
46
+ self.blocks = torch.nn.ModuleList()
47
+ for _ in range( self.num_layers) :
48
+ # attention sub-block
49
+ if self_att :
50
+ self.blocks.append( MultiSelfAttentionHead( dim_embed, num_heads_self))
51
+ # cross attention between encoder and decoder
52
+ self.blocks.append( MultiCrossAttentionHead( dim_embed, num_heads_self, num_heads_other))
53
+ # feature space mapping sub-block
54
+ self.blocks.append( MLP( dim_embed, num_mlp_layers))
55
+
56
+ ###################################
57
+ def forward(self, token_seq_embed, encoder_out):
58
+ '''Evaluate decoder'''
59
+
60
+ for il in range(self.num_layers) :
61
+ token_seq_embed = self.blocks[2*il]( token_seq_embed, encoder_out[il] )
62
+ token_seq_embed = self.blocks[2*il+1]( token_seq_embed, encoder_out[il] )
63
+
64
+ return token_seq_embed
65
+
66
+ ###################################
67
+ def get_attention( self, xin, iblock) :
68
+ '''
69
+ Get attention and projected values from specific layer and her head
70
+ '''
71
+
72
+ assert False
73
+
74
+ # # embedding
75
+ # token_seq_embed = prepare_token( self, xin, self.size_token_info)
76
+
77
+ # # attention heads + feature space mappings
78
+ # for idx in range(2*iblock) :
79
+ # token_seq_embed = self.blocks[idx]( token_seq_embed)
80
+ # atts, vsh = self.blocks[2*iblock].get_attention( token_seq_embed)
81
+
82
+ # return (atts, vsh)
vendor/atmorep-official/atmorep/transformer/interformer.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ####################################################################################################
2
+ #
3
+ # Copyright (C) 2022
4
+ #
5
+ ####################################################################################################
6
+ #
7
+ # project : atmorep
8
+ #
9
+ # author : atmorep collaboration
10
+ #
11
+ # description :
12
+ #
13
+ # license :
14
+ #
15
+ ####################################################################################################
16
+
17
+ import torch
18
+ import numpy as np
19
+ import math
20
+
21
+ from atmorep.transformer.transformer_base import MLP
22
+ from atmorep.transformer.transformer_attention import MultiInterAttentionHead
23
+
24
+
25
+ class Interformer(torch.nn.Module) :
26
+
27
+ def __init__(self, num_layers, dim_input, dims_embed,
28
+ num_heads_self = 2, num_heads_coupling = 2, num_mlp_layers = 2,
29
+ num_tokens = [], size_token_info = 6):
30
+ '''
31
+
32
+ '''
33
+
34
+ super(Interformer, self).__init__()
35
+ self.num_layers = num_layers
36
+ self.num_tokens = num_tokens
37
+ self.size_token_info = size_token_info
38
+
39
+ # learnable linear embedding
40
+ self.token_embed = torch.nn.Linear( dim_input, dims_embed[0] - size_token_info)
41
+
42
+ self.blocks = torch.nn.ModuleList()
43
+ for _ in range( num_layers) :
44
+ # attention sub-block
45
+ self.blocks.append( MultiInterAttentionHead( dims_embed, num_heads_self, num_heads_coupling,
46
+ num_tokens ))
47
+ # feature space mapping sub-block
48
+ self.blocks.append( MLP( dims_embed[0], num_mlp_layers))
49
+
50
+ def forward(self, xin):
51
+ # never called directly but only through multiformer
52
+ assert False