Sync from GitHub 077955b
Browse files- .gitattributes +3 -0
- README.md +184 -0
- assets/banner.png +3 -0
- assets/icon.png +3 -0
- assets/logo.png +3 -0
- assets/logo.svg +1 -0
- models/README.md +34 -0
- models/aardvark.md +40 -0
- models/ace2.md +23 -0
- models/arches.md +21 -0
- models/aurora.md +51 -0
- models/canglong.md +60 -0
- models/fengwu.md +13 -0
- models/fuxi.md +15 -0
- models/fuxi_ens.md +58 -0
- models/fuxi_s2s.md +19 -0
- models/gencast.md +47 -0
- models/genfocal.md +65 -0
- models/graphcast.md +16 -0
- models/neuralgcm.md +103 -0
- models/neuralgcm_precip.md +22 -0
- models/orca_dl.md +20 -0
- models/pangu.md +16 -0
- models/pbc.md +46 -0
- models/stormcast.md +25 -0
- models/translocation.md +47 -0
- models/tropicyclonenet.md +20 -0
- models/unicm.md +10 -0
- models/weathernext_cyclones.md +43 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ 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 |
+
assets/banner.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/icon.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/logo.png filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
tags:
|
| 5 |
+
- weather
|
| 6 |
+
- weather-forecasting
|
| 7 |
+
- climate
|
| 8 |
+
- earth-science
|
| 9 |
+
- sea-surface-temperature
|
| 10 |
+
- pytorch
|
| 11 |
+
- jax
|
| 12 |
+
- model-zoo
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
> Synced from [GISWLH/WeatherAI](https://github.com/GISWLH/WeatherAI) — code, tests and issues live on GitHub. No model weights are stored here:
|
| 16 |
+
> official weights stay with their authors and are downloaded on demand under their own licences.
|
| 17 |
+
|
| 18 |
+
<div align="center">
|
| 19 |
+
|
| 20 |
+
<img src="https://github.com/GISWLH/WeatherAI/blob/main/docs\assets\banner.png" alt="WeatherAI: 20 peer-reviewed weather and climate AI models in one readable PyTorch library" width="100%">
|
| 21 |
+
|
| 22 |
+
**English** · [简体中文](https://github.com/GISWLH/WeatherAI/blob/main/README_zh.md) · [日本語](https://github.com/GISWLH/WeatherAI/blob/main/README_ja.md)
|
| 23 |
+
|
| 24 |
+
[](https://github.com/GISWLH/WeatherAI/blob/main/LICENSE)
|
| 25 |
+
[](https://github.com/GISWLH/WeatherAI/blob/main/pyproject.toml)
|
| 26 |
+
[](https://pytorch.org)
|
| 27 |
+
[](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\neuralgcm.md)
|
| 28 |
+
[](#model-zoo)
|
| 29 |
+
[](https://huggingface.co/spaces/LonghaoWang/weatherai-graphcast-smoke)
|
| 30 |
+
[](https://huggingface.co/LonghaoWang/WeatherAI)
|
| 31 |
+
[](https://github.com/GISWLH/WeatherAI/stargazers)
|
| 32 |
+
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
**WeatherAI** re-implements the AI weather and climate models published in *Nature*, *Science* and their sister
|
| 36 |
+
journals as plain, readable PyTorch `nn.Module`s, and checks every port against the authors' own code.
|
| 37 |
+
Load the official weights, compare with the original, fine-tune, or start from a Lite config that runs on a laptop.
|
| 38 |
+
|
| 39 |
+
## ✨ Highlights
|
| 40 |
+
|
| 41 |
+
**19 Oct 2026 research update:** [PBC probabilistic S2S calibration, GenFocal two-stage
|
| 42 |
+
lite downscaling, and gray-swan translocation diagnostics](https://github.com/GISWLH/WeatherAI/blob/main/docs\research_20261019.md)
|
| 43 |
+
are importable by name and integrated with WeatherPre. All three passed the
|
| 44 |
+
[synthetic ZeroGPU smoke](https://github.com/GISWLH/WeatherAI/blob/main/docs\results\research_20261019_zerogpu.json).
|
| 45 |
+
PBC and the diagnostic wrapper extend the existing 20-model zoo; they are not
|
| 46 |
+
two additional pretrained weather forecasters.
|
| 47 |
+
|
| 48 |
+
```python
|
| 49 |
+
from weatherai import PBC, PBC_lite, GenFocal, GenFocal_lite, Translocation, Translocation_lite
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
- 🧩 **20 models, one library** — Pangu, GraphCast, Aurora, GenCast, NeuralGCM, FuXi-S2S, CAS-Canglong and more, all
|
| 53 |
+
with the same `import` style and plain tensors in / tensors out. No per-model environments to juggle.
|
| 54 |
+
- 🔀 **Multi-framework** — native PyTorch throughout; official **JAX** code is kept where it matters (NeuralGCM training and
|
| 55 |
+
precipitation) and as the reference for GraphCast, GenCast and WeatherNext; **ONNX**-only releases (FuXi-ENS, FuXi-S2S)
|
| 56 |
+
are rebuilt as PyTorch modules. Runs on CPU and **CUDA** GPUs.
|
| 57 |
+
- ✅ **Verified, not just rewritten** — official checkpoints strict-load for 14 models, and outputs match the official
|
| 58 |
+
implementation (often bit-for-bit). Every claim has a script and a JSON result; GPU runs are tested on an NVIDIA
|
| 59 |
+
Blackwell GPU through a Hugging Face ZeroGPU Space.
|
| 60 |
+
- 🪶 **Full size and Lite** — the paper-size network for real experiments, plus Lite configs (same code, small grid) for 15
|
| 61 |
+
models that train on a laptop CPU or a single small GPU.
|
| 62 |
+
|
| 63 |
+
## 🚀 Quick start
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
pip install git+https://github.com/GISWLH/WeatherAI.git # or: git clone ... && pip install -e ".[dev]"
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
```python
|
| 70 |
+
import torch
|
| 71 |
+
from weatherai.models import Canglong_lite, GraphCast_lite, Aurora_small
|
| 72 |
+
|
| 73 |
+
sst = Canglong_lite()(torch.randn(1, 8, 8, 32, 64)) # 3-month SST outlook -> (1, 1, 3, 32, 64)
|
| 74 |
+
step = GraphCast_lite()(torch.randn(1, 4, 32, 64)) # one 6-hour step on a tiny icosahedral mesh
|
| 75 |
+
aurora = Aurora_small(pretrained=True) # official weights (~450 MB), strict-loaded into native PyTorch
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
<a id="model-zoo"></a>
|
| 79 |
+
## 🗺️ Model zoo
|
| 80 |
+
|
| 81 |
+
🔓 official weights load · 🎯 matches the official code · ⚡ tested on a CUDA GPU · 🪶 Lite config · 🆕 new.
|
| 82 |
+
Each **docs** link opens a one-page summary: usage, what was verified, and what was not.
|
| 83 |
+
|
| 84 |
+
### 🌍 Global medium-range forecasting
|
| 85 |
+
|
| 86 |
+
| Model | Import | Notes |
|
| 87 |
+
|-------|--------|-------|
|
| 88 |
+
| **Pangu-Weather** | `from weatherai.models import Pangu, Pangu_lite` | 3-D Earth-specific transformer · *Nature* 2023 · [paper](https://doi.org/10.1038/s41586-023-06185-3) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\pangu.md) · 🪶 |
|
| 89 |
+
| **FuXi** | `from weatherai.models import FuXi` | Cube embedding + Swin-V2 U-Transformer · *npj Clim. Atmos. Sci.* 2023 · [paper](https://doi.org/10.1038/s41612-023-00512-1) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi.md) |
|
| 90 |
+
| **FengWu** | `from weatherai.models import FengWu, FengWu_lite` | Multi-modal encode–fuse–decode · *Commun. Earth Environ.* 2025 · [paper](https://doi.org/10.1038/s43247-025-02502-y) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fengwu.md) · 🪶 |
|
| 91 |
+
| **GraphCast** | `from weatherai.models import GraphCast, GraphCast_lite` | Grid ↔ icosahedral-mesh GNN, stage-wise parity vs JAX and PhysicsNeMo · *Science* 2023 · [paper](https://doi.org/10.1126/science.adi2336) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\graphcast.md) · 🎯 ⚡ 🪶 |
|
| 92 |
+
| **Aurora** | `from weatherai.models import Aurora, Aurora_lite, Aurora_small` | Perceiver + Swin-3D foundation model · *Nature* 2025 · [paper](https://doi.org/10.1038/s41586-025-09005-y) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\aurora.md) · 🔓 🎯 ⚡ 🪶 |
|
| 93 |
+
| **NeuralGCM** | `from weatherai.models.neuralgcm import NeuralGCMLearnedComponents, train` | Differentiable dynamical core + learned physics; training in JAX · *Nature* 2024 · [paper](https://doi.org/10.1038/s41586-024-07744-y) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\neuralgcm.md) · 🔓 🎯 ⚡ 🪶 |
|
| 94 |
+
| **Aardvark Weather** | `from weatherai.models.aardvark import AardvarkE2E, load_official_encoder` | End-to-end forecasting from raw observations · *Nature* 2025 · [paper](https://doi.org/10.1038/s41586-025-08897-0) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\aardvark.md) · 🔓 🎯 ⚡ 🪶 |
|
| 95 |
+
|
| 96 |
+
### 🎲 Ensemble & generative
|
| 97 |
+
|
| 98 |
+
| Model | Import | Notes |
|
| 99 |
+
|-------|--------|-------|
|
| 100 |
+
| **GenCast** | `from weatherai.models.gencast import GenCast_lite, GenCastDenoiser_from_official` | Diffusion ensemble: native denoiser + DPM-Solver++ sampler · *Nature* 2025 · [paper](https://doi.org/10.1038/s41586-024-08252-9) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\gencast.md) · 🔓 🎯 ⚡ 🪶 |
|
| 101 |
+
| **FuXi-ENS** | `from weatherai.models.fuxi_ens import FuXiENS, FuXiENS_lite, load_official` | 2.5 B-parameter latent-noise ensemble, rebuilt from ONNX · *Sci. Adv.* 2025 · [paper](https://doi.org/10.1126/sciadv.adu2854) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi_ens.md) · 🔓 🎯 ⚡ 🪶 |
|
| 102 |
+
| **ArchesWeather / Gen** | `from weatherai.models.arches import ArchesWeather_lite, ArchesWeatherGen_lite, convert` | 3-D attention forecaster + flow-matching generator · *Sci. Adv.* 2026 · [paper](https://doi.org/10.1126/sciadv.adx2372) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\arches.md) · 🔓 🎯 ⚡ 🪶 |
|
| 103 |
+
| **NeuralGCM precipitation** | `from weatherai.models.neuralgcm import precip` | Stochastic precipitation checkpoints + trainable loss (JAX) · *Sci. Adv.* 2026 · [paper](https://doi.org/10.1126/sciadv.adv6891) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\neuralgcm_precip.md) · 🔓 ⚡ |
|
| 104 |
+
|
| 105 |
+
### 🌊 Sub-seasonal to seasonal, climate & ocean
|
| 106 |
+
|
| 107 |
+
| Model | Import | Notes |
|
| 108 |
+
|-------|--------|-------|
|
| 109 |
+
| **CAS-Canglong** 🆕 | `from weatherai.models.canglong import Canglong, Canglong_lite, load_official` | 3-D Earth-specific transformer for 1–3-month global SST · *Adv. Sci.* · [paper](https://doi.org/10.1002/advs.78271) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\canglong.md) · 🔓 🎯 ⚡ 🪶 |
|
| 110 |
+
| **FuXi-S2S** | `from weatherai.models.fuxi_s2s.convert import load_official` | Sub-seasonal Swin-V2 ensemble (1.04 B), rebuilt from ONNX · *Nat. Commun.* 2024 · [paper](https://doi.org/10.1038/s41467-024-50714-1) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi_s2s.md) · 🔓 🎯 ⚡ 🪶 |
|
| 111 |
+
| **UniCM** | `from weatherai.models.unicm import UniCM, UniCMConfig` | Coupled climate-mode transformer (ENSO and other modes) · *Nat. Mach. Intell.* 2026 · [paper](https://doi.org/10.1038/s42256-026-01245-5) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\unicm.md) · 🎯 ⚡ 🪶 |
|
| 112 |
+
| **ORCA-DL** | `from weatherai.models.orca_dl import ORCADL, load_official` | Global 3-D ocean forecaster, monthly steps · *Sci. Adv.* 2025 · [paper](https://doi.org/10.1126/sciadv.adu2488) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\orca_dl.md) · 🔓 🎯 ⚡ |
|
| 113 |
+
| **ACE2-ERA5** | `from weatherai.models.ace2 import ACE2, load_official` | Spherical Fourier neural operator climate emulator with conservation · *npj Clim. Atmos. Sci.* 2025 · [paper](https://doi.org/10.1038/s41612-025-01090-0) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\ace2.md) · 🔓 🎯 ⚡ |
|
| 114 |
+
| **GenFocal** | `from weatherai.models.genfocal import GenFocalNet, reflow_loss, sample_flow` | Generative debiasing flow (lite core) · *Nat. Mach. Intell.* 2026 · [paper](https://doi.org/10.1038/s42256-026-01308-7) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\genfocal.md) · 🎯 ⚡ 🪶 |
|
| 115 |
+
|
| 116 |
+
### 🌀 Regional & tropical cyclones
|
| 117 |
+
|
| 118 |
+
| Model | Import | Notes |
|
| 119 |
+
|-------|--------|-------|
|
| 120 |
+
| **StormCast** | `from weatherai.models.stormcast import StormCast, StormCast_lite, load_official` | 3 km regional (HRRR) regression U-Net + EDM diffusion · *Sci. Adv.* 2026 · [paper](https://doi.org/10.1126/sciadv.adv0423) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\stormcast.md) · 🔓 🎯 ⚡ 🪶 |
|
| 121 |
+
| **TropiCycloneNet** | `from weatherai.models.tropicyclonenet import TCNM, load_official` | Multimodal tropical-cyclone track + intensity · *Nat. Commun.* 2025 · [paper](https://doi.org/10.1038/s41467-025-61087-4) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\tropicyclonenet.md) · 🔓 🎯 ⚡ |
|
| 122 |
+
| **WeatherNext Cyclones** | `from weatherai.models.weathernext_cyclones import WeatherNextCyclonesNet_from_official, WeatherNextCyclonesNative_lite` | Functional generative network for cyclones (Mini) · *Nature* 2026 · [paper](https://doi.org/10.1038/s41586-026-10953-2) · [docs](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\weathernext_cyclones.md) · 🔓 🎯 ⚡ 🪶 |
|
| 123 |
+
|
| 124 |
+
More: [all model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · [verification log](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md) · [roadmap & planned models](https://github.com/GISWLH/WeatherAI/blob/main/docs\roadmap.md)
|
| 125 |
+
|
| 126 |
+
## 🔬 What "verified" means
|
| 127 |
+
|
| 128 |
+
For each port, the unchanged official code (PyTorch, JAX or onnxruntime) and the native module receive the same weights
|
| 129 |
+
and inputs, and their outputs are compared; the scripts are in [`scripts/`](https://github.com/GISWLH/WeatherAI/tree/main/scripts) and the numbers in
|
| 130 |
+
[`docs/results/`](https://github.com/GISWLH/WeatherAI/tree/main/docs\results). GPU runs use [`scripts/gpu_smoke.py`](https://github.com/GISWLH/WeatherAI/blob/main/scripts\gpu_smoke.py) on the Hugging Face Space.
|
| 131 |
+
These are implementation checks, not forecast-skill claims; each model page lists what was **not** verified.
|
| 132 |
+
|
| 133 |
+
## 🤗 Hugging Face
|
| 134 |
+
|
| 135 |
+
- **Space** — [LonghaoWang/weatherai-graphcast-smoke](https://huggingface.co/spaces/LonghaoWang/weatherai-graphcast-smoke): ZeroGPU smoke tests for every model.
|
| 136 |
+
- **Hub** — [LonghaoWang/WeatherAI](https://huggingface.co/LonghaoWang/WeatherAI): model card and model pages, synced from this repository by [`.github/workflows/sync_hf.yml`](https://github.com/GISWLH/WeatherAI/blob/main/.github\workflows\sync_hf.yml).
|
| 137 |
+
- Official weights stay with their authors and keep their licences (Apache-2.0, MIT, CC-BY, CC-BY-NC(-ND), …): WeatherAI downloads them on demand and never redistributes them.
|
| 138 |
+
|
| 139 |
+
## 🧪 Tests
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
pip install -e ".[dev]"
|
| 143 |
+
pytest tests/models -q # unit tests; parity references are stored under tests/models/*/data
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
## 📖 Citation
|
| 147 |
+
|
| 148 |
+
Please cite the original paper of each model you use (linked above) and, if WeatherAI helped, this repository
|
| 149 |
+
([`CITATION.cff`](https://github.com/GISWLH/WeatherAI/blob/main/CITATION.cff)):
|
| 150 |
+
|
| 151 |
+
```bibtex
|
| 152 |
+
@software{wang_weatherai,
|
| 153 |
+
author = {Wang, Longhao},
|
| 154 |
+
title = {{WeatherAI}: a PyTorch model zoo of AI weather and climate models},
|
| 155 |
+
url = {https://github.com/GISWLH/WeatherAI},
|
| 156 |
+
year = {2026}
|
| 157 |
+
}
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
## 🤝 Contributing
|
| 161 |
+
|
| 162 |
+
Issues and pull requests are welcome. A new model is accepted when it comes with a native module, a Lite config, a parity
|
| 163 |
+
script against the official code, unit tests and a page in `docs/models/`. If WeatherAI saves you time, a ⭐ helps others find it.
|
| 164 |
+
|
| 165 |
+
## License
|
| 166 |
+
|
| 167 |
+
[MIT](https://github.com/GISWLH/WeatherAI/blob/main/LICENSE) — Copyright (c) 2026 Longhao Wang (GISWLH); adapted code from [WeatherLearn](https://github.com/lizhuoq/WeatherLearn)
|
| 168 |
+
(MIT), see [NOTICE](https://github.com/GISWLH/WeatherAI/blob/main/NOTICE). Model weights are under their authors' licences. Research and educational code: forecast skill
|
| 169 |
+
depends on data, training and weights, and nothing here replaces an operational forecasting system.
|
| 170 |
+
|
| 171 |
+
<!-- readme-visual-refresh:artwork-source -->
|
| 172 |
+
[Editable banner artwork · Excalidraw](https://github.com/GISWLH/WeatherAI/blob/main/assets\readme\banner.excalidraw)
|
| 173 |
+
<!-- /readme-visual-refresh:artwork-source -->
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
<!-- readme-visual-refresh:execution -->
|
| 177 |
+
## Demonstration captured on EHPC · 17 October 2026
|
| 178 |
+
|
| 179 |
+

|
| 180 |
+
|
| 181 |
+
New visualization of the repository's existing parity-check JSON. This displays a recorded historical check; it is not a new training or model-validation run.
|
| 182 |
+
|
| 183 |
+
[Execution record and checksums](https://github.com/GISWLH/WeatherAI/blob/main/assets\readme\execution-record.json).
|
| 184 |
+
<!-- /readme-visual-refresh:execution -->
|
assets/banner.png
ADDED
|
Git LFS Details
|
assets/icon.png
ADDED
|
|
Git LFS Details
|
assets/logo.png
ADDED
|
Git LFS Details
|
assets/logo.svg
ADDED
|
|
models/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model pages
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [Verification log](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md) · [Roadmap](https://github.com/GISWLH/WeatherAI/blob/main/docs\roadmap.md)
|
| 4 |
+
|
| 5 |
+
One page per model: how to use it, what was verified against the official code, and what was not. The same pages are
|
| 6 |
+
published on the Hugging Face Hub ([LonghaoWang/WeatherAI](https://huggingface.co/LonghaoWang/WeatherAI)).
|
| 7 |
+
|
| 8 |
+
| Group | Model | Page | Paper |
|
| 9 |
+
|-------|-------|------|-------|
|
| 10 |
+
| 🎯 Probabilistic S2S calibration | PBC | [pbc.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\pbc.md) | *Nature* 2026, [doi:10.1038/s41586-026-11126-x](https://doi.org/10.1038/s41586-026-11126-x) |
|
| 11 |
+
| 🔬 Extreme-event diagnostics | Gray-swan translocation (existing backbones) | [translocation.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\translocation.md) | *Science Advances* 2026, [doi:10.1126/sciadv.ady9406](https://doi.org/10.1126/sciadv.ady9406) |
|
| 12 |
+
| 🌍 Medium-range | Pangu-Weather | [pangu.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\pangu.md) | *Nature* 2023, [doi:10.1038/s41586-023-06185-3](https://doi.org/10.1038/s41586-023-06185-3) |
|
| 13 |
+
| 🌍 Medium-range | FuXi | [fuxi.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi.md) | *npj Climate and Atmospheric Science* 2023, [doi:10.1038/s41612-023-00512-1](https://doi.org/10.1038/s41612-023-00512-1) |
|
| 14 |
+
| 🌍 Medium-range | FengWu | [fengwu.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fengwu.md) | *Communications Earth & Environment* 2025, [doi:10.1038/s43247-025-02502-y](https://doi.org/10.1038/s43247-025-02502-y) |
|
| 15 |
+
| 🌍 Medium-range | GraphCast | [graphcast.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\graphcast.md) | *Science* 2023, [doi:10.1126/science.adi2336](https://doi.org/10.1126/science.adi2336) |
|
| 16 |
+
| 🌍 Medium-range | Aurora | [aurora.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\aurora.md) | *Nature* 2025, [doi:10.1038/s41586-025-09005-y](https://doi.org/10.1038/s41586-025-09005-y) |
|
| 17 |
+
| 🌍 Medium-range | NeuralGCM | [neuralgcm.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\neuralgcm.md) | *Nature* 2024, [doi:10.1038/s41586-024-07744-y](https://doi.org/10.1038/s41586-024-07744-y) |
|
| 18 |
+
| 🌍 Medium-range | Aardvark Weather | [aardvark.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\aardvark.md) | *Nature* 2025, [doi:10.1038/s41586-025-08897-0](https://doi.org/10.1038/s41586-025-08897-0) |
|
| 19 |
+
| 🎲 Ensemble | GenCast | [gencast.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\gencast.md) | *Nature* 2025, [doi:10.1038/s41586-024-08252-9](https://doi.org/10.1038/s41586-024-08252-9) |
|
| 20 |
+
| 🎲 Ensemble | FuXi-ENS | [fuxi_ens.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi_ens.md) | *Science Advances* 2025, [doi:10.1126/sciadv.adu2854](https://doi.org/10.1126/sciadv.adu2854) |
|
| 21 |
+
| 🎲 Ensemble | ArchesWeather / ArchesWeatherGen | [arches.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\arches.md) | *Science Advances* 2026, [doi:10.1126/sciadv.adx2372](https://doi.org/10.1126/sciadv.adx2372) |
|
| 22 |
+
| 🎲 Ensemble | NeuralGCM precipitation | [neuralgcm_precip.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\neuralgcm_precip.md) | *Science Advances* 2026, [doi:10.1126/sciadv.adv6891](https://doi.org/10.1126/sciadv.adv6891) |
|
| 23 |
+
| 🌊 S2S / climate / ocean | CAS-Canglong 🆕 | [canglong.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\canglong.md) | *Advanced Science*, [doi:10.1002/advs.78271](https://doi.org/10.1002/advs.78271) |
|
| 24 |
+
| 🌊 S2S / climate / ocean | FuXi-S2S | [fuxi_s2s.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi_s2s.md) | *Nature Communications* 2024, [doi:10.1038/s41467-024-50714-1](https://doi.org/10.1038/s41467-024-50714-1) |
|
| 25 |
+
| 🌊 S2S / climate / ocean | UniCM | [unicm.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\unicm.md) | *Nature Machine Intelligence* 2026, [doi:10.1038/s42256-026-01245-5](https://doi.org/10.1038/s42256-026-01245-5) |
|
| 26 |
+
| 🌊 S2S / climate / ocean | ORCA-DL | [orca_dl.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\orca_dl.md) | *Science Advances* 2025, [doi:10.1126/sciadv.adu2488](https://doi.org/10.1126/sciadv.adu2488) |
|
| 27 |
+
| 🌊 S2S / climate / ocean | ACE2-ERA5 | [ace2.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\ace2.md) | *npj Climate and Atmospheric Science* 2025, [doi:10.1038/s41612-025-01090-0](https://doi.org/10.1038/s41612-025-01090-0) |
|
| 28 |
+
| 🌊 S2S / climate / ocean | GenFocal | [genfocal.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\genfocal.md) | *Nature Machine Intelligence* 2026, [doi:10.1038/s42256-026-01308-7](https://doi.org/10.1038/s42256-026-01308-7) |
|
| 29 |
+
| 🌀 Regional / cyclones | StormCast | [stormcast.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\stormcast.md) | *Science Advances* 2026, [doi:10.1126/sciadv.adv0423](https://doi.org/10.1126/sciadv.adv0423) |
|
| 30 |
+
| 🌀 Regional / cyclones | TropiCycloneNet | [tropicyclonenet.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\tropicyclonenet.md) | *Nature Communications* 2025, [doi:10.1038/s41467-025-61087-4](https://doi.org/10.1038/s41467-025-61087-4) |
|
| 31 |
+
| 🌀 Regional / cyclones | WeatherNext Cyclones | [weathernext_cyclones.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\weathernext_cyclones.md) | *Nature* 2026, [doi:10.1038/s41586-026-10953-2](https://doi.org/10.1038/s41586-026-10953-2) |
|
| 32 |
+
|
| 33 |
+
Journal versions were checked against Crossref when they were added (CAS-Canglong: checked 2026-10-18, *Advanced Science*,
|
| 34 |
+
published 15 October 2026, article e78271). Planned and blocked models (FuXi-DA, NowcastNet): [roadmap](https://github.com/GISWLH/WeatherAI/blob/main/docs\roadmap.md).
|
models/aardvark.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Aardvark Weather — native PyTorch encoder + processor + decoder
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature* 2025, [doi:10.1038/s41586-025-08897-0](https://doi.org/10.1038/s41586-025-08897-0)
|
| 4 |
+
|
| 5 |
+
Aardvark Weather (*Nature* 2025; official code CC0) = observation **encoder** → **processor** → station **decoder**.
|
| 6 |
+
`weatherai.models.aardvark` now contains explicit ports of all three (parameter names identical to the official ones):
|
| 7 |
+
|
| 8 |
+
| file | contents |
|
| 9 |
+
|---|---|
|
| 10 |
+
| `setconv.py` | `SetConv` (Gaussian ConvDeepSet: off-grid→grid, grid→grid, grid→off-grid, density channel) |
|
| 11 |
+
| `aardvark.py` | `ViT` (per-variable patch embed + variable aggregation, or MLP + single embed), `AardvarkProcessor` (24 h step) |
|
| 12 |
+
| `unet.py` | cylindrical-padding conv / transposed conv, `Down`, `Up`, `Unet` |
|
| 13 |
+
| `system.py` | `AardvarkEncoder` (instrument set-convs + 277-channel stack + patch-3 ViT), `AardvarkDecoder` (U-Net → station set-conv → MLP), `AardvarkE2E`, official-checkpoint / sample loaders |
|
| 14 |
+
|
| 15 |
+
```python
|
| 16 |
+
import torch
|
| 17 |
+
from weatherai.models.aardvark import (AardvarkProcessor_lite, AardvarkE2E, load_official_encoder, load_official_decoder,
|
| 18 |
+
load_official_processor, load_official_sample)
|
| 19 |
+
|
| 20 |
+
m = AardvarkProcessor_lite().eval() # 0.23 M params, random init, 60x31 grid; trainable
|
| 21 |
+
y = m(torch.randn(1, 35, 60, 31)) # (1, 31, 60, 24): normalised 24 h tendency
|
| 22 |
+
|
| 23 |
+
# Official weights (HF dataset av555/aardvark-weather, trained_model/...) - all strict=True loads:
|
| 24 |
+
enc = load_official_encoder("trained_model/encoder/epoch_96")
|
| 25 |
+
dec = load_official_decoder("trained_model/decoder/tas/lt_1/epoch_18")
|
| 26 |
+
proc = load_official_processor("trained_model/processor/forecast_1/epoch_0")
|
| 27 |
+
task = load_official_sample("aardvark-weather-public/data/sample_data_final.pkl") # official sample (CUDA pickle -> CPU)
|
| 28 |
+
station, forecast, init_state = AardvarkE2E(enc, [proc], dec).eval()(task, return_gridded=True) # 24 h forecast; station = normalised tas
|
| 29 |
+
# every piece can be built with other sizes / depths and trained (see tests: tiny encoder+processor+decoder training step)
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
**Verified (CPU; official code run on the official sample as reference, `scripts/aardvark_system_reference.py`):**
|
| 33 |
+
encoder initial state max |Δ| = 0.0 (atol 1e-5, rtol 1e-4); station decoder (tas, lt_1) max |Δ| = 0; E2E (encoder → 1 processor → decoder)
|
| 34 |
+
station output max |Δ| = 2.9e-6, gridded forecast max relative error 8e-5 (values up to 1.2e5); processor on random input max |Δ| ≈ 2e-4
|
| 35 |
+
(unchanged earlier check). Unit tests: set-conv vs naive loop, NaN handling, cylindrical conv shapes, parameter names, a training step on a tiny
|
| 36 |
+
whole system. HF ZeroGPU: see [docs/model_status.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md).
|
| 37 |
+
**Not implemented / not verified:** the official data loaders and training scripts (they depend on multi-TB local memmaps; the repo itself says they
|
| 38 |
+
cannot be executed), the FiLM / attention options of the official U-Net (unused by the released weights), `e2e_finetuned` checkpoints (not loaded/tested),
|
| 39 |
+
decoder checkpoints other than `tas/lt_1`, processors `forecast_2..10`, multi-step E2E, other station variables (`ws`), skill vs. observations.
|
| 40 |
+
The official sample has one timestep only, so the comparison covers that single case.
|
models/ace2.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ACE2-ERA5 — native PyTorch spherical Fourier neural operator (official weights load; matches `fme` rollouts)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *npj Climate and Atmospheric Science* 2025, [doi:10.1038/s41612-025-01090-0](https://doi.org/10.1038/s41612-025-01090-0)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/ace2` re-implements Ai2's ACE2-ERA5 (180×360 Gauss–Legendre grid, 44 inputs → 50 outputs, 6 h step) without depending on `fme` or `torch-harmonics`:
|
| 6 |
+
`sht.py` (real SHT/iSHT on Gauss–Legendre and equiangular grids), `sfno.py` (SFNO: 8 blocks, embed 384, instance norm, per-degree complex channel mixing = Driscoll–Healy convolution, inner linear skip + outer identity skip, big skip), `model.py` (the stepper around the network) and `convert.py`.
|
| 7 |
+
Weights: HF [`allenai/ACE2-ERA5`](https://huggingface.co/allenai/ACE2-ERA5) (Apache-2.0, not committed).
|
| 8 |
+
|
| 9 |
+
```python
|
| 10 |
+
from weatherai.models.ace2 import download, load_official
|
| 11 |
+
from weatherai.models.ace2.data import load_case
|
| 12 |
+
|
| 13 |
+
download("/workspace/ckpt/ace2") # 1.8 GB checkpoint + 2020 initial conditions + forcing (0.55 GB)
|
| 14 |
+
m = load_official("/workspace/ckpt/ace2") # strict-load; 455,831,040 network parameters
|
| 15 |
+
ic, forcing, times = load_case("/workspace/ckpt/ace2", ic_index=5, n_steps=32) # 2020-06-01T00Z, 8 days
|
| 16 |
+
outs = m.rollout(ic, forcing, 32) # list of dicts name -> [1, 180, 360], physical units (all 50 outputs)
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
* **Stepper (taken from `fme`'s `step_with_adjustments` + the checkpoint's config, not guessed):** normalise → SFNO → denormalise → clamp the 16 `force_positive` fields → pin the global-mean dry-air surface pressure to its initial value (float64) → close the global moisture budget by rescaling precipitation and recomputing the advective tendency → overwrite surface temperature where `round(ocean_fraction)==1` with the next-step forcing. `DSWRFtoa` is read at t+1.
|
| 20 |
+
* **Verified (CPU fp32):** SHT/iSHT equal `torch_harmonics` 0.8.0 (max relative difference ≤3e-8 forward, 0 inverse; Gauss–Legendre 180×360, equiangular and small grids); a random-weight lite SFNO matches `fme`'s `SphericalFourierNeuralOperatorNet` to 1e-5; with the official checkpoint a **12-step (3-day) rollout from the shipped 2020-01-01 initial condition matches the official `fme` Stepper**: max relative error over all 50 output fields 2.6e-7 after step 1, 4.6e-6 after step 2, 7.9e-5 after step 12 (float32 round-off growth; surface pressure differs by ≤0.03 Pa). Unit tests: conservation of dry air and global moisture budget by construction, SST prescription, gradients.
|
| 21 |
+
* **Real case (CPU, one 8-day rollout from 2020-06-01T00Z, lat-weighted RMSE vs WeatherBench2 ERA5 interpolated to 1.5°; ACE2 vs persistence):** day 1: h500 7.27 vs 56.9 m, TMP850 0.739 vs 2.86 K, TMP2m 0.687 vs 1.76 K; day 3: h500 17.8 vs 92.3 m, TMP850 1.35 vs 3.96 K, TMP2m 1.09 vs 2.35 K; day 7: h500 53.3 vs 112 m, TMP850 2.62 vs 4.77 K, TMP2m 1.98 vs 3.1 K. Global dry air drifted by 3.2e-05 Pa over 8 days; global-mean precipitation 2.90 mm/day; CPU time 236 s for 32 steps.
|
| 22 |
+
* **HF ZeroGPU (RTX PRO 6000 Blackwell MIG 2g.48gb, torch 2.13+cu130, 2026-10-02): PASS** — strict load 3.6 s; relative error vs the stored CPU slices 1.5e-06 (1 step) / 6.5e-06 (4 steps) with TF32 off; 32-step (8-day) rollout 3.53 s; dry-air drift over 8 days 3.1e-05 Pa; peak GPU memory 3.1 GB; GPU RMSEs reproduce the CPU report (day-1 h500 7.266 m, day-7 h500 53.278 m).
|
| 23 |
+
* **NOT verified:** multi-year / climate-scale stability and the paper's AMIP-style forced-response results (the repository's 100-year inference was not run), skill statistics over more than one case, comparison with the paper's scores, other ACE2 checkpoints (ACE2-SOM, ACE2-EAMv3), training, bf16/fp16. The forecast case uses the interpolation of 1° output to ERA5's 1.5° grid, which smooths slightly. The native SHT is O(L²) memory per transform table (about 24 MB per direction in fp32).
|
models/arches.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ArchesWeatherGen — native PyTorch 3D-attention forecaster + flow-matching generator (official weights load; parity with geoarches)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Science Advances* 2026, [doi:10.1126/sciadv.adx2372](https://doi.org/10.1126/sciadv.adx2372)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/arches` re-implements INRIA's ArchesWeather (deterministic, 24 h step, 1.5° = 121×240 grid, 6 surface + 6 levels×13 pressure-level variables) and ArchesWeatherGen
|
| 6 |
+
(4 deterministic members embedded + a conditional flow-matching network that samples the residual). No `geoarches` dependency at runtime. Weights: HF [`gcouairon/ArchesWeather`](https://huggingface.co/gcouairon/ArchesWeather) (BSD; not committed) and normalisation stats from the geoarches repo (BSD-3, fetched from GitHub raw, not committed).
|
| 7 |
+
|
| 8 |
+
```python
|
| 9 |
+
from weatherai.models.arches import ArchesWeatherGen_lite, convert
|
| 10 |
+
|
| 11 |
+
m = ArchesWeatherGen_lite() # tiny trainable config, same code path
|
| 12 |
+
convert.download("/workspace/ckpt/arches") # ~2.2 GB; loading the Lightning ckpt needs `pip install omegaconf`
|
| 13 |
+
model, stats = convert.load_official_gen("/workspace/ckpt/arches") # strict-load of gen net + 4 embedded det members
|
| 14 |
+
```
|
| 15 |
+
See `scripts/arches_real_data.py` (WeatherBench2 ERA5 1.5° sample -> det ensemble mean and generative members).
|
| 16 |
+
|
| 17 |
+
* **Verified (CPU fp32, oracle = `geoarches` in `/workspace/venv/ga`, `scripts/arches_parity_full.py`, `tests/parity/arches`):** official checkpoints, full size, random normalised inputs: deterministic member max|Δ| 1.6e-5 (rel 1.8e-6); 4-member average rel 8.5e-7; generative-net call rel 8.3e-7; a full 5-step seeded sample max|Δ| 1.5e-5 (rel 9.9e-7). Lite-config exact embedder/backbone parity and the flow-matching scheduler vs diffusers `FlowMatchEulerDiscreteScheduler` pass (unit tests: 7 passed, 1 full-size test gated by `ARCHES_FULL=1`).
|
| 18 |
+
* **Quirks reproduced on purpose:** (1) the released generative net was trained with an int32-overflow bug in its month/hour time features (they come out as month∈{1,12}, hour∈{0,23}); `legacy_overflow_time_features` reproduces it (deterministic members use true month/hour); (2) the SwiGLU block uses a GELU gate; (3) the two "skip" members add the input state.
|
| 19 |
+
* **Real-data check (CPU, WeatherBench2 ERA5 1.5°, init 2020-06-01 12Z, lat-weighted RMSE):** z500 +24 h: persistence 561, det ensemble mean 39.0, generative member 54.2, 3-member mean 46.2 m²/s²; t850: 2.84 / 0.589 / 0.829 / 0.672 K; +48 h generative member z500 110 vs persistence 749. This is a **single case**; no ensemble-skill/CRPS statistics, no comparison to the paper's scores.
|
| 20 |
+
* **HF ZeroGPU (RTX PRO 6000 Blackwell MIG 2g.48gb, torch 2.13+cu130, 2026-10-02): PASS** — strict load 4.8 s; det member forward 0.29 s; relative error vs CPU 2.5e-6 (det) / 2.0e-6 (5-step sample) with TF32 off; real case reproduces the CPU RMSEs (z500 det 39.0, gen member 54.4); 25-step generative member 4.4 s; peak GPU memory 3.0 GB.
|
| 21 |
+
* **NOT verified:** probabilistic skill (CRPS/spread-skill) over many cases, long rollouts, parity with random inputs only (no real-input element-wise comparison vs geoarches), the 4 separate public deterministic seeds beyond seed 0 on GPU, bf16, and training (only gradient flow of the lite configs).
|
models/aurora.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Aurora — native PyTorch implementation
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature* 2025, [doi:10.1038/s41586-025-09005-y](https://doi.org/10.1038/s41586-025-09005-y)
|
| 4 |
+
|
| 5 |
+
`weatherai.models.aurora` re-implements Aurora (*Nature* 2025; MIT,
|
| 6 |
+
[microsoft/aurora](https://github.com/microsoft/aurora)) as explicit `nn.Module`s with readable `forward()`s,
|
| 7 |
+
in the style of the Pangu/FuXi code (no dependency on the `aurora` pip package):
|
| 8 |
+
|
| 9 |
+
| file | contents |
|
| 10 |
+
|---|---|
|
| 11 |
+
| `patch_embed.py` | `LevelPatchEmbed` – per-variable 3-D patch conv over (history × P × P) |
|
| 12 |
+
| `perceiver.py` | `PerceiverAttention`, `PerceiverResampler` (post-norm cross-attention + MLP) |
|
| 13 |
+
| `encoder.py` | `Perceiver3DEncoder` – patch-embed surface(+static) and every level → level aggregation → + position / patch-scale / lead-time / absolute-time Fourier embeddings |
|
| 14 |
+
| `swin3d.py` | `Swin3DBackbone`: `SwinStage`s of `Swin3DBlock` (shifted 3-D windows with periodic-longitude mask, AdaLN on lead time), `PatchMerging3D`, `PatchSplitting3D` (linear1 → reshape/permute → crop padding → norm → linear2) |
|
| 15 |
+
| `decoder.py` | `Perceiver3DDecoder` – level de-aggregation + per-variable linear unpatchify |
|
| 16 |
+
| `aurora.py` | `Aurora` (plain-tensor I/O, official normalisation statistics, `Aurora_lite`, `Aurora_small`) |
|
| 17 |
+
| `official.py` | the old thin wrapper over `microsoft-aurora` – **reference oracle only** (tests / parity) |
|
| 18 |
+
|
| 19 |
+
```python
|
| 20 |
+
import torch
|
| 21 |
+
from weatherai.models import Aurora, Aurora_lite, Aurora_small
|
| 22 |
+
|
| 23 |
+
m = Aurora_lite().eval() # ~3 M params, random init, CPU friendly
|
| 24 |
+
surf = torch.randn(1, 2, 4, 16, 32) + torch.tensor([278., 0, 0, 1e5]).view(1, 1, 4, 1, 1) # (B,T=2,[2t,10u,10v,msl],H,W)
|
| 25 |
+
static = torch.randn(3, 16, 32) # [lsm, z, slt]
|
| 26 |
+
atmos = torch.randn(1, 2, 5, 4, 16, 32) # (B,T,[z,u,v,t,q],L=4,H,W)
|
| 27 |
+
with torch.no_grad():
|
| 28 |
+
surf_next, atmos_next = m(surf, static, atmos) # physical units, +6 h
|
| 29 |
+
|
| 30 |
+
# fully configurable / trainable: any depths, heads, window, patch size, embed dim
|
| 31 |
+
my = Aurora(embed_dim=128, encoder_depths=(2, 4, 2), decoder_depths=(2, 4, 2),
|
| 32 |
+
encoder_num_heads=(4, 8, 16), decoder_num_heads=(16, 8, 4), num_heads=4, window_size=(2, 6, 12))
|
| 33 |
+
loss = (my(surf, static, atmos)[0] - surf[:, 1]).pow(2).mean(); loss.backward() # see tests for a full training step
|
| 34 |
+
|
| 35 |
+
# Official small pretrained checkpoint (~450 MB from HF microsoft/aurora), strict=True load into the native model
|
| 36 |
+
small = Aurora_small(pretrained=True).eval() # or Aurora_small(checkpoint_path="aurora-0.25-small-pretrained.ckpt")
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
**Verified (numerically, vs. the official `microsoft-aurora` package at b628d7c, same weights, same inputs; fp32,
|
| 40 |
+
`atol=1e-5, rtol=1e-4` – observed max abs difference was exactly 0.0 on CPU):** random-weight state-dict transfer
|
| 41 |
+
`strict=True` for 3 configs (window (2,2,2)/(2,3,5)/(2,6,12), uneven depths, v3 lead-time embedding), grids
|
| 42 |
+
16×32 … 32×64 incl. the odd-latitude crop (H%4==1) and patch grids that need merge-padding / split-cropping
|
| 43 |
+
(25×44); the official `aurora-0.25-small-pretrained.ckpt` strict-loads into the native `Aurora_small` and
|
| 44 |
+
reproduces the official output on 33×64 inputs. HF ZeroGPU results: see [docs/model_status.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md).
|
| 45 |
+
Unit tests: `tests/models/aurora` (shapes, odd grids, merge/split + window helpers, param-name layout,
|
| 46 |
+
25-step training demo, grads, parity).
|
| 47 |
+
**Not verified / not included:** skill on real ERA5/HRES data (random inputs only), multi-step rollout;
|
| 48 |
+
upstream features that are *not* ported: LoRA, stochastic/ensemble mode, level-conditioned embeddings,
|
| 49 |
+
dynamic/atmos-static variables, separate/modulation heads (so the fine-tuned 1.3 B, HRES-0.1°, air-pollution,
|
| 50 |
+
wave and v1.5 checkpoints are **not** loadable; `aurora-0.25-pretrained` and `-small-pretrained` are); the 1.3 B
|
| 51 |
+
checkpoint was not run; built-in normalisation stats only for the 13 standard ERA5 levels.
|
models/canglong.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CAS-Canglong — native PyTorch, bit-exact vs the official code (S2S global SST)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Advanced Science*, "Improved Skills for Predicting Sub-Seasonal to Seasonal Global Sea Surface Temperature", [doi:10.1002/advs.78271](https://doi.org/10.1002/advs.78271) (preprint: arXiv:2409.05369)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/canglong` re-implements CAS-Canglong, a 3-D Earth-specific transformer (Pangu-style, U-shaped with a skip
|
| 6 |
+
concatenation) that predicts global sea surface temperature 1–3 months ahead from 16 monthly frames of 8 ocean/atmosphere
|
| 7 |
+
variables on the 0.25° ERA5 grid. Official code: [GISWLH/CAS-Canglong](https://github.com/GISWLH/CAS-Canglong) (MIT; the model is
|
| 8 |
+
defined in `notebook/model.ipynb`, helpers in `utils/`). Weights: figshare [10.6084/m9.figshare.26779969](https://doi.org/10.6084/m9.figshare.26779969)
|
| 9 |
+
(not committed here). Parameter names equal the official module, so an official `state_dict` strict-loads.
|
| 10 |
+
|
| 11 |
+
```python
|
| 12 |
+
import torch
|
| 13 |
+
from weatherai.models.canglong import Canglong, CanglongConfig, Canglong_lite, canglong_loss, load_official
|
| 14 |
+
|
| 15 |
+
m = Canglong_lite() # 0.29 M params, 8 months x 8 vars on 32x64 -> 3 months; CPU friendly
|
| 16 |
+
y = m(torch.randn(1, 8, 8, 32, 64)) # (1, 1, 3, 32, 64) normalised SST
|
| 17 |
+
loss = canglong_loss(y[0, 0], torch.randn(3, 32, 64)); loss.backward() # official loss: lead-weighted MSE + correlation penalty
|
| 18 |
+
|
| 19 |
+
full = Canglong(CanglongConfig.official()) # 43.9 M params; input (B, 8, 16, 721, 1440) -> (B, 1, 3, 721, 1440)
|
| 20 |
+
model = load_official("canglong3_0005_600ep_base.tar") # figshare checkpoint (torch.save of the module; trusted file)
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
* **Structure:** 3-D patch embedding (2 months × 4 × 4 px) → 8 × 181 × 360 tokens, embed 72 → 2 Earth-specific blocks →
|
| 24 |
+
down-sample (8 × 91 × 180, 144 ch) → 6 + 6 blocks → up-sample → 2 blocks → concat with the first-stage skip → transposed-conv
|
| 25 |
+
recovery (1 × 8 × 8) → centre crop to 3 × 721 × 1440. Window (2, 6, 12), heads (6, 12, 12, 6), drop-path 0–0.2.
|
| 26 |
+
Channel 0 is SST (land set to 0, then per-variable standardisation); the notebook appends net solar radiation as channel 7.
|
| 27 |
+
* **`load_official`** accepts the released full-module pickle (the notebook's `__main__` / `canglong.*` / `weatherlearn.*`
|
| 28 |
+
classes are unpickled as empty stand-ins and only their tensors are read), plain state dicts, `nn.DataParallel` prefixes and
|
| 29 |
+
the WeatherLearn naming (`proj` / `conv`) used by `notebook/Infer.ipynb`.
|
| 30 |
+
* **Verified (CPU, fp32, random weights; `scripts/canglong_parity.py`, `docs/results/canglong_parity*.json`):** the unchanged
|
| 31 |
+
official notebook cell + `utils` is executed and its `state_dict` strict-loaded into the native model.
|
| 32 |
+
Full size (1 × 8 × 16 × 721 × 1440, 43,906,258 params): **max |Δ| = 0.0**, output (1, 1, 3, 721, 1440), forward 52 s native /
|
| 33 |
+
64 s official on 4 CPU cores, peak RSS 9.1 GB for both models together. Lite config: forward max |Δ| = 0.0, gradients through
|
| 34 |
+
the official loss identical (max rel. error 0.0 over 115 parameter tensors). 5 unit tests (stored lite reference, parameter
|
| 35 |
+
count, pickled-module loading with stubbed classes, state-dict variants, training-loss decrease).
|
| 36 |
+
* **Official-code behaviour reproduced on purpose:**
|
| 37 |
+
1. The output patch (1, 8, 8) is twice the embedding patch (4, 4); the recovered 1448 × 2880 field is centre-cropped to
|
| 38 |
+
721 × 1440, so output pixels are decoded from the central half of the token grid (token rows 45–135 of 181, columns 90–269
|
| 39 |
+
of 360). The time axis keeps the last 3 of the 8 time tokens (the crop depth is Pangu's 13 levels).
|
| 40 |
+
2. Every second block shifts its windows by a hard-coded (1, 3, 6) (= window // 2 for the official window).
|
| 41 |
+
3. `patchrecovery2d` / `patchrecovery3d` are computed and discarded, `patchembed2d` / `patchembed3d` are never called; they
|
| 42 |
+
are kept (`legacy_heads=True`) only so official checkpoints strict-load. The native forward skips them (same output).
|
| 43 |
+
4. Loss: lead weights (1, 1.5, 2) multiply *inside* the square, and the correlation penalty box (named `nino` in the
|
| 44 |
+
notebook, rows 288:336 × cols 480:640) covers 18°N–6°N, 120°E–160°E on the 90°N→90°S, 0→359.75°E grid.
|
| 45 |
+
* **Released checkpoint on released data (GPU, fp32; `scripts/canglong_official_checkpoint.py`,
|
| 46 |
+
[`docs/results/canglong_official_checkpoint.json`](https://github.com/GISWLH/WeatherAI/blob/main/docs\results\canglong_official_checkpoint.json), 2026-10-18):** the figshare
|
| 47 |
+
`canglong3_0005_600ep_base.tar`, `normalized_data.pt` and `ocean_mask.npy` (MD5s equal to the figshare record) on an
|
| 48 |
+
NVIDIA RTX 4090. `load_official` and the unchanged notebook cell strict-load the same weights; on the last 16-month window
|
| 49 |
+
of the released data the two outputs are **identical (max |Δ| = 0.0)**, finite, 0.8 s per full-size forward. Against the
|
| 50 |
+
following 3 months of that file the predicted SST has area-weighted ocean RMSE 1.20 / 1.01 / 0.87 K at leads 1–3
|
| 51 |
+
(persistence 1.19 / 2.39 / 3.59 K) — one window, a sanity check rather than a skill estimate.
|
| 52 |
+
* **GPU smoke:** `scripts/gpu_smoke.py canglong` (lite parity + lite training step + one full-size forward) is wired into the
|
| 53 |
+
HF Space; the same smoke on CPU (2026-10-10): **PASS** — lite vs official reference max |Δ| 0.0, lite gradients finite,
|
| 54 |
+
full-size forward 54 s. Log: [model_status.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md).
|
| 55 |
+
* **Live forecasts from the same model family:** [canglong.earth](https://canglong.earth) and the Hugging Face Space
|
| 56 |
+
[LonghaoWang/CAS-Canglong](https://huggingface.co/spaces/LonghaoWang/CAS-Canglong) (16-month SST and ENSO outlook).
|
| 57 |
+
The operational 16-month model is a later Swin-transformer variant described in the
|
| 58 |
+
[CAS-Canglong repository](https://github.com/GISWLH/CAS-Canglong); it is not part of this port.
|
| 59 |
+
* **NOT verified / not done:** the paper's skill scores over the full hindcast period, the data pipeline (ERA5 download,
|
| 60 |
+
normalisation statistics), training at full size.
|
models/fengwu.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FengWu — multi-modal encode–fuse–decode (PyTorch)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Communications Earth & Environment* 2025, [doi:10.1038/s43247-025-02502-y](https://doi.org/10.1038/s43247-025-02502-y)
|
| 4 |
+
|
| 5 |
+
`FengWu` follows the 2023 preprint architecture (separate encoders per variable group, a cross-modal fuser, decoders, optional
|
| 6 |
+
uncertainty head); `FengWu_lite` is a CPU-friendly default (64 × 128, 69 channels). Architecture notes and status:
|
| 7 |
+
[fengwu_specs.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\fengwu_specs.md), [fengwu_status.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\fengwu_status.md). No official weights are published.
|
| 8 |
+
|
| 9 |
+
```python
|
| 10 |
+
import torch
|
| 11 |
+
from weatherai.models import FengWu_lite
|
| 12 |
+
y = FengWu_lite()(torch.randn(1, 69, 64, 128)) # (1, 69, 64, 128)
|
| 13 |
+
```
|
models/fuxi.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FuXi — cube embedding + Swin-V2 U-Transformer (PyTorch)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *npj Climate and Atmospheric Science* 2023, [doi:10.1038/s41612-023-00512-1](https://doi.org/10.1038/s41612-023-00512-1)
|
| 4 |
+
|
| 5 |
+
`weatherai.models.fuxi` (adapted from [WeatherLearn](https://github.com/lizhuoq/WeatherLearn), MIT): space-time cube embedding of
|
| 6 |
+
two input steps, a Swin-V2 U-Transformer and a fully connected head. Architecture only — the official FuXi weights are
|
| 7 |
+
released as ONNX and are not loaded here. The ensemble and sub-seasonal members of the family are separate, verified ports:
|
| 8 |
+
[FuXi-ENS](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi_ens.md) and [FuXi-S2S](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\fuxi_s2s.md).
|
| 9 |
+
|
| 10 |
+
```python
|
| 11 |
+
from weatherai.models import FuXi
|
| 12 |
+
m = FuXi()
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
Tests: `tests/models/fuxi`.
|
models/fuxi_ens.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FuXi-ENS — native PyTorch (reverse-engineered from the official ONNX; numerically checked vs onnxruntime)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Science Advances* 2025, [doi:10.1126/sciadv.adu2854](https://doi.org/10.1126/sciadv.adu2854)
|
| 4 |
+
|
| 5 |
+
FuXi-ENS (*Science Advances* 2025) is a latent-noise ensemble forecaster. The only official artefact is `fuxi_ens.onnx`
|
| 6 |
+
(Zenodo [10.5281/zenodo.15124541](https://zenodo.org/records/15124541), **CC-BY-NC-4.0 — non-commercial; weights are never committed**:
|
| 7 |
+
download with `python scripts/fetch_fuxi_ens.py --out /workspace/ckpt/fuxi_ens`, 9.9 GB). `weatherai/models/fuxi_ens/model.py` is an
|
| 8 |
+
explicit PyTorch re-implementation whose structure, constants and parameter names were read out of the 26,018-node graph; **the official
|
| 9 |
+
code has no PyTorch definition, so the ONNX graph itself is the oracle**.
|
| 10 |
+
|
| 11 |
+
```
|
| 12 |
+
input [B,2,78,721,1440] (2 steps x (5 vars x 13 levels + 13 surface)) --(x-mean)/std, NaN->0, accumulated channels 73-77 := 0--> xn
|
| 13 |
+
dist_p : PatchEmbed (Conv 8x8 on [state;static const] -> LN) -> dropout "drop0" -> layers.0 (7 AdaLN blocks) -> dropout "drop1"
|
| 14 |
+
-> layers.1 (7 blocks) -> final AdaLN (+patch-embed residual) -> ConvTranspose 8x8 heads: mean, logvar (14 blocks)
|
| 15 |
+
sample = mean + exp(logvar/2) * eps <- the ensemble noise ("RandomNormalLike")
|
| 16 |
+
decoder.0: z = xn + sample (channels 73-77 := 0) -> PatchEmbed -> 6 layers x 7 AdaLN blocks -> final AdaLN (+residual)
|
| 17 |
+
-> pl_head (65 ch) || sf_head (13 ch) -> x*std+mean ; tp = exp(clip(.,0,7)) - 1 (42 blocks)
|
| 18 |
+
output [B,2,78,721,1440] = concat(input[:, -1], new state) (== the ONNX output; inference.py feeds it back as next input)
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
AdaLN block = `adaln = Linear(SiLU(cond))` -> `(shift, scale, gate)` x 2; `x += g1*Attn(LN(x)(1+s1)+b1)`, `x += g2*MLP(LN(x)(1+s2)+b2)`;
|
| 22 |
+
`cond = MLP(sin/cos(step)) + MLP(hour/24) + MLP(doy/365)`. Attention: 24 heads x 64, 18x18 windows over the 90x180 token grid,
|
| 23 |
+
odd blocks cyclic-shifted by 9 with the stored −100 mask, **1-D RoPE on the window-flattened index** (not 2-D), q and k scaled by d^-1/4;
|
| 24 |
+
MLP is a gated GELU (`a*gelu(b)`, `fc1` 1536→8192 no bias); LayerNorm is the *unbiased-variance* form (eps 1e-6). All of this was recovered
|
| 25 |
+
from the graph and then verified numerically (below). 2,474,861,958 parameters + buffers `mean/std [78,1,1]` and `const [6,721,1440]`
|
| 26 |
+
(parameter count equals the ONNX weight elements exactly; the 22 `attn_mask` initialisers are derived by `make_shift_mask` and
|
| 27 |
+
tested equal to the ONNX tensors).
|
| 28 |
+
|
| 29 |
+
```python
|
| 30 |
+
from weatherai.models.fuxi_ens import load_official, FuXiENSNoise, FuXiENS_lite, FuXiENSConfig
|
| 31 |
+
model = load_official("/workspace/ckpt/fuxi_ens/fuxi_ens.onnx") # strict state-dict load, weights memory-mapped (low RAM)
|
| 32 |
+
g = torch.Generator().manual_seed(0)
|
| 33 |
+
noise = FuXiENSNoise.sample(model.cfg, 1, g) # 2 dropout masks + latent normal noise, all explicit
|
| 34 |
+
y = model(x, step, hour, doy, noise) # x: [1,2,78,721,1440] physical units; step=t, hour=h/24, doy=min(365,doy)/365
|
| 35 |
+
y2 = model(x, step, hour, doy, generator=torch.Generator().manual_seed(1)) # another ensemble member
|
| 36 |
+
lite = FuXiENS_lite() # ~2.1 M params, same code, trainable (see tests)
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
**Random ops in the exported graph (handled):** the ONNX has no eval switch — `RandomUniformLike` (×2: dropout p=0.2 on the patch embedding
|
| 40 |
+
`drop0` and on the output of `dist_p.layers.0` `drop1`, keep-prob 0.8, scale 1/0.8) and `RandomNormalLike` (latent noise) are always active, so
|
| 41 |
+
two ONNX runs differ. For a deterministic comparison `scripts/fuxi_ens_fixed_noise_onnx.py` rewrites the 108 MB graph so these three tensors
|
| 42 |
+
are graph *inputs* (no op is changed), and both runtimes receive identical tensors (torch generator seed 1234). `FuXiENSNoise` is the
|
| 43 |
+
corresponding explicit input of the PyTorch model; `FuXiENS.forward` without `noise` draws all three from a `torch.Generator`.
|
| 44 |
+
|
| 45 |
+
**Verified (CPU, fp32; `scripts/fuxi_ens_ort_segments.py` + `scripts/fuxi_ens_verify.py`; official `input.nc` 2018-09-13 18Z/2018-09-14 00Z,
|
| 46 |
+
step=0, fixed noise):** one full forward (all 14 + 42 blocks), strict load of all 498 initialisers. Stage-by-stage vs onnxruntime 1.30 (max |Δ| / max |ref|):
|
| 47 |
+
normalised input 0 (exact), patch embed 5e-7, dist_p layer 0 1.8e-6, dist_p out 1.4e-5, mean/logvar 4.7e-7 / 1.2e-6, sample 1.2e-7, decoder input `z` 6e-8,
|
| 48 |
+
decoder layers 0–3 ≤ 1.5e-6, layer 4–5 1.7e-4–2.3e-4 (a few outlier tokens, rel-RMSE 1e-5), decoder out 8e-5. **Final output** (physical units, all 78 channels × 2 steps):
|
| 49 |
+
max |Δ| = 1.66 on |values| up to 2.06e5 (geopotential; max |Δ|/max|ref| = 8.1e-6); per-channel max |Δ| / channel-std ≤ 3.3e-3 (precipitation `tp`, whose
|
| 50 |
+
`exp(clip)-1` amplifies logit errors) and ≤ 1.8e-3 for every other channel; relative RMSE ≤ 1.1e-5 for every channel; NaN (SST land) positions identical; the pass-through
|
| 51 |
+
step is bit-identical. That is fp32-rounding level for a 2.5 B-parameter network, **not** bit-exactness.
|
| 52 |
+
|
| 53 |
+
**HF GPU (ZeroGPU Space, NVIDIA RTX PRO 6000 Blackwell MIG 2g.48gb, torch 2.13+cu130; `scripts/gpu_smoke.py fuxi_ens`): PASS.** The 9.9 GB weights were fetched *on the Space*
|
| 54 |
+
from Zenodo with `scripts/fetch_fuxi_ens.py` (parallel HTTP range requests + local inflate: ≈ 430–480 s versus ≈ 3 MB/s ≈ 55 min for a single stream; `/tmp` has ≈ 3 TB free) and strict-loaded into CPU RAM
|
| 55 |
+
(0.8 s, memory-mapped); then, inside the 120 s ZeroGPU window, moved to the GPU in **fp32 (no reduced precision was needed)**: 3 s, 11.5 GB resident, 16.0 GB peak, one forward 2.5 s.
|
| 56 |
+
Same official `input.nc` and the same fixed noise (torch CPU generator, seed 1234) as the CPU run; compared on the committed stride-12 slice of the onnxruntime output (all 78 channels × both steps):
|
| 57 |
+
max |Δ| = 0.078 (max |Δ|/channel-std = 5.9e-4, tolerance 1e-2·std); step-0 pass-through equal; a second member with different noise differs. TF32 disabled. The Space also passes the 10 synthetic unit tests (2 file-dependent tests skipped there). Only this single forward step was run on the GPU (no 15-day rollout, no bf16/fp16).
|
| 58 |
+
Caveat: the Space weights are preloaded in a background thread (`fetch_fuxi_ens` endpoint) because a redeploy wipes `/tmp` and downloading does not fit into one 120 s GPU call.
|
models/fuxi_s2s.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FuXi-S2S — native PyTorch from the ONNX, onnxruntime parity (weights CC-BY-NC-ND, never committed)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature Communications* 2024, [doi:10.1038/s41467-024-50714-1](https://doi.org/10.1038/s41467-024-50714-1)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/fuxi_s2s` re-implements FuXi-S2S (*Nature Communications* 2024), reverse-engineered from the released ONNX graph (no PyTorch source is published): input two 76-channel 121×240 frames + step index, 2×2 patch embedding (1536), step-embedding MLP, Swin-V2 U-Transformer (2 stages × 6 blocks, 10×10 windows, cosine attention, continuous relative-position bias, gated-GELU MLP), a variational latent head (`mean`, log-variance, low-rank `cov`) sampled with two noise tensors, a 2-stage 12-block decoder and a pixel-shuffle head; precipitation is log-transformed, and the output is fed back autoregressively (1.039 B parameters). Weights: Zenodo 15718402 `model-1.0.tar` (2.08 GB, **CC-BY-NC-ND-4.0**): downloaded by the user (`download()` / `scripts/fetch_weights.py fuxi_s2s`), **never committed or redistributed**; only scalar fingerprints (`tests/models/fuxi_s2s/data/cpu_summary.json`) are stored.
|
| 6 |
+
|
| 7 |
+
```python
|
| 8 |
+
from weatherai.models.fuxi_s2s import make_input, download_sample
|
| 9 |
+
from weatherai.models.fuxi_s2s.convert import download, load_official
|
| 10 |
+
m = load_official(download("/workspace/ckpt/fuxi_s2s")) # download() returns the .onnx path; strict load, fp32
|
| 11 |
+
download_sample("/workspace/ckpt/fuxi_s2s/data") # raw ERA5 sample inputs from the authors' GitHub
|
| 12 |
+
x, dates = make_input("/workspace/ckpt/fuxi_s2s/data") # (2, 76, 121, 240)
|
| 13 |
+
y = m(torch.from_numpy(x)[None], torch.zeros(1)) # step 0: (1, 2, 76, 121, 240) = [last input, forecast]
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
* **Verified (CPU, fp32 native vs onnxruntime fp16-graph, official sample 2020-06-01/02, identical injected noise; `scripts/fuxi_s2s_compare.py`, `docs/results/fuxi_s2s_parity.json`):** strict load of all 576 initialisers; intermediate taps rel. L2 2e-4 to 1e-3 (embedding 4.8e-4, encoder 9.9e-4, latent `z` 3.3e-5, decoder 6.2e-4); step-0 output rel. L2 6.3e-6 (worst channel 8.2e-4), NaN mask identical; 3-step autoregressive chain rel. L2 6.3e-6 / 1.1e-5 / 1.4e-5 (worst channel ≤1.2e-3). The residual is fp16-vs-fp32 rounding, not an architecture difference; 5 unit tests.
|
| 17 |
+
* **Findings:** the released `dist_p.cov` weight is exactly zero, so ensemble spread comes only from the diagonal noise (`eps2`); the shifted-window mask covers latitude only (longitude periodic), which differs from a standard Swin mask in 6 windows; the data.zip `input.nc` shipped with the repo is already normalised and unusable with this ONNX (it normalises internally), so inputs are rebuilt from the raw `data/sample` ERA5 files.
|
| 18 |
+
* **HF ZeroGPU (2026-10-02):** **PASS** — strict load 1.2 s, one fp32 step 0.67 s, peak 5.4 GB, probe statistics vs CPU within 5.6e-6 (of std)
|
| 19 |
+
* **NOT verified / not done:** forecast skill or S2S metrics, the 42-step / 11-member production runs, training (lite config is trainable, gradients tested), bit-level emulation of the ORT fp16 kernels.
|
models/gencast.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GenCast — native PyTorch denoiser (official architecture, official Mini weights load) + EDM/DPM-Solver++ sampler
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature* 2025, [doi:10.1038/s41586-024-08252-9](https://doi.org/10.1038/s41586-024-08252-9)
|
| 4 |
+
|
| 5 |
+
GenCast (*Nature* 2025) is a conditional EDM diffusion model whose denoiser is a GraphCast-style
|
| 6 |
+
grid→mesh→grid network with a 16-layer k-hop mesh transformer; official code is JAX (`google-deepmind/weathernext`).
|
| 7 |
+
`weatherai.models.gencast` contains explicit `nn.Module`s of the **official denoiser** (parameter names mirror the Haiku names)
|
| 8 |
+
plus the EDM wrapper / DPM-Solver++ 2S sampler. No wrapper around JAX is involved at runtime.
|
| 9 |
+
|
| 10 |
+
| file | contents |
|
| 11 |
+
|---|---|
|
| 12 |
+
| `denoiser.py` | `FourierFeaturesMLP` (noise-level encoder), `LinearNormConditioning`, `ConditionedMLP` (Linear→swish→Linear→LN→cond-affine), `Grid2MeshGNN`, `MeshTransformer` (+ `MeshAttention`: dense masked or banded tri-block-diagonal k-hop attention), `Mesh2GridGNN`, `GenCastDenoiser`, `convert_official_params`, `GenCastDenoiser_from_official`, channel stacking helpers |
|
| 13 |
+
| `graphs.py` | grid2mesh radius graph, finest-mesh banded (reverse Cuthill-McKee) permutation, mesh2grid containing-triangle graph, k-hop mask (re-uses `graphcast.mesh`) |
|
| 14 |
+
| `gencast.py` | EDM preconditioning/loss, noise schedule, DPM-Solver++ 2S + churn sampler, `DenoiserNet` (config-driven adapter), `GenCast`, `GenCast_lite` |
|
| 15 |
+
|
| 16 |
+
```python
|
| 17 |
+
import numpy as np, torch
|
| 18 |
+
from weatherai.models.gencast import (GenCast_lite, GenCastDenoiser_from_official, denoiser_inputs, unstack_variables)
|
| 19 |
+
|
| 20 |
+
# (a) small config-driven, trainable instance of the SAME native architecture (random init)
|
| 21 |
+
m = GenCast_lite(img_size=(16, 32), out_channels=4, cond_channels=8, latent=32, transformer_layers=2, mesh_level=1)
|
| 22 |
+
cond, target = torch.randn(2, 8, 16, 32), torch.randn(2, 4, 16, 32)
|
| 23 |
+
loss = m.loss(target, cond); loss.backward() # EDM-weighted denoising loss (training step)
|
| 24 |
+
members = m.eval().sample(cond, num_members=4) # (4, 2, 4, 16, 32) DPM-Solver++ 2S + churn
|
| 25 |
+
|
| 26 |
+
# (b) official GenCast-1p0deg-Mini weights (public GCS bucket, 230 MB) in the native denoiser
|
| 27 |
+
# gs://dm_graphcast/gencast/params/GenCast 1p0deg Mini <2019.npz
|
| 28 |
+
lat, lon = np.linspace(-90, 90, 181), np.arange(360) # official 1° grid
|
| 29 |
+
net = GenCastDenoiser_from_official("mini.npz", lat, lon) # strict load; mesh_size=4, 16 layers, 57.5 M params
|
| 30 |
+
x = denoiser_inputs(inputs, forcings, noisy_targets, (181, 360)) # dict-of-arrays -> (B, 181*360, 264), official channel order
|
| 31 |
+
raw = net(x, noise_levels) # (B, Ng, 84) raw network output
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
**Status — verified (CPU, fp32):**
|
| 35 |
+
* **Denoiser vs official JAX with the official Mini weights** (reference from the official `weathernext1_gen.denoiser.Denoiser`,
|
| 36 |
+
`scripts/gencast_denoiser_reference.py`, random inputs, official plain-`mha` attention because the TPU splash kernel is unavailable):
|
| 37 |
+
- *trained configuration* (mesh_size=4 / 2562 mesh nodes, `attention_k_hop=16`, 181×360 grid, 102 k grid→mesh edges, batch 1): max |Δ| = 4.8e-5 on all 84 outputs (output magnitude up to 22; max rel. to output max 3.2e-6);
|
| 38 |
+
- small configuration (mesh_size=2, k_hop=3, 13×24, batch 2, two noise levels): max |Δ| = 7.4e-5; banded tri-block-diagonal attention gives the same (6.9e-5).
|
| 39 |
+
- graph construction (permuted mesh, grid2mesh/mesh2grid indices and edge features) equals the official graphs (indices exactly, features ≤3e-8).
|
| 40 |
+
- all all official parameter tensors (57,492,612 weights) strict-load; unit tests use atol 2e-4 / rtol 1e-4 (small) and atol 5e-4 (full).
|
| 41 |
+
* EDM coefficients, noise/churn schedules and the DPM-Solver++ 2S sampler (± churn) match the official JAX sampler (rtol 2e-4, closed-form toy denoiser, fixed noise).
|
| 42 |
+
* Training step example/test: loss decreases on a fixed batch, all parameters receive gradients (`tests/models/gencast`).
|
| 43 |
+
|
| 44 |
+
**Not verified / not ported:** the *network* only was compared against the official network — **not** the full model (no end-to-end official
|
| 45 |
+
ensemble forecast comparison; the official `InputsAndResiduals` normalisation, NaN-cleaning of SST and the autoregressive rollout are not ported); the sampling noise
|
| 46 |
+
is iid Gaussian on the grid, official is spherical-harmonic white noise (needs `dinosaur`); the 0.25° and Operational checkpoints were not run
|
| 47 |
+
(same code path, larger graphs); the lite config is not a trained model. Details: [docs/model_status.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md).
|
models/genfocal.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GenFocal — climate debiasing and generative regional downscaling
|
| 2 |
+
|
| 3 |
+
## Update: two-stage interface, 19 October 2026
|
| 4 |
+
|
| 5 |
+
`from weatherai import GenFocal, GenFocal_lite` now exposes a composition of
|
| 6 |
+
the ReFlow debiasing network and a conditional EDM super-resolution network.
|
| 7 |
+
The native SR adds noise-level preconditioning, weighted denoising loss and
|
| 8 |
+
a single-segment Heun sampler. Both native U-ViTs remain approximations;
|
| 9 |
+
they are **not compatible with official Orbax checkpoints**. Default lite
|
| 10 |
+
weights are random. This is an architectural/testing implementation, not a
|
| 11 |
+
trained regional climate product.
|
| 12 |
+
|
| 13 |
+
```python
|
| 14 |
+
from weatherai import GenFocal_lite
|
| 15 |
+
model = GenFocal_lite(channels=4).eval()
|
| 16 |
+
# x, cond: normalized B,T,lon,lat,C tensors, with channel:mean/channel:std.
|
| 17 |
+
result = model.downscale(x, cond, output_size=(8, 32, 16),
|
| 18 |
+
flow_steps=8, diffusion_steps=16)
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
The native interpolation uses bilinear space and replicated time; official
|
| 22 |
+
daily/hourly preprocessing, positional embeddings, filtered resizes, local
|
| 23 |
+
regional padding, classifier-free guidance, multi-device overlap consensus
|
| 24 |
+
and full official network weight parity are not claimed. Physical output
|
| 25 |
+
requires an explicit physical baseline plus residual climatology mean/std.
|
| 26 |
+
|
| 27 |
+
`GenFocalOfficial` in `weatherai.models.genfocal` builds the **actual upstream**
|
| 28 |
+
ReFlow or EDM U-ViT from official JSON/YAML configs and optionally restores
|
| 29 |
+
Orbax state/EMA. It is lazy and separate from the lite PyTorch API. Inspected
|
| 30 |
+
upstream: `google-research/swirl-dynamics@0beb5d20a5b31969e12069ba7d86fc0537e08528`.
|
| 31 |
+
The wrapper follows upstream construction/restoration code, but full checkpoint
|
| 32 |
+
inference has not been executed here. Use upstream's recommended Linux/Python
|
| 33 |
+
3.12 environment; the existing Space uses Python 3.10, below upstream's >=3.11.
|
| 34 |
+
|
| 35 |
+
The previous archive-size obstacle has an alternative: the public GCS prefix
|
| 36 |
+
`gs://genfocal/super_resolution/checkpoints/conus_7d_t2m_w10m_q1000_msl/`
|
| 37 |
+
contains 12 actual files, **2.022 GiB** (2.171 GB), excluding directory markers.
|
| 38 |
+
`python scripts/fetch_genfocal.py` inventories it; `--download --out PATH`
|
| 39 |
+
downloads that one subtree with size/checksum checks. The verified manifest is
|
| 40 |
+
[recorded here](https://github.com/GISWLH/WeatherAI/blob/main/docs\results\genfocal_conus_checkpoint_manifest.json). No old
|
| 41 |
+
content was deleted and no weights have been loaded in the native models.
|
| 42 |
+
|
| 43 |
+
The complete public archives remain [Zenodo 21285802](https://zenodo.org/records/21285802):
|
| 44 |
+
8,737,960,532-byte SR and 39,143,363,979-byte debiasing tarballs (CC-BY-4.0).
|
| 45 |
+
[Code archive](https://zenodo.org/records/21268077), Apache-2.0.
|
| 46 |
+
See [three-paper integration and test scope](https://github.com/GISWLH/WeatherAI/blob/main/docs\research_20261019.md).
|
| 47 |
+
|
| 48 |
+
## Historical flow-core verification (2 October 2026)
|
| 49 |
+
|
| 50 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature Machine Intelligence* 2026, [doi:10.1038/s42256-026-01308-7](https://doi.org/10.1038/s42256-026-01308-7)
|
| 51 |
+
|
| 52 |
+
`weatherai/models/genfocal` ports the debiasing-stage maths of GenFocal (*Nature Machine Intelligence* 2026): conditional rectified-flow (ReFlow) loss `mean(w·((x₁−x₀)−v(x_t,t,cond))²)` with `x_t = t·x₁ + (1−t)·x₀`, the RK4 sampler (forward and reversed flow) with output de-normalisation, plus a **lite** trainable 3-D U-ViT flow network (`GenFocalNet`: FiLM residual blocks, axial spatial/temporal attention with QK-norm, periodic longitude, channel-mean/std conditioning, zero-init output). The network is a structural stand-in: it is **not** weight-compatible with the official Flax `UNet3d`. The super-resolution stage (EDM denoiser) is not ported. Code: GitHub google-research/swirl-dynamics (Apache-2.0); checkpoints Zenodo 21285802 (CC-BY-4.0), not downloaded.
|
| 53 |
+
|
| 54 |
+
```python
|
| 55 |
+
from weatherai.models.genfocal import GenFocalNet, GenFocalNetConfig, reflow_loss, sample_flow
|
| 56 |
+
net = GenFocalNet(GenFocalNetConfig.lite(4)) # inputs (batch, time, lon, lat, channels)
|
| 57 |
+
loss = reflow_loss(net, x0, x1, {"channel:mean": cm, "channel:std": cs}) # x0 = climate model, x1 = ERA5 (normalised)
|
| 58 |
+
y = sample_flow(net, x0, {"channel:mean": cm, "channel:std": cs}, num_steps=8) # RK4, official schedule
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
* **Verified (CPU, fp64 native vs fp32 official; oracle = unchanged `ConditionalReFlowModel.loss_fn` and `inference_utils.sampling_from_batch` on a tiny Flax network, stored reference; `scripts/genfocal_make_ref.py`, `scripts/genfocal_parity.py`, `docs/results/genfocal_parity.json`):** loss rel. error ≤5.8e-7 (uniform / logistic time draw, with and without weighted norm); 4- and 8-step RK4 samples, forward and reversed, rel. L2 ≤7.5e-8 (max abs ≤1.3e-6); 6 unit tests (also periodic-longitude equivariance and a training-loss decrease of the lite net).
|
| 62 |
+
* **Official-code quirks reproduced:** `tspan = arange(0, 1, 1/N)` has N points, so the sampler takes N−1 steps and returns the state at t = 1−1/N, not t = 1 (`to_end=True` integrates to 1); `lognormal_sampler` is `logistic(std·u + mean)`, not log-normal.
|
| 63 |
+
* **HF ZeroGPU (2026-10-02), random weights:** **PASS** — lite net (3.3 M params, 8×64×32×10, 64-128-128 ch) train step + backward 0.23 s, 8-step RK4 sample 0.26 s, peak 1.0 GB; lite output vs CPU max abs 2.5e-3 (not isolated; default cuDNN conv precision suspected).
|
| 64 |
+
* **Blocker for the real model:** Zenodo 21285802 holds `super_resolution_checkpoints.tar.gz` (8.74 GB) and `debiasing_checkpoints.tar.gz` (39.1 GB), Orbax/OCDBT (tensorstore) checkpoints of the Flax `UNet3d` / `UNet` (e.g. `conus_7d_t2m_w10m_q1000_msl/300000`, 491 MB blobs); the debiasing config is a 6-level 128-channel 3-D U-ViT (`resize_to_shape` (256, 128), 8 snapshots, 10 channels, 128 heads). Not done: the 39 GB archive exceeds the box disk (~25 GB free) and the Space's storage; porting the official `UNet3d` / `FilteredResize` / `InterpConvMerge` / axial attention to read those weights; ERA5/LENS2 inputs (Google Cloud buckets). No real-data debiasing or downscaling was run.
|
| 65 |
+
* **NOT verified / not done:** the official network (no weight load, no parity), the super-resolution stage, real-data skill, the evaluation scripts.
|
models/graphcast.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GraphCast — grid ↔ icosahedral-mesh GNN (PyTorch)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Science* 2023, [doi:10.1126/science.adi2336](https://doi.org/10.1126/science.adi2336)
|
| 4 |
+
|
| 5 |
+
Independent re-implementation of the encode (Grid2Mesh) – process (multi-mesh message passing) – decode (Mesh2Grid) design.
|
| 6 |
+
Stage-wise parity against DeepMind's JAX code and NVIDIA PhysicsNeMo at mesh levels 0–1 (graphs, Grid2Mesh, processor
|
| 7 |
+
N ≤ 16, Mesh2Grid): [graphcast_parity.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\graphcast_parity.md). Architecture notes: [graphcast_specs.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\graphcast_specs.md).
|
| 8 |
+
No official checkpoint is loaded and there is no whole-model parity claim.
|
| 9 |
+
|
| 10 |
+
```python
|
| 11 |
+
import torch
|
| 12 |
+
from weatherai.models import GraphCast_lite
|
| 13 |
+
y = GraphCast_lite()(torch.randn(1, 4, 32, 64)) # tiny mesh (level 1), residual next step
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
Training smoke example: `examples/graphcast/train_smoke.py`.
|
models/neuralgcm.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NeuralGCM — native learned components + torch spherical harmonics (JAX wrapper kept as oracle)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature* 2024, [doi:10.1038/s41586-024-07744-y](https://doi.org/10.1038/s41586-024-07744-y)
|
| 4 |
+
|
| 5 |
+
NeuralGCM (*Nature* 2024) = a differentiable spectral dynamical core (`dinosaur`: moist primitive equations on
|
| 6 |
+
sigma levels, IMEX-RK stepping) + learned Haiku components. **This is only partly ported:**
|
| 7 |
+
|
| 8 |
+
| ported natively (`weatherai/models/neuralgcm/network.py`, `spectral.py`) | not ported (exact stuck points: docs/model_status.md) |
|
| 9 |
+
|---|---|
|
| 10 |
+
| `EpdTower` / `ColumnMLP` (gelu-MLP encode → 5 residual process blocks → decode; the decoder, 2 encoders and the physics tendency tower) | the dinosaur dycore: sigma-coordinate primitive equations, `imex_rk_sil3` stepper, filters, `DycoreWithPhysicsCorrector` |
|
| 11 |
+
| `SurfaceEmbedding` (land/sea/sea-ice towers + mask/ice blend), `VerticalConvTower` (5 × Conv1D over levels) | tower *input feature builders* (dimensional velocity/prognostic/pressure/radiation/latitude/memory features, normalisation, input clipping/filters) and output transforms |
|
| 12 |
+
| `LearnedPositionalFeatures`, `LearnedOrography` | the base orography dataset (shipped in the checkpoint's aux data), forcing interpolation, `encode/advance/decode/unroll` plumbing |
|
| 13 |
+
| `SphericalHarmonicsGrid`: real SH basis, `to_modal/to_nodal`, ∇², ∂λ, cosφ∂φ, div/curl/grad, u,v ↔ vorticity/divergence | stochastic checkpoints (noise/`stochastic.py`), other resolutions |
|
| 14 |
+
|
| 15 |
+
```python
|
| 16 |
+
import pickle, torch
|
| 17 |
+
from weatherai.models.neuralgcm import NeuralGCMLearnedComponents, convert_official_params, SphericalHarmonicsGrid, SpectralGridConfig
|
| 18 |
+
|
| 19 |
+
params = pickle.load(open("deterministic_2_8_deg.pkl", "rb"))["params"] # official checkpoint (trusted file; needs jax to unpickle)
|
| 20 |
+
net = convert_official_params(params) # strict: raises if any of the 121 Haiku modules / 14,518,180 weights is unused
|
| 21 |
+
y = net.physics(torch.randn(2365, 128, 64)) # (192, 128, 64) physics-tendency tower on packed nodal features (C, lon, lat)
|
| 22 |
+
g = SphericalHarmonicsGrid(SpectralGridConfig.TL63()) # 2.8° Gaussian grid, modal shape (127, 65)
|
| 23 |
+
vor, div = g.uv_nodal_to_vor_div_modal(u, v) # (..., lon, lat) -> (..., m, l)
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
Everything is differentiable and trainable (`tests/.../test_neuralgcm_native.py::TestTrainableNative`: tower training step; gradients
|
| 27 |
+
through the SH layer). The official JAX wrapper (`NeuralGCM_lite()`, `NeuralGCMWrapper.forecast`) is unchanged and is the only
|
| 28 |
+
path that produces forecasts.
|
| 29 |
+
|
| 30 |
+
**Status — verified (numerically vs the official `neuralgcm` 1.2.3 / `dinosaur` 1.5.0, float32, official 2.8° deterministic checkpoint):**
|
| 31 |
+
all 121 Haiku modules / 14,518,180 weights strict-load; real packed tower inputs from one official encode→advance→decode step of the
|
| 32 |
+
demo ERA5 snapshot (24 random columns), max |native − official|: decoder 1.5e-4 (|y|≤206), encoder 1.3e-3 (|y|≤699), encoder_1 1.1e-4,
|
| 33 |
+
physics 1.3e-5 (|y|≤20), surface towers ≤5.8e-6, surface blend ≤1e-5, vertical CNN 2.2e-7 (|y|≤0.11), learned orography 0.0;
|
| 34 |
+
SH layer on random fields: ≤3.1e-5 for transforms, vorticity/divergence and the u,v round trip (|values| up to ~100), exact (0.0) for ∇²,
|
| 35 |
+
∂λ; float64 basis orthonormal to 1e-10. **Not verified / not claimed:** any forecast produced by native code (there is none),
|
| 36 |
+
whole-model parity, the dycore, feature builders, other checkpoints (1.4°/0.7°/stochastic). (JAX-on-GPU and the JAX training workflow: see the next subsection.) Details and results:
|
| 37 |
+
[docs/model_status.md](https://github.com/GISWLH/WeatherAI/blob/main/docs\model_status.md).
|
| 38 |
+
|
| 39 |
+
## NeuralGCM — training / modification workflow (JAX, official `neuralgcm` + `dinosaur` + Haiku; `weatherai/models/neuralgcm/train.py`)
|
| 40 |
+
|
| 41 |
+
JAX is allowed for NeuralGCM, so the model that can actually be **trained and modified** is the *official* one: the dycore is
|
| 42 |
+
official code, the learned parts are re-configured through the official gin config and trained with `optax`.
|
| 43 |
+
(The torch towers / SH layer above are kept for inspection and porting; they cannot forecast.)
|
| 44 |
+
|
| 45 |
+
**What you can modify** (all verified to change the Haiku parameter tree and train; `CONFIG_KEYS` lists them):
|
| 46 |
+
|
| 47 |
+
| knob | where | effect |
|
| 48 |
+
|---|---|---|
|
| 49 |
+
| `LATENT_SIZE`, `LAYER_SIZE` | `build_model(ck, {...})` | width of the decoder / 2 encoder / physics EPD towers (default 384 / 384) |
|
| 50 |
+
| `NUM_BLOCKS`, `process/MlpUniform.num_hidden_layers` | same | depth: residual process blocks (5) and layers per block (3) |
|
| 51 |
+
| `N_CNN_FEATURES`, `POSITIONAL_LATENT_SIZE` | same | vertical 1-D CNN width (32), learned positional-feature channels (8) |
|
| 52 |
+
| `SURFACE_MODEL_LATENT_SIZE/LAYER_SIZE/OUTPUT_SIZE` | same | land / sea / sea-ice surface towers (8) |
|
| 53 |
+
| `N_INNER_DYCORE_STEPS` | same | dycore substeps per physics call (5) |
|
| 54 |
+
| any other gin binding | `extra_bindings=["name = value"]` | e.g. other tower options in the shipped config |
|
| 55 |
+
| which parameters are trained | `fit(..., freeze=[regex,…])` | e.g. freeze everything except `physics` / the decoder |
|
| 56 |
+
| initialisation | `params="official" \| "init" \| "transfer"` | keep the checkpoint; random Haiku init (from scratch); random init + copy every official array whose path *and* shape still match |
|
| 57 |
+
| loss | `rollout_loss(..., steps, variables, lat_weights, level_w)` | `steps=0` encode→decode reconstruction, `steps≥1` differentiable `unroll` through the dycore |
|
| 58 |
+
|
| 59 |
+
**Not modified / not claimed:** the dynamical core equations, the feature builders and the data pipeline are the official code (not
|
| 60 |
+
re-implemented). No paper-scale training (multi-year ERA5, 1–4 day rollouts, spectral losses, multi-stage schedule, TPUs) was run, and
|
| 61 |
+
nothing here claims forecast skill from the demos below.
|
| 62 |
+
|
| 63 |
+
```python
|
| 64 |
+
import pickle, numpy as np
|
| 65 |
+
from weatherai.models.neuralgcm import train as T
|
| 66 |
+
from weatherai.models.neuralgcm.neuralgcm import download_checkpoint
|
| 67 |
+
ck = pickle.load(open(download_checkpoint("deterministic_2_8_deg"), "rb")) # trusted official file
|
| 68 |
+
|
| 69 |
+
# (a) from scratch, smaller towers (874,020 params instead of 14,518,180)
|
| 70 |
+
m, rep = T.build_model(ck, {"LATENT_SIZE": 64, "LAYER_SIZE": 64, "NUM_BLOCKS": 2}, params="init", seed=0)
|
| 71 |
+
x, forc = T.demo_snapshot(m) # bundled ERA5 snapshot
|
| 72 |
+
tg = {k: v[None] for k, v in x.items() if k != "sim_time"} # reconstruction target
|
| 73 |
+
m, hist = T.fit(m, x, forc, tg, steps=0, n_iters=8, lr=3e-3) # hist["loss"], hist["grad_norm"]
|
| 74 |
+
|
| 75 |
+
# (b) fine-tune the official checkpoint on real ERA5 (public ARCO-ERA5 on GCS, regridded; needs gcsfs)
|
| 76 |
+
off, _ = T.build_model(ck, params="official")
|
| 77 |
+
ds = T.fetch_era5_window(off, "2020-01-01T00", steps=1, hours=1) # cached .nc
|
| 78 |
+
x, forc = T.demo_snapshot(off) # or build inputs from ds[0]; see tests / scripts for the exact call
|
| 79 |
+
off2, hist = T.fit(off, x, forc, T.stack_targets(off, ds, 1), steps=1, n_iters=6, lr=3e-5, freeze=[r"^(?!.*physics)"])
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
**Training recipe used for the demos:** `optax.chain(clip_by_global_norm(1.0), adam(lr))`; loss = mean squared error of the decoded
|
| 83 |
+
state vs the target, each variable normalised by one std (per-level scales blow up at the model top), cos(lat) weights, levels above
|
| 84 |
+
50 hPa masked (the encoder→decoder reconstruction error dominates there); `steps=0` for reconstruction, `steps=1` (1 h) through the
|
| 85 |
+
dycore for fine-tuning; learning rate 3e-3 from scratch, 3e-5 for fine-tuning.
|
| 86 |
+
|
| 87 |
+
**Verified** (`tests/models/neuralgcm/test_neuralgcm_train.py`, 7 tests, JAX 0.10.2 CPU on the box, 176 s):
|
| 88 |
+
config overrides change the architecture (874,020 vs 14,518,180 params; `process_tower_1` exists, `_2` does not); `transfer` copies all
|
| 89 |
+
14,518,180 official weights when only unrelated knobs change; from-scratch reconstruction training decreases the loss
|
| 90 |
+
(1.288 → 0.929 in 8 Adam steps); gradients through a dycore rollout reach the encoder, physics tower and decoder (finite, 129/129
|
| 91 |
+
parameter leaves non-zero); training with the physics tower frozen leaves it bit-identical; fine-tuning the *official* model on
|
| 92 |
+
real ERA5 (2020-01-01 00Z → +1 h) lowers the loss 0.002582 → 0.002217 after 6 steps (held-out 2020-07-01 12Z: 0.004205 → 0.004002; `scripts/neuralgcm_finetune_era5.py`, 331 s for 6 steps on 8 CPU
|
| 93 |
+
cores, peak RSS ~7 GB). **These are mechanics checks, not skill:** at +1 h the official model's error vs ERA5 is dominated by the
|
| 94 |
+
encoder→decoder reconstruction error near the model top and is *larger* than persistence at many levels; fine-tuning for 6 steps on one
|
| 95 |
+
snapshot is overfitting to that snapshot, and no improvement in forecast skill is claimed.
|
| 96 |
+
|
| 97 |
+
**HF GPU (ZeroGPU RTX PRO 6000 Blackwell MIG 2g.48gb):** `neuralgcm_train` smoke **PASS with JAX actually on the GPU**
|
| 98 |
+
(`jax_backend: gpu`, `CudaDevice`, params live on the device; 4 reconstruction steps 0.0030 → 0.0027, rollout gradient finite, 129/129 leaves non-zero).
|
| 99 |
+
What it took: the Space runs Python 3.10, so JAX is capped at 0.6.2, which has only the `jax[cuda12]` extra (not `cuda13`) — with plain `jax[cpu]`
|
| 100 |
+
(the earlier setting) or an unmatched extra JAX silently falls back to CPU; and on the Blackwell slice XLA's GEMM autotuner aborts the process on the
|
| 101 |
+
dycore's `HIGHEST`-precision matmuls (`Algorithm not supported by the ElementalIrEmitter: ALG_DOT_BF16_BF16_F32`), fixed with
|
| 102 |
+
`XLA_FLAGS=--xla_gpu_autotune_level=0` (set in `hf_space/app.py` before `import jax`). ZeroGPU exposes the GPU only inside `@spaces.GPU`
|
| 103 |
+
calls (120 s limit), so only the small config was timed there; the full-size fine-tune was run on CPU only.
|
models/neuralgcm_precip.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NeuralGCM precipitation — official checkpoints via official code, trainable loss
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Science Advances* 2026, [doi:10.1126/sciadv.adv6891](https://doi.org/10.1126/sciadv.adv6891)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/neuralgcm/precip.py` wraps the two checkpoints of the *Science Advances* 2026 paper: `stochastic_precip_2_8_deg.pkl` (**predicts** precipitation; evaporation is diagnosed from the column water budget) and `stochastic_evap_2_8_deg.pkl` (predicts evaporation; precipitation is **diagnosed** from the budget). Zenodo [17109230](https://zenodo.org/records/17109230), CC-BY-4.0, downloaded to `/workspace/ckpt/ngcm_precip` and never committed. Both are stochastic models at 2.8° (128×64, 32 levels) with 1 h steps.
|
| 6 |
+
|
| 7 |
+
```python
|
| 8 |
+
from weatherai.models.neuralgcm import precip as P
|
| 9 |
+
import neuralgcm
|
| 10 |
+
paths = P.download("/workspace/ckpt/ngcm_precip") # two 45 MB pickles
|
| 11 |
+
m = P.build(P.load_checkpoint(paths["precip"])) # pickle: trusted official file
|
| 12 |
+
ds = neuralgcm.demo.load_data(m.data_coords)
|
| 13 |
+
pred = P.rollout(m, ds, steps=24, hours=1, seed=0) # unchanged official stochastic rollout
|
| 14 |
+
rate = P.precip_rate_mm_per_hour(pred) # mm/h from the cumulative diagnostic
|
| 15 |
+
params, hist = P.fit_precip(m, inp, forcing, target_mm_per_h, n_updates=100, steps=6, lr=3e-4) # differentiable loss + optax
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
* **Diagnostics:** `precipitation_cumulative_mean` (m of water since encode) and `evaporation` (kg m⁻² s⁻¹, negative = evaporation); the rate helper differences the cumulative field.
|
| 19 |
+
* **Verified (CPU):** both pickles load and run through the unchanged official code; parameter counts 11,145,090 (precip, 293 tensors) and 11,080,210 (evap, 230 tensors); different seeds give different members; precipitation of the precip model is non-negative and monotone; the precipitation loss is differentiable through the dycore (gradient is non-zero on the parameters upstream of the precipitation diagnostic, 38 % of the leaves; the rest correctly receive zero); a short fine-tune on a constant target reduces the loss (`tests/models/neuralgcm/test_neuralgcm_precip.py`, 6 tests).
|
| 20 |
+
* **Real case (CPU, one case):** ERA5 from ARCO at 2.8°, init 2020-01-01T00Z, 24 hourly steps, 4 members, vs ERA5 `total_precipitation` 24 h accumulation (ERA5 global mean 2.97 mm). *precip model:* member global means 2.50–2.82 mm, member RMSE 3.82–4.04 mm, pattern correlation 0.82–0.83, ensemble-mean RMSE 3.25 mm / correlation 0.868, spread 2.15 mm, no negative rates. *evap model:* global means 2.64–2.80 mm, member RMSE 4.01–4.07 mm, ensemble-mean RMSE 3.33 mm / correlation 0.870; **its diagnosed precipitation is negative in 38 % of cell-hours (minimum −1.24 mm/h)** — the budget-diagnosed field is not constrained to be positive. A constant-global-mean baseline has RMSE 6.29 mm (`docs/results/neuralgcm_precip_eval.json`, `scripts/neuralgcm_precip_eval.py`).
|
| 21 |
+
* **HF ZeroGPU (JAX 0.6.2 CUDA backend, RTX PRO 6000 Blackwell MIG 2g.48gb, 2026-10-02):** `ngcm_precip` rollout **PASS** (6-step global-mean cumulative precipitation 0.5612 mm vs CPU 0.5620 mm; 58 s incl. XLA compile), `ngcm_evap` rollout **PASS** (0.7586 vs 0.7594 mm; 62 s incl. compile), `ngcm_precip_train` **PASS** (3 optax updates through the loss: 0.127 → 0.104 → 0.069, gradients finite; 164 s incl. compile — the training call is longer than the 120 s nominal ZeroGPU allotment but completed). The first, combined rollout+training smoke aborted ("GPU task aborted") and was therefore split in two. JAX peak GPU memory was not measured.
|
| 22 |
+
* **NOT done / not verified:** a **native-PyTorch port of these checkpoints** — the existing converter for the deterministic checkpoint does not strict-load them (no memory encoder; decoder encode tower 784→1040 inputs, physics decode 192→193 outputs, physics encode 2365→2118 inputs, extra parameters), so tower-level torch parity was not attempted; the paper's skill claims, IMERG/GPCP comparison, multi-day / multi-case statistics, extremes and long-horizon behaviour, multi-year training of the precipitation loss (only mechanics were tested), the fit loop on real multi-step targets. The sqrt-MSE precipitation loss is our choice, not the paper's training recipe.
|
models/orca_dl.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ORCA-DL — native PyTorch Swin ocean model (official weights load; matches official code)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Science Advances* 2025, [doi:10.1126/sciadv.adu2488](https://doi.org/10.1126/sciadv.adu2488)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/orca_dl` re-implements ORCA-DL (*Science Advances* 2025) without `transformers`/`timm`/`global_land_mask`; parameter names equal the official ones. Weights: HF dataset `JayKuo/ORCA-DL-data` (`seed_1..8.bin`, 2.16 GB each; licence not stated, GitHub repo has no LICENSE file; never committed). Input: GODAS-style monthly fields on 128×360 (−63.5…63.5°), 16 levels, normalised by the shipped monthly anomaly statistics.
|
| 6 |
+
|
| 7 |
+
```python
|
| 8 |
+
from weatherai.models.orca_dl import download, download_example, load_official, load_example
|
| 9 |
+
download("/workspace/ckpt/orca_dl", seeds=(1,)); download_example("/tmp/orca_example") # example input is fetched from GitHub, not vendored
|
| 10 |
+
m = load_official("/workspace/ckpt/orca_dl", seed=1) # strict load, 539,942,562 parameters
|
| 11 |
+
ocean, atmo = load_example("/tmp/orca_example", "/workspace/ckpt/orca_dl", month=0) # (1,66,128,360), (1,2,128,360)
|
| 12 |
+
y = m(torch.from_numpy(ocean), torch.from_numpy(atmo), predict_time_steps=11) # (1,11,66,128,360), normalised; channel 32 = tos
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
* **Structure:** six ocean encoders (so, thetao, tos, uo, vo, zos) and one wind-stress encoder (patch 2×3, three Swin stages, 2-D rotary positions in windows), a fusion block (wind-stress latent gates the ocean latent after a lead-time rotation, then two global-window Swin blocks), six decoders. Every Swin MLP except the wind-stress encoder is a mixture of six lead-month experts. Lead months 1–6 are predicted *directly* from the same input; the lead-6 output becomes the next input; wind stress stays at its initial value.
|
| 16 |
+
* **Finding:** the official land-mask attention mask is a no-op (its mask is built with an all-zero convolution kernel, so it is uniform), so the native port uses standard Swin masks; numerics agree. The land mask is used only for the loss.
|
| 17 |
+
* **Verified (CPU):** strict load; 7-month rollout from the official demo input vs the official modules (seed_1): rel. L2 4.4e-07–7.3e-07, max abs 1.1e-04 (normalised units; `docs/results/orca_dl_parity.json`); random-weight small config vs official: rel. L2 7.3e-07 (`orca_dl_parity_lite.json`); 9 unit tests.
|
| 18 |
+
* **Real case (one initial month):** GODAS Jan 1980 (the official demo input), seed_1, tos vs GODAS 5 m temperature, lat-weighted RMSE (°C), ORCA-DL vs persistence vs shipped climatology: +1 mo: 0.43 vs 0.70 vs 0.99; +3 mo: 0.64 vs 1.04 vs 0.77; +6 mo: 0.72 vs 3.55 vs 0.68; +9 mo: 0.84 vs 3.38 vs 0.68; +11 mo: 0.96 vs 1.14 vs 0.90. It beats persistence at every lead and the climatology only at leads 1–2 (the climatology is the shipped training-period mean, not GODAS's own); Niño3.4 anomaly at +1 mo 0.49 vs truth 0.42 °C (`docs/results/orca_dl_real_case.json`).
|
| 19 |
+
* **HF ZeroGPU (RTX PRO 6000 Blackwell MIG, torch 2.13+cu130, 2026-10-02): PASS** — strict load 5.2 s, 7-month rollout 0.82 s, 2.4 GB peak; GPU rel. error vs CPU ≤8e-7 (tos +1/+7, zos +1).
|
| 20 |
+
* **NOT verified / not done:** multi-seed ensemble (only seed_1 downloaded), more than one initial month, decadal rollouts, the paper's Niño/IOD/AMV skill, other reanalysis inputs, training, the licence of the weights.
|
models/pangu.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Pangu-Weather — 3-D Earth-specific transformer (PyTorch)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature* 2023, [doi:10.1038/s41586-023-06185-3](https://doi.org/10.1038/s41586-023-06185-3)
|
| 4 |
+
|
| 5 |
+
`weatherai.models.pangu` is adapted from [WeatherLearn](https://github.com/lizhuoq/WeatherLearn) (MIT): patch embedding of the
|
| 6 |
+
surface (4 + 3 masks) and upper-air (5 variables × 13 levels) fields, a U-shaped stack of 3-D Earth-specific window-attention
|
| 7 |
+
blocks with an Earth position bias, and patch recovery. `Pangu` is the paper size (721 × 1440, embed 192); `Pangu_lite`
|
| 8 |
+
uses 8 × 8 patches for smaller grids. The same blocks are reused by [CAS-Canglong](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\canglong.md).
|
| 9 |
+
|
| 10 |
+
```python
|
| 11 |
+
from weatherai.models import Pangu, Pangu_lite
|
| 12 |
+
m = Pangu_lite()
|
| 13 |
+
surface, upper_air = m(surface, surface_mask, upper_air) # (B,4,721,1440), (3,721,1440), (B,5,13,721,1440)
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
Not included: the official ONNX weights (no PyTorch checkpoint is published), training recipes. Tests: `tests/models/pangu`.
|
models/pbc.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PBC: probabilistic subseasonal calibration
|
| 2 |
+
|
| 3 |
+
[Model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · [Three-paper integration](https://github.com/GISWLH/WeatherAI/blob/main/docs\research_20261019.md)
|
| 4 |
+
|
| 5 |
+
Guan et al., **Advancing subseasonal forecasting with machine learning**, Nature,
|
| 6 |
+
14 October 2026. [Paper](https://doi.org/10.1038/s41586-026-11126-x).
|
| 7 |
+
[Official MIT code](https://github.com/mouatadid/pbc), inspected at
|
| 8 |
+
`d59e18f720bac712e18563bd469d4679c120f9e5`;
|
| 9 |
+
[archived release](https://zenodo.org/records/22238796).
|
| 10 |
+
|
| 11 |
+
PBC is statistical post-processing, not an autoregressive neural forecaster.
|
| 12 |
+
It combines Debias++ (historical CDF bias subtraction), Persistence++ (per-grid,
|
| 13 |
+
per-threshold least squares on intercept, rolling climatology, two observable
|
| 14 |
+
lag indicators and raw forecast CDF), clip-then-isotonic CDF projection, and
|
| 15 |
+
equal-weight averaging. Forecasts in the paper cover days 19-25 and 26-32.
|
| 16 |
+
These are target-relative lead windows, **not** WeatherPre's standard weeks 3/4
|
| 17 |
+
(days 14-21/21-28); do not interchange the labels.
|
| 18 |
+
|
| 19 |
+
```python
|
| 20 |
+
from weatherai import PBC, PBC_lite
|
| 21 |
+
model = PBC() # PBC_lite() uses the same statistical algorithm
|
| 22 |
+
model.fit(raw_train, observed_train, clim_train, lag1_train, lag2_train,
|
| 23 |
+
debias_raw=seasonal_raw, debias_observed=seasonal_observed)
|
| 24 |
+
cdf = model(raw, climatology, lag1, lag2, debias_target=raw_observed_thresholds)
|
| 25 |
+
probabilities = model.predict_proba(raw, climatology, lag1, lag2)
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
Tensor axes: `(sample, *grid, K-1)` in training; either that layout or `(*grid,
|
| 29 |
+
K-1)` in prediction. Four CDF thresholds produce five category probabilities.
|
| 30 |
+
`ensemble_cdf` uses strict `<`, including ties at zero precipitation. Save/load
|
| 31 |
+
the fitted parameters with `state_dict`; there is no universal pretrained
|
| 32 |
+
checkpoint in the official release. SVD least squares also handles collinear
|
| 33 |
+
predictors on CUDA. The lite variant reduces data/grid size, not the algorithm.
|
| 34 |
+
|
| 35 |
+
Scope: the numerical kernels are implemented. Users must prepare the paper's
|
| 36 |
+
20-year climatologies, model/observed quintile thresholds, hindcasts, seasonal
|
| 37 |
+
training windows and observable lags; the rolling three-year selection over
|
| 38 |
+
Debias++ spans 14/28/35 and issuance-date ensembles is not automated here.
|
| 39 |
+
Pass separately prepared Debias and Persistence CDFs when their thresholds
|
| 40 |
+
differ (as they do in several official experiments). Fit only on observations
|
| 41 |
+
available at issuance. No live data download, paper RPSS replication, POET,
|
| 42 |
+
AIFS-SUBS retraining, or MicroDuet operational product is claimed.
|
| 43 |
+
|
| 44 |
+
Tests compare the isotonic result to SciPy and the fitted coefficients to
|
| 45 |
+
independent NumPy least squares, and check category normalization, rank
|
| 46 |
+
deficiency, strict ties, invalid inputs and checkpoint round trips.
|
models/stormcast.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# StormCast — native PyTorch regression U-Net + EDM diffusion U-Net (official weights load; bit-identical to PhysicsNeMo on CPU)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Science Advances* 2026, [doi:10.1126/sciadv.adv0423](https://doi.org/10.1126/sciadv.adv0423)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/stormcast` re-implements NVIDIA's StormCast v1 (ERA5 → HRRR, 3 km, 512×640 grid, 99 state + 26 conditioning + 2 invariant channels)
|
| 6 |
+
without any PhysicsNeMo dependency. Weights: HF [`nvidia/stormcast-v1-era5-hrrr`](https://huggingface.co/nvidia/stormcast-v1-era5-hrrr) (Apache-2.0, ungated; not committed here).
|
| 7 |
+
|
| 8 |
+
```python
|
| 9 |
+
from weatherai.models.stormcast import StormCast_lite, load_official
|
| 10 |
+
from weatherai.models.stormcast.convert import download
|
| 11 |
+
|
| 12 |
+
m = StormCast_lite() # tiny trainable config (64x64, 8 state channels), same code path
|
| 13 |
+
y = m(x_state, x_cond) # one 1-hour step: regression mean + 18/4-step Heun diffusion residual
|
| 14 |
+
|
| 15 |
+
download("/workspace/ckpt/stormcast") # ~800 MB from the Hub (HF_TOKEN only needed if you are rate limited)
|
| 16 |
+
model = load_official("/workspace/ckpt/stormcast") # strict load of both networks + means/stds/invariants
|
| 17 |
+
nxt = model(x_hrrr, x_cond_on_hrrr_grid) # (B,99,512,640) physical units, (B,26,512,640) coarse conditioning interpolated to HRRR
|
| 18 |
+
mean_only = model(x_hrrr, x_cond_on_hrrr_grid, deterministic_only=True) # regression mean without the diffusion residual
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
* **What is verified (CPU, fp32, real official checkpoint, 512×640):** the native `StormCastUNet` and `EDMPrecond` match the PhysicsNeMo 2.2.2 modules loaded from the *same* `.mdlus` state dicts with **max|Δ| = 0.0** (regression net, and the EDM denoiser at σ = 0.002 / 0.5 / 5 / 800; random-normal inputs). Strict loading works (only the two empty `device_buffer` tensors are dropped); parameter counts 78,588,387 and 121,058,275.
|
| 22 |
+
Small random-weight parity (64×64) and the 6-step Heun sampler against `physicsnemo.diffusion.samplers.sample` + `EDMNoiseScheduler` also pass (`tests/parity/stormcast`).
|
| 23 |
+
Quirk reproduced on purpose: PhysicsNeMo's eval-mode GroupNorm uses the **unbiased** variance.
|
| 24 |
+
* **HF ZeroGPU (RTX PRO 6000 Blackwell MIG 2g.48gb, torch 2.13+cu130, 2026-10-02): PASS** — official weights downloaded on the Space, strict-loaded in 2.7 s; regression 512×640 forward 0.3–0.5 s; relative error vs the stored CPU reference slices 1.7e-6 (regression) / 7.7e-6 (EDM σ = 5) with TF32 off (8e-4 / 6.5e-3 with default TF32); a full step (regression + 18 Heun steps = 35 denoiser evaluations) took 10.3 s; peak GPU memory 5.2 GB; different seeds give different samples.
|
| 25 |
+
* **NOT verified:** forecast skill on real data — no real HRRR/GFS/ERA5 case was run (inputs were random tensors at physical mean/std), no multi-hour rollout, no comparison with the Earth2Studio pipeline end-to-end, no reproduction of paper scores, no bf16/fp16, no training of the official-size networks (only the lite config was trained for gradient flow). The HRRR/ERA5 data pipeline (hybrid-level fields, interpolation of the global conditioning to the HRRR grid) is not included.
|
models/translocation.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gray-swan translocation: forecast diagnostics
|
| 2 |
+
|
| 3 |
+
[Model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · [Three-paper integration](https://github.com/GISWLH/WeatherAI/blob/main/docs\research_20261019.md)
|
| 4 |
+
|
| 5 |
+
Sun et al., **Predicting regional gray swans via translocation: AI weather models
|
| 6 |
+
and Dubai's unprecedented 2024 rainfall**, Science Advances, 7 October 2026.
|
| 7 |
+
[Paper](https://doi.org/10.1126/sciadv.ady9406).
|
| 8 |
+
|
| 9 |
+
Classification: extreme-event generalization and interpretability. This paper
|
| 10 |
+
does **not** introduce a sixth/new forecasting architecture. It evaluates
|
| 11 |
+
GraphCast, FuXi, AIFS, Pangu-Weather and FourCastNet-v2, and investigates
|
| 12 |
+
GraphCast/AIFS effective receptive fields (ERFs) and fine-tuning experiments.
|
| 13 |
+
|
| 14 |
+
Official resources from the paper:
|
| 15 |
+
|
| 16 |
+
| Resource | Code / model |
|
| 17 |
+
|---|---|
|
| 18 |
+
| GraphCast | [DeepMind code and weight links](https://github.com/google-deepmind/graphcast) |
|
| 19 |
+
| FuXi | [Code and ONNX weight links](https://github.com/tpys/FuXi) |
|
| 20 |
+
| AIFS | [ecmwf/aifs-single-1.0](https://huggingface.co/ecmwf/aifs-single-1.0) |
|
| 21 |
+
| Pangu / FourCastNet-v2 | [ECMWF ai-models integrations](https://github.com/ecmwf-lab/ai-models) |
|
| 22 |
+
| GraphCast fine-tuning | [csubich/graphcast](https://github.com/csubich/graphcast) |
|
| 23 |
+
| Dynamically similar event tracking | [cyclonetracking](https://github.com/alexcrawford0927/cyclonetracking) |
|
| 24 |
+
| Paper ERF notebook and data | [Zenodo 18111128](https://zenodo.org/records/18111128) |
|
| 25 |
+
|
| 26 |
+
Zenodo lists the 19,427-byte notebook `graphcast-ERF-cleaned.ipynb`, README,
|
| 27 |
+
and three ERF data ZIP files (~3.14, 2.03 and 2.41 GB). These are diagnostics,
|
| 28 |
+
not newly released model checkpoints. Model licenses follow each backbone.
|
| 29 |
+
|
| 30 |
+
```python
|
| 31 |
+
from weatherai import Translocation, Translocation_lite
|
| 32 |
+
model = Translocation_lite() # random-weight small GraphCast
|
| 33 |
+
# For scientific use: model = Translocation(your_differentiable_forecaster).eval()
|
| 34 |
+
result = model.effective_receptive_field(x, channel=0, location=(3, 4))
|
| 35 |
+
# Or region_mask=(H,W): select the strongest predicted value inside the region.
|
| 36 |
+
signed_erf = result['gradient'] # B,C,H,W, with original signs preserved
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
The wrapper delegates forecasts to its supplied backbone. The diagnostic uses
|
| 40 |
+
the derivative of one selected output with respect to every input variable,
|
| 41 |
+
as in the paper's Dubai analysis. It does not change/accumulate parameter
|
| 42 |
+
gradients. Tests check signed analytical derivatives and a GraphCast directional
|
| 43 |
+
finite-difference oracle. Real use requires the correct physical output,
|
| 44 |
+
normalization, precipitation channel and coordinates. Lite channel 0 has no
|
| 45 |
+
physical variable identity. A nonlocal ERF is not by itself evidence of causal
|
| 46 |
+
translocation. The paper's weather reconstruction, event tracking and
|
| 47 |
+
fine-tuning ablations have not been reproduced.
|
models/tropicyclonenet.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TropiCycloneNet (TCN_M) — native PyTorch, official weights load, bit-exact vs official code
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature Communications* 2025, [doi:10.1038/s41467-025-61087-4](https://doi.org/10.1038/s41467-025-61087-4)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/tropicyclonenet` re-implements the TCN_M generator (*Nature Communications* 2025): `Unet3D` (8 observed 64×64 500 hPa geopotential frames → 12), two `EnvNet`s (tabular environment + geopotential, 2-layer Transformer), two LSTM encoders, six LSTM decoders (generators) and a class chooser; 6-hourly steps, 8 observed → 4 forecast steps (6–24 h) of (Δlon, Δlat, Δpressure, Δwind). Checkpoint: Zenodo 15024028 `checkpoint_with_model_16000.pt` (CC-BY-4.0, 4,767,195 parameters; not committed). Data: public TCND release (Zenodo 15009527, CC-BY-4.0); `scripts/tcn_prepare_ni.py` builds the official loader layout (NI test split).
|
| 6 |
+
|
| 7 |
+
```python
|
| 8 |
+
from weatherai.models.tropicyclonenet import download, load_official, make_sample, sequences
|
| 9 |
+
m = load_official(download("/workspace/ckpt/tcn")) # strict load of g_state
|
| 10 |
+
f = f"{root}/BST_data/NI/test/NI2019BSTFANI.txt"
|
| 11 |
+
s = make_sample(root, f, sequences(f)[5])
|
| 12 |
+
inc, cls = m.sample(s["obs_traj_rel"], s["image_obs"], s["env"], num_samples=6) # (4, 6, 1, 4) increments; works on CPU (the official code calls .cuda())
|
| 13 |
+
# relative_to_abs + to_physical + track_error_km give km errors: see scripts/tcn_eval_ni.py
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
* **Verified (CPU):** official-code parity on 24 real NI test windows built by the *official* dataset class: my data loader (no OpenCV; bilinear resize) matches it to 6e-7, the six generators and the chooser logits are bit-identical (max abs 0) at matched RNG, and the 6-member sampling path is identical (`docs/results/tcn_parity.json`, `scripts/tcn_parity.py`); 5 unit tests.
|
| 17 |
+
* **Official-code defects found (not copied):** (1) the sampling loop iterates `range(n_unique)` instead of the drawn class ids, so on a single storm 42 of 48 members stay at the `ones` initialisation (native: 0); (2) `all_g_out=True` ignores `user_noise`. The chooser is almost uniform (mean max probability 0.174 vs 1/6 = 0.167; argmax classes [101, 197, 4, 0, 5, 0] on the NI test set).
|
| 18 |
+
* **NI test split (20 storms 2017–2022, 307 windows, 3 sampling seeds), track error at 6/12/18/24 h in km:** best of 6 members (official metric) 30 / 53 / 79 / 113; mean member 39 / 77 / 118 / 164; constant velocity 43 / 89 / 137 / 191; persistence 84 / 166 / 246 / 326. At 24 h the pressure MAE is 9.4 hPa and the wind MAE 7.7 m/s (best-track member) (`docs/results/tcn_eval_ni.json`). The official script quotes 24 / 47 / 72 / 103 km (all six basins, checkpoint 5100); not directly comparable.
|
| 19 |
+
* **HF ZeroGPU (2026-10-02):** **PASS** — load 0.1 s, forward+6-member sample 1.3 s, peak 0.35 GB, max abs vs CPU 3.7e-6
|
| 20 |
+
* **NOT verified / not done:** the other five basins and the paper's all-basin scores, checkpoints other than `g_state` (`g_best_state`, `g_best_nl_state` load but were not evaluated), the Himawari visualisation, training (the discriminator/GAN loop is not ported), the code licence (GitHub repo has no LICENSE file).
|
models/unicm.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# UniCM — native PyTorch, bit-exact vs official code (random weights, no public checkpoint)
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature Machine Intelligence* 2026, [doi:10.1038/s42256-026-01245-5](https://doi.org/10.1038/s42256-026-01245-5)
|
| 4 |
+
|
| 5 |
+
`weatherai/models/unicm` re-implements UniCM (*Nature Machine Intelligence* 2026): two encoder–decoder transformers (spatio-temporal field branch and climate-mode series branch) on a 12×72 ocean grid with 5 channels (SST, taux, tauy, HTC300, T20D), 2×2 patches, d=256, 4 heads, 4+4 layers, 12 monthly steps in → 24 out; 12,726,037 parameters, state-dict keys identical to the official `UniCM`. `climate_modes` computes the mode series from the fields (region means; dipoles; WWV proxy), `unicm_loss` is the teacher-forced SST + mode loss (the skill term is not ported), `load_official(path)` strict-loads an official `model_best.pkl`. Official-code quirks are reproduced (spatial attention ignores its mask; decoder FFN reuses `sublayer[1]`; `time_emb` shared between branches).
|
| 6 |
+
|
| 7 |
+
* **Verified (CPU, random weights, official `models.py` unchanged; `scripts/unicm_parity.py`):** 24-step eval rollout bit-identical (max abs 0.0, field and modes; full size and lite), teacher-forced pass rel. L2 3e-7, gradients rel. L2 ≤2e-5 per parameter (`docs/results/unicm_parity*.json`); 5 unit tests with a stored lite reference.
|
| 8 |
+
* **Checkpoint:** none public (GitHub and Zenodo 19173780 hold code only; weights come from local training with `app_train.py` on CMIP6/ORAS5, not obtainable here).
|
| 9 |
+
* **HF ZeroGPU (2026-10-02):** **PASS** — 24-step rollout 0.36 s, batch-4 train step 0.21 s, peak 3.8 GB, lite vs official reference max abs 7e-7
|
| 10 |
+
* **NOT verified / not done:** any pretrained forecast or skill, training on real data, the lambda3 skill loss.
|
models/weathernext_cyclones.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# WeatherNext Cyclones Mini — native PyTorch network (official Mini weights load), JAX wrapper kept as oracle
|
| 2 |
+
|
| 3 |
+
[← Model zoo](..\README.md) · [All model pages](https://github.com/GISWLH/WeatherAI/blob/main/docs\models\README.md) · **Paper:** *Nature* 2026, [doi:10.1038/s41586-026-10953-2](https://doi.org/10.1038/s41586-026-10953-2)
|
| 4 |
+
|
| 5 |
+
WN-C / WeatherNext 2 (FGN, a *Functional Generative Network*) is the same family as GenCast/GraphCast (grid → icosahedral mesh → 16-layer k-hop
|
| 6 |
+
transformer → grid) but with its own wiring: instead of a diffusion noise level, a 32-channel N(0,1) vector conditions every normalisation, the
|
| 7 |
+
encoders/decoder are one big split-input / split-output linear over all variables, and the GNN edge MLPs use the "pre-gather matmul" form.
|
| 8 |
+
`weatherai.models.weathernext_cyclones` now contains an **explicit native PyTorch port of that network** (`network.py`; transformer, attention
|
| 9 |
+
and graph code are shared with the GenCast port), with the official `WeatherNextCyclones_Mini_<2024` weights converted 1:1 and
|
| 10 |
+
`WeatherNextCyclonesWrapper` (official JAX, Python ≥ 3.12) retained only as a test/oracle path.
|
| 11 |
+
|
| 12 |
+
```python
|
| 13 |
+
import numpy as np, torch
|
| 14 |
+
from weatherai.models.weathernext_cyclones import (
|
| 15 |
+
WeatherNextCyclonesNet_from_official, stack_grid_inputs, stack_mesh_inputs, unstack_outputs, download_checkpoint)
|
| 16 |
+
|
| 17 |
+
lat = np.arange(-90, 90.5, 1.0, dtype=np.float32); lon = np.arange(0, 360, 1.0, dtype=np.float32) # official 1° grid, 181x360
|
| 18 |
+
net = WeatherNextCyclonesNet_from_official(download_checkpoint(), lat, lon, attention_type="triblockdiag") # 56.7 M params, strict load
|
| 19 |
+
# inputs: *normalised* fields in the official layout (see stack_grid_inputs), noise ~ N(0,1) of shape (batch, 32)
|
| 20 |
+
gx = stack_grid_inputs(inputs, forcings, (181, 360)); mx = stack_mesh_inputs(inputs, forcings)
|
| 21 |
+
y = net(gx, mx, torch.randn(1, 32)) # (B, H*W, 101) one 6 h step, all 29 target variables
|
| 22 |
+
fields = unstack_outputs(y, net.cfg, (181, 360)) # {name: (B,1,[13,]181,360)}
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
Trainable, config-driven variant (same modules, ~0.2 M params, fair-CRPS ensemble loss as in the official `fgn.Predictor`):
|
| 26 |
+
`WeatherNextCyclonesNative_lite()` → `m.loss(grid_x, mesh_x, target, num_samples=2)`; example in `tests/models/weathernext_cyclones/test_wnc_native.py`.
|
| 27 |
+
|
| 28 |
+
**Status — verified:**
|
| 29 |
+
* All 376 official parameter arrays (56 684 133 weights) are consumed exactly once by `convert_official_params` and strict-load.
|
| 30 |
+
* Network vs the **official JAX `ForwardPass`** (official Mini weights, random inputs + noise, plain `mha`; JAX/Haiku run on CPU):
|
| 31 |
+
* float64 reference, mesh_splits=2, k_hop=3, 13×24 grid, batch 2 (the *reference* is run with x64 and its bf16-oriented fp32 up-casts disabled, so rounding
|
| 32 |
+
cannot hide a structural error): native vs official max |Δ| **2.2e-12** (dense attention) / **2.1e-12** (banded attention) — test tolerance 1e-9.
|
| 33 |
+
* float32 official run, same small config: max |Δ| 1.4e-3 (outputs up to ~26; official-JAX f32 vs official-JAX f64 differ by 9.3e-4) — test tolerance 5e-3.
|
| 34 |
+
* **trained size** (mesh_splits=5 → 10 242 mesh nodes, k_hop 16, 181×360 grid, banded attention, float32): max |Δ| 2.8e-3 on outputs up to ~25
|
| 35 |
+
(per variable ≤ 2.9e-3; probabilities 3.5e-6) — test tolerance 5e-3 (the 65 MB reference is not committed; regenerate with `scripts/wnc_forward_reference.py full`).
|
| 36 |
+
* graph edge sets (ball query grid→mesh 101 892 edges; closest triangle mesh→grid 195 480 edges at full size, 576/936 at the small size) equal the official arrays.
|
| 37 |
+
* HF ZeroGPU (RTX PRO 6000 Blackwell MIG, torch 2.13+cu130) `--pretrained` smoke: PASS — strict load, float64 max |Δ| 1.9e-12 / 1.6e-12 (dense / banded), float32 max |Δ|
|
| 38 |
+
7.6e-4 / 1.2e-3 (TF32 off) and 8.3e-4 / 1.3e-3 (TF32 default), peak 533 MB; Space unit tests: 5 passed, 10 skipped (the skips need local official ckpt or the JAX package; parity is covered by the smoke).
|
| 39 |
+
|
| 40 |
+
**Not verified / not ported:** the `fgn.Predictor` wrapper stack (input/residual normalisation constants, NaN cleaning of SST, autoregressive rollout, the
|
| 41 |
+
2-sample training ensemble), data pipeline/ERA5-HRES loading, the **cyclone tracker** (`cyclones/direct_tracker.py`) and IBTrACS pipeline; multi-step forecasts of the
|
| 42 |
+
native model; the 0.25° / WN2 / `<2023` checkpoints; skill. The comparison is network-level on random inputs, not forecast-level on real data. The official TPU
|
| 43 |
+
`splash_mha` kernel is not used (neither on our side nor in the reference). Stochastic sampling: noise is drawn iid N(0,1) as in the official `gaussian_noise_generator`.
|