Spaces:
Running on Zero
Running on Zero
File size: 4,409 Bytes
d7d41bd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | """
aifs.era5_worker
================
Standalone subprocess entry point — reads EarthMover's public ERA5
Icechunk/Zarr-v3 store on S3 (anonymous access, no account needed).
This script is deliberately NOT imported by the rest of the app. It is
invoked via ``subprocess`` with ``PYTHONPATH`` pointed at an isolated
``pip install --target`` directory (see :mod:`aifs.era5_env`), because
``icechunk`` requires ``zarr>=3`` while ``anemoi-datasets`` (already a
hard dependency of this Space, for AIFS inference) pins ``zarr<=2.18``.
The two cannot coexist in one interpreter's import path, so this script
only ever runs in a separate process with its own isolated zarr install.
Only stdlib + numpy + icechunk + zarr are imported here — keep it that
way so it never accidentally picks up the main env's (incompatible)
zarr via a transitive import.
Usage
-----
python3 era5_worker.py <request.json> <response_prefix>
``request.json`` is a list of ``{"group", "var", "level", "time_idx"}``
dicts. Writes ``<response_prefix>.npz`` (arrays keyed by request index,
one per successfully-read request) and ``<response_prefix>.meta.json``
(``{"errors": {...}, "resolved_levels": {...}}``). Progress is reported
as ``PROGRESS <i>/<n> <group>/<var>`` lines on stdout.
"""
import json
import sys
import time
import numpy as np
BUCKET = "earthmover-icechunk-era5"
PREFIX = "icechunkV2"
REGION = "us-east-1"
MAX_RETRIES = 6
_RETRIABLE_KEYWORDS = (
"429", "rate limit", "too many requests", "timeout", "connection reset",
"503", "service unavailable", "throughput", "streaming error", "i/o error",
)
def _retriable(exc: Exception) -> bool:
msg = str(exc).lower()
return any(k in msg for k in _RETRIABLE_KEYWORDS)
def _with_retry(fn, label: str):
for attempt in range(MAX_RETRIES):
try:
return fn()
except Exception as exc:
if attempt < MAX_RETRIES - 1 and _retriable(exc):
wait = min(3 * (2 ** attempt), 30)
print(f"RETRY {label}: {exc} (attempt {attempt + 2}/{MAX_RETRIES}, waiting {wait}s)", flush=True)
time.sleep(wait)
else:
raise
def _open_store():
import icechunk
def _open():
storage = icechunk.s3_storage(bucket=BUCKET, prefix=PREFIX, region=REGION, anonymous=True)
repo = icechunk.Repository.open(storage)
session = repo.readonly_session("main")
return session.store
return _with_retry(_open, "open icechunk repo")
def main():
request_path, response_prefix = sys.argv[1], sys.argv[2]
with open(request_path) as f:
requests = json.load(f)
import zarr
store = _open_store()
groups = {}
level_coords = {}
results = {}
errors = {}
resolved_levels = {}
n = len(requests)
for i, req in enumerate(requests):
group, var, level, time_idx = req["group"], req["var"], req.get("level"), req["time_idx"]
print(f"PROGRESS {i + 1}/{n} {group}/{var}" + (f"@{level}hPa" if level is not None else ""), flush=True)
try:
if group not in groups:
groups[group] = _with_retry(
lambda g=group: zarr.open_group(store, mode="r", path=f"{g}/spatial"),
f"open group {group}",
)
g = groups[group]
if var not in g.array_keys():
raise KeyError(f"'{var}' not found in ERA5 group '{group}' (have: {sorted(g.array_keys())})")
arr = g[var]
def _read():
if level is not None:
if group not in level_coords:
level_coords[group] = np.asarray(g["pressure_level"][:])
levels = level_coords[group]
pos = int(np.argmin(np.abs(levels - level)))
resolved_levels[str(i)] = float(levels[pos])
return arr[time_idx, pos]
return arr[time_idx]
data = _with_retry(_read, f"read {group}/{var}")
results[str(i)] = np.asarray(data, dtype=np.float32)
except Exception as exc:
errors[str(i)] = str(exc)
np.savez_compressed(f"{response_prefix}.npz", **results)
with open(f"{response_prefix}.meta.json", "w") as f:
json.dump({"errors": errors, "resolved_levels": resolved_levels}, f)
if __name__ == "__main__":
main()
|