Download model/common.py from OneScience-Group/GenCast: direct link, hf CLI and curl.
- Browser
- Download file 5.68 kB
-
https://huggingface.co/OneScience-Group/GenCast/resolve/main/model/common.py
- Command line
-
hf download hf://OneScience-Group/GenCast/model/common.py
-
curl -L -o common.py https://huggingface.co/OneScience-Group/GenCast/resolve/main/model/common.py
5.68 kB
| from __future__ import annotations | |
| import json | |
| import os | |
| from pathlib import Path | |
| from typing import Any | |
| import numpy as np | |
| import xarray | |
| import yaml | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| def load_config(path: str | Path) -> dict[str, Any]: | |
| with Path(path).open(encoding="utf-8") as source: | |
| return yaml.safe_load(source) | |
| def resolve_path(path: str | Path) -> Path: | |
| candidate = Path(path).expanduser() | |
| return candidate if candidate.is_absolute() else PROJECT_ROOT / candidate | |
| def configure_jax(platform: str) -> None: | |
| if platform != "auto" and "JAX_PLATFORM_NAME" not in os.environ: | |
| os.environ["JAX_PLATFORM_NAME"] = platform | |
| def load_stats(stats_dir: str | Path) -> dict[str, xarray.Dataset]: | |
| directory = resolve_path(stats_dir) | |
| names = ( | |
| "diffs_stddev_by_level", | |
| "mean_by_level", | |
| "stddev_by_level", | |
| "min_by_level", | |
| ) | |
| stats = {} | |
| for name in names: | |
| path = directory / f"{name}.nc" | |
| if not path.exists(): | |
| raise FileNotFoundError(f"Missing GenCast statistic: {path}") | |
| stats[name] = xarray.load_dataset(path).compute() | |
| from model.graphcast import gencast, graphcast | |
| inputs = set(gencast.TASK.input_variables) - set(graphcast.GENERATED_FORCING_VARS) | |
| targets = set(gencast.TASK.target_variables) | |
| required_by_stat = { | |
| "mean_by_level": inputs | (targets - inputs), | |
| "stddev_by_level": inputs | (targets - inputs), | |
| "diffs_stddev_by_level": targets & inputs, | |
| "min_by_level": {"sea_surface_temperature"}, | |
| } | |
| for stat_name, dataset in stats.items(): | |
| missing = sorted(required_by_stat[stat_name] - set(dataset.data_vars)) | |
| if missing: | |
| raise ValueError(f"{stat_name} is missing GenCast variables: {missing}") | |
| for name, values in dataset.data_vars.items(): | |
| array = np.asarray(values) | |
| if not np.all(np.isfinite(array)): | |
| raise ValueError(f"{stat_name}.{name} contains non-finite values") | |
| if "level" in values.dims and tuple(values.level.values) != tuple( | |
| gencast.TASK.pressure_levels | |
| ): | |
| raise ValueError(f"{stat_name}.{name} does not use GenCast WB13 order") | |
| if stat_name in ("stddev_by_level", "diffs_stddev_by_level") and np.any(array <= 0): | |
| raise ValueError(f"{stat_name}.{name} must be strictly positive") | |
| return stats | |
| def save_trainer_checkpoint( | |
| path: str | Path, | |
| *, | |
| params: Any, | |
| state: Any, | |
| optimizer_state: Any, | |
| step: int, | |
| config: dict[str, Any], | |
| ) -> None: | |
| import jax | |
| destination = resolve_path(path) | |
| destination.parent.mkdir(parents=True, exist_ok=True) | |
| leaves, treedef = jax.tree_util.tree_flatten( | |
| {"params": params, "state": state, "optimizer_state": optimizer_state} | |
| ) | |
| arrays = {f"leaf_{i}": np.asarray(value) for i, value in enumerate(leaves)} | |
| arrays["treedef"] = np.array([treedef], dtype=object) | |
| arrays["step"] = np.asarray(step, dtype=np.int64) | |
| arrays["config_json"] = np.asarray(json.dumps(config, sort_keys=True)) | |
| temporary = destination.with_suffix(destination.suffix + ".tmp") | |
| with temporary.open("wb") as output: | |
| np.savez(output, **arrays) | |
| os.replace(temporary, destination) | |
| def load_trainer_checkpoint( | |
| path: str | Path, | |
| ) -> tuple[Any, Any, Any, int, dict[str, Any]]: | |
| import jax | |
| source_path = resolve_path(path) | |
| with np.load(source_path, allow_pickle=True) as source: | |
| treedef = source["treedef"].item() | |
| leaves = [source[f"leaf_{i}"] for i in range(len(source.files) - 3)] | |
| tree = jax.tree_util.tree_unflatten(treedef, leaves) | |
| saved_config = json.loads(str(source["config_json"])) | |
| return ( | |
| tree["params"], tree["state"], tree["optimizer_state"], | |
| int(source["step"]), saved_config, | |
| ) | |
| def validate_checkpoint_config( | |
| current: dict[str, Any], | |
| saved: dict[str, Any], | |
| *, | |
| scope: str = "resume", | |
| ) -> None: | |
| """Validate checkpoint compatibility for training resume or inference.""" | |
| if scope not in ("resume", "inference"): | |
| raise ValueError("scope must be 'resume' or 'inference'") | |
| inference_paths = ( | |
| ("model",), ("sampler",), ("data", "stats_dir"), | |
| ("data", "static_dir"), ("data", "precipitation_interval_hours"), | |
| ) | |
| resume_only_paths = ( | |
| ("training", "learning_rate"), | |
| ("training", "betas"), ("training", "epsilon"), | |
| ("training", "seed"), ("data", "data_dir"), | |
| ("data", "train_years"), ("data", "train_stride"), | |
| ("parallel", "mode"), ("parallel", "num_devices"), | |
| ("parallel", "global_batch_size"), ("parallel", "axis_name"), | |
| ) | |
| if scope == "resume": | |
| if "parallel" not in saved: | |
| saved = dict(saved) | |
| saved["parallel"] = { | |
| "mode": "single", | |
| "num_devices": 1, | |
| "global_batch_size": 1, | |
| "axis_name": "devices", | |
| } | |
| paths = inference_paths + resume_only_paths | |
| else: | |
| paths = inference_paths | |
| for path in paths: | |
| current_value: Any = current | |
| saved_value: Any = saved | |
| for key in path: | |
| current_value = current_value[key] | |
| saved_value = saved_value[key] | |
| if current_value != saved_value: | |
| name = ".".join(path) | |
| raise ValueError( | |
| f"Trainer checkpoint configuration mismatch for {name} " | |
| f"during {scope}: " | |
| f"saved={saved_value!r}, current={current_value!r}" | |
| ) | |