简介:这份资源面向气象学研究者、AI 工程实践者与需要本地跑通气象大模型的学生,提供 Pangu、Fuxi、Fengwu、GraphCast、FourCastNet 等模型的本地部署项目代码。压缩包共 2 个文件,以 html 页面与 inscode 工程文件为主,前者用于呈现部署说明与索引,后者承载可运行的工程配置,整体仅 5KB,轻量易取。内容围绕虚拟环境创建、依赖库安装、模型接入、预训练权重下载与 ERA5 输入数据对接展开,并针对 GPU 调用失败、版本不匹配等常见问题给出排错思路,Fengwu 等需手动安装的模型也有对应指引。已有 138 人学习,适合希望快速搭建本地气象预报实验环境、对照复现模型流程的读者参考。
1. 本地部署气象大模型:从“跑不起来”到“跑得稳”的那道坎
很多人第一次拿到气象大模型的代码包,第一反应是“这不就是个推理脚本吗,装个环境就能跑”。结果真到本地一跑,显存直接爆掉,或者模型加载到一半报了个 shape mismatch,再或者推理出来的场数据全是 NaN。气象大模型和普通 CV/NLP 模型最大的区别在于:它的输入输出往往是多维网格场,涉及气压层、时间步、经纬度网格,数据预处理和模型结构耦合得非常紧。这份项目代码解决的就是这个问题——它把数据读取、归一化、模型加载、推理、后处理打包成了一条可复现的链路,适合想在本地做气象场推理、不想被在线服务绑住的从业者。你不需要从头搭一个 Transformer,但你需要知道每个环节的参数边界在哪。
2. 环境搭建与依赖锁定:为什么你的 CUDA 版本总是对不上
2.1 先看模型到底吃什么硬件
气象大模型的参数量通常不会像 LLM 那样夸张,但它的中间激活值非常吃显存。原因是输入场往往是[B, T, C, H, W]这种五维张量,T 是时间步,C 是变量数(位势、温度、湿度、风场等),H/W 是经纬度网格。常见做法是先用一个小规模配置跑通,再逐步放大。
我一般会先确认三件事:
- 模型权重的精度:fp32 还是 fp16/bf16。fp32 下显存占用大约是 fp16 的两倍。
- 推理时的 batch size 和时间步长度。这两个参数直接决定峰值显存。
- 是否用了 FlashAttention 或类似算子。用了的话对 CUDA 版本有硬性要求。
项目代码里通常会有一个requirements.txt或environment.yml,但光看这个不够,因为 PyTorch 的 CUDA 版本和系统驱动是两回事。下面是我习惯的检查顺序:
# 先看驱动支持的 CUDA 上限 nvidia-smi # 再看 PyTorch 实际编译用的 CUDA 版本 python -c "import torch; print(torch.version.cuda); print(torch.cuda.is_available())" # 确认 cuDNN 版本,气象模型里卷积和 attention 混用很常见 python -c "import torch; print(torch.backends.cudnn.version())"逻辑说明:nvidia-smi右上角的 CUDA Version 是驱动能支持的最高版本,不是当前安装的版本。torch.version.cuda才是 PyTorch 实际编译时链接的版本。这两个不一致是绝大多数“CUDA 不可用”的根因。
参数说明:如果torch.cuda.is_available()返回 False,先别急着重装驱动,优先检查 PyTorch 是不是装成了 CPU 版。用pip list | grep torch看包名,torch后面带+cpu的就是 CPU 版。
2.2 依赖锁定的实操步骤
气象大模型常见的依赖坑集中在三个包:xarray、netCDF4、cfgrib。这三个包处理气象格点数据,版本不兼容时会出现“能读文件但维度顺序不对”的玄学问题。
# 建议用 conda 建独立环境,不要混在 base 里 conda create -n weather-ai python=3.10 -y conda activate weather-ai # 先装 PyTorch,按官方命令选对应 CUDA 版本 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 再装气象数据处理栈,锁定版本 pip install xarray==2023.8.0 netCDF4==1.6.4 cfgrib==0.9.10.4 pip install numpy==1.24.3 pandas==2.0.3逻辑说明:先装 PyTorch 再装其他包,是因为xarray等包会间接依赖numpy,如果先装了高版本 numpy,再装 PyTorch 时可能被降级,导致其他包报错。锁定numpy==1.24.3是因为 numpy 2.x 对部分气象库的 ABI 不兼容。
参数说明:python=3.10是气象 AI 生态里兼容性最好的版本,3.11 以上部分 cfgrib 版本会出问题。CUDA 选 11.8 还是 12.1 取决于你的驱动版本,驱动 525 以上选 12.1,否则选 11.8。
提示:如果你的机器没有 GPU,项目代码通常也支持 CPU 推理,但时间步长和网格分辨率要降到最低档,否则推理时间会从秒级变成小时级。
3. 数据预处理:格点场对齐与归一化的三个关键参数
3.1 气象数据的维度顺序陷阱
气象大模型的输入不是图片,而是格点场。常见的存储格式是 NetCDF 或 GRIB,维度顺序可能是(time, level, lat, lon)或(time, lat, lon, level)。项目代码里一般会有一个preprocess.py或dataset.py,核心工作就是把不同来源的场对齐到同一个网格上。
我见过最多的翻车场景是:训练时用的纬度是从北到南,推理时读的数据是从南到北,模型不报错,但输出完全反了。这种问题不会抛异常,只能靠可视化发现。
import xarray as xr import numpy as np def load_and_align(path, target_lat, target_lon): ds = xr.open_dataset(path) # 统一纬度顺序为从南到北,避免隐性翻转 if ds.lat[0] > ds.lat[-1]: ds = ds.sortby('lat') # 插值到目标网格,method 用 linear 还是 nearest 取决于变量类型 ds = ds.interp(lat=target_lat, lon=target_lon, method='linear') # 检查是否有 NaN,气象场里缺测值很常见 if ds.to_array().isnull().any(): ds = ds.fillna(0.0) # 或按变量用气候态填充 return ds逻辑说明:sortby('lat')保证纬度单调递增,这是大多数气象模型预训练的默认约定。interp做网格对齐,但注意method='linear'对连续变量(温度、位势)合适,对分类变量(海陆掩膜)要用nearest。
参数说明:target_lat和target_lon必须和模型训练时的网格一致,通常代码包里会有一个grid_info.json或类似文件,里面写了分辨率(如 0.25 度)和范围。不要自己随便定,否则模型输出没有物理意义。
3.2 归一化:均值方差从哪来
气象场的量级差异很大:位势高度是几千,温度是两三百,风速是几十。如果不做归一化,模型训练会很难收敛。项目代码里一般会提供mean.nc和std.nc两个统计文件,或者直接在配置里写死数值。
import json def normalize(data, stats_path): with open(stats_path, 'r') as f: stats = json.load(f) # 按变量逐个归一化,不要全局减同一个均值 for var in data.data_vars: if var in stats: mean = stats[var]['mean'] std = stats[var]['std'] data[var] = (data[var] - mean) / std return data逻辑说明:按变量归一化是气象 AI 的标准做法。全局归一化会让小量级变量(如湿度)被大量级变量(如位势)淹没。
参数说明:stats_path指向的统计文件必须和训练时用的是同一份。如果你自己重新算,要注意统计的时间范围和空间范围要和训练集一致,否则推理分布偏移。
注意:有些项目代码会把归一化写在模型 forward 里,有些写在 dataset 里。如果两边都做了,输出会二次归一化,表现为数值极小或极大。排查时先看模型输出范围是否在 [-1, 1] 附近。
4. 模型加载与推理:显存不够时的四个降级策略
4.1 权重加载的常见报错与处理
项目代码里的模型定义通常是一个WeatherModel类,权重是.pt或.ckpt文件。加载时报Missing key(s)或Unexpected key(s)是最常见的。
import torch from model import WeatherModel def load_model(ckpt_path, config_path, device='cuda'): with open(config_path, 'r') as f: config = json.load(f) model = WeatherModel(**config['model_args']) ckpt = torch.load(ckpt_path, map_location='cpu') # 有些 ckpt 外层包了 state_dict 或 model 键 if 'state_dict' in ckpt: ckpt = ckpt['state_dict'] elif 'model' in ckpt: ckpt = ckpt['model'] # 去掉 DataParallel 的 module. 前缀 ckpt = {k.replace('module.', ''): v for k, v in ckpt.items()} model.load_state_dict(ckpt, strict=False) model.to(device) model.eval() return model逻辑说明:strict=False允许部分 key 不匹配,但你要打印出 missing 和 unexpected 的 key 列表,确认不是关键层缺失。module.前缀是 DataParallel 或 DDP 训练留下的,推理时要去掉。
参数说明:map_location='cpu'先加载到 CPU 再移到 GPU,避免 GPU 显存不够导致加载失败。如果 ckpt 是 fp16 保存的,加载后要手动model.half()或保持 fp32 取决于你的推理精度需求。
4.2 显存降级的四个档位
当显存不够时,按以下顺序降级,不要一上来就换小模型:
| 档位 | 操作 | 显存降幅 | 代价 |
|---|---|---|---|
| 1 | 减小 batch size 到 1 | 30%-50% | 推理时间线性增加 |
| 2 | 减小时间步长度 | 20%-40% | 长序列预报能力下降 |
| 3 | 混合精度 fp16/bf16 | 40%-50% | 部分算子精度损失 |
| 4 | 梯度检查点(推理时也可用) | 50%-70% | 速度慢一倍 |
# 混合精度推理示例 with torch.no_grad(): with torch.autocast(device_type='cuda', dtype=torch.float16): output = model(input_tensor) # 输出转回 fp32 再做后处理 output = output.float()逻辑说明:autocast自动把部分算子转 fp16,但 layer norm 等对精度敏感的算子会保持 fp32。输出转回 fp32 是因为后处理里的物理计算(如计算风速)对精度有要求。
参数说明:dtype=torch.float16适用于大多数 NVIDIA GPU,torch.bfloat16适用于 Ampere 及以上架构。如果你的 GPU 不支持 bf16,强行用会报错或回退到 fp32。
提示:推理时用
torch.no_grad()是必须的,否则中间激活值会保留计算图,显存占用翻倍。这个坑我见过太多次。
5. 避坑与排查:气象大模型本地部署的五个血泪教训
5.1 现象:推理结果全是 NaN
原因:输入数据里有缺测值(通常用 9999 或 1e30 填充),归一化后变成极大值,经过几层网络就溢出成 NaN。
解决:在预处理阶段用xarray的where或fillna把缺测值替换掉。不要用 0 直接填,最好用该变量的气候态均值。排查时可以在模型 forward 里加 hook,看哪一层的输出先出现 NaN。
5.2 现象:模型输出和可视化对不上,场是反的
原因:纬度顺序不一致。训练时用的数据纬度从北到南,推理时读的数据从南到北,或者反过来。
解决:在load_and_align里强制sortby('lat'),并在可视化时用origin='lower'确认方向。这个坑不会报错,只能靠肉眼。
5.3 现象:CUDA out of memory,但 nvidia-smi 显示显存没满
原因:PyTorch 的缓存分配器保留了显存碎片,或者有未释放的中间变量。
解决:在推理循环里用torch.cuda.empty_cache(),但不要每步都调,会拖慢速度。更根本的方法是减小 batch size 或时间步。另外检查是否有变量在循环外累积,比如把每步输出存到 list 里再拼接,改成预分配 tensor。
5.4 现象:推理速度比预期慢十倍
原因:数据加载成了瓶颈。NetCDF 文件在机械硬盘上随机读很慢,或者xarray的 lazy loading 导致每次索引都重新读文件。
解决:先把数据预处理成.npy或.pt格式,推理时直接内存映射。或者用xr.open_dataset(..., chunks={'time': 1})做分块,但要注意 chunk 大小和显存的匹配。
5.5 现象:换了台机器,同样的代码报版本不兼容
原因:气象数据处理栈的依赖树很深,cfgrib依赖eccodes,eccodes又和系统库有关。
解决:用 conda 而不是 pip 装eccodes,或者用项目代码里提供的 Dockerfile。如果没有 Dockerfile,自己写一个,把系统依赖也锁进去。我一般会在environment.yml里把eccodes的版本也写上。
6. 进阶技巧:用 ONNX 导出把推理速度再压一截
当你把本地部署跑通之后,下一步通常是优化推理速度。PyTorch 原生推理在气象大模型上不一定是最快的,因为模型里往往有大量小算子,Python 调用开销占比高。我一般会走 ONNX 导出这条路。
import torch import torch.onnx def export_to_onnx(model, dummy_input, onnx_path): model.eval() torch.onnx.export( model, dummy_input, onnx_path, export_params=True, opset_version=14, do_constant_folding=True, input_names=['input'], output_names=['output'], dynamic_axes={ 'input': {0: 'batch', 1: 'time'}, 'output': {0: 'batch', 1: 'time'} } )逻辑说明:opset_version=14对气象模型里常见的grid_sample和interpolate支持较好。dynamic_axes把 batch 和时间步设为动态,这样同一个 ONNX 文件可以跑不同长度的序列。
参数说明:do_constant_folding=True会把常量表达式提前算掉,减小模型体积。导出后建议用onnxruntime做一次数值对比,确认输出和 PyTorch 的差异在 1e-4 以内。
导出之后用onnxruntime-gpu推理:
import onnxruntime as ort import numpy as np sess = ort.InferenceSession('weather_model.onnx', providers=['CUDAExecutionProvider']) input_name = sess.get_inputs()[0].name output = sess.run(None, {input_name: input_array.astype(np.float32)})逻辑说明:providers里把CUDAExecutionProvider放第一位,CPUExecutionProvider作为回退。如果 CUDA provider 加载失败,onnxruntime 会自动回退到 CPU,但速度会慢很多,所以要在日志里确认实际用的 provider。
参数说明:input_array的 dtype 必须是 float32,ONNX 对 float64 支持不好。如果原始数据是 float64,先转换。
我自己的习惯是:每次导出 ONNX 后,先跑一个单步推理,把 PyTorch 和 ONNX 的输出做np.allclose对比,确认误差在可接受范围再上批量。这个步骤帮我省过好几次“导出后结果完全不对”的后悔药。从那以后我每次换模型结构或者升级 onnxruntime 版本,都会强制走一遍这个对比流程。希望帮到你。
本文还有配套的精品资源,点击获取