简介:这份资源面向气象学研究者、AI 工程实践者及希望本地跑通气象大模型的学生,提供 Pangu、Fuxi、Fengwu、GraphCast、FourCastNet 等主流模型的本地部署项目代码。压缩包共 2 个文件,以 html 与 inscode 为主,html 承载项目说明与结构入口,inscode 用于代码组织与运行配置,整体约 5KB,轻量便于快速查阅与二次整理。已有 138 人学习下载,说明其在气象大模型本地化部署方向具备一定参考价值。读者可据此了解从虚拟环境创建、依赖库安装、模型接入到预训练权重下载与输入数据对接的完整链路,并获取 GPU 调用失败、版本不匹配等常见问题的排错思路;同时涉及通过 CDS 访问 ERA5 数据并驱动模型开展气象预报的实践路径,适合作为本地部署与调试的起步参考。
1. 本地部署气象大模型:从一张显卡到一次可复现的区域预报
区域气象预报这件事,过去要么依赖超算中心跑数值模式,要么调用外部接口拿现成结果。前者门槛高,后者不可控。本地部署气象大模型,本质是把一个已经训练好的气象预测网络,连同它的权重、推理脚本和依赖环境,完整搬到自己的机器上跑起来。它解决的核心问题是:在断网或数据敏感的场景下,依然能对指定区域做未来数小时到数天的气象要素预测。适合谁?有 GPU 工作站、需要做区域精细化预报验证、或者想把气象预测嵌入自己业务流水线的工程师。这篇文章不讲气象学原理,只讲怎么把这件事从零跑通,以及跑通之后怎么调、怎么排错。
2. 选型与最小可跑通环境:为什么不是随便找个模型就能跑
2.1 气象大模型的三种常见形态与本地部署的匹配度
目前能拿到代码的气象大模型,大致分三类。第一类是纯数据驱动的全球中期预报模型,输入是历史再分析数据,输出是未来若干天的全球格点场,典型结构是 Transformer 或 GraphCast 类的图神经网络。第二类是区域降尺度模型,输入粗分辨率预报场,输出高分辨率局地场,参数量通常小很多。第三类是时序预测模型,只针对单站点或小区域做温度、风速等要素的序列预测。
本地部署最友好的其实是第二类和第三类。第一类全球模型虽然效果惊艳,但推理一次动辄需要几十 GB 显存,普通工作站根本吃不下。我一般会先看模型仓库里的推理脚本,如果它默认加载的是全球 0.25 度格点、时间步长 6 小时、预报 10 天,那基本可以判断这不是一张消费级显卡能碰的。区域模型和站点模型则现实得多,一张 24GB 显存的卡就能跑,甚至有些轻量模型 8GB 也能推理。
选型时还有一个容易被忽略的点:模型是否依赖特定的气象数据格式。很多模型训练时用的是 NetCDF 格式的再分析数据,变量名、维度顺序、单位都有固定约定。如果你手头只有 GRIB 格式或者站点 CSV,就需要先做格式转换。这个转换工作量,往往比部署模型本身还大。
2.2 最小环境搭建:CUDA、PyTorch 与气象库的版本咬合
本地部署翻车最多的地方不是模型本身,而是环境。气象大模型通常依赖 PyTorch,而 PyTorch 又和 CUDA 版本强绑定。更麻烦的是,气象数据处理库如 xarray、netCDF4、cfgrib 对底层库版本也有要求。我踩过的坑是:先装了最新版 PyTorch,结果 cfgrib 编译不过,回头降 PyTorch 又导致 CUDA 不匹配。
稳妥的做法是先确定 CUDA 版本,再选 PyTorch,最后装气象库。假设你的显卡驱动支持 CUDA 12.1,那么:
# 创建独立环境,避免污染系统 Python conda create -n weather-llm python=3.10 -y conda activate weather-llm # 安装 PyTorch,注意 cu121 对应 CUDA 12.1 pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu121 # 安装气象数据处理基础库 pip install xarray netCDF4 cfgrib numpy pandas # 安装模型推理常用依赖 pip install einops timm matplotlib这段命令的关键参数是--index-url,它决定了 PyTorch 从哪个源下载对应 CUDA 版本的二进制包。如果这里写错,后面torch.cuda.is_available()会返回 False,模型加载时直接报错。einops和timm是很多气象 Transformer 模型会用到的张量操作和视觉骨干库,提前装上省得后面缺依赖。
装完后用一段短脚本验证:
import torch import xarray as xr # 检查 CUDA 是否可用,这是后续所有推理的前提 print("CUDA available:", torch.cuda.is_available()) print("CUDA version:", torch.version.cuda) print("Device count:", torch.cuda.device_count()) # 检查 xarray 能否正常读取 NetCDF # 这里用随机数据模拟,实际使用时替换为你的 .nc 文件路径 import numpy as np fake = xr.Dataset({"t2m": (("time", "lat", "lon"), np.random.rand(1, 10, 10))}) print(fake)如果 CUDA available 是 True,xarray 也能正常构造数据集,说明基础环境没问题。注意,这里没有直接读真实文件,因为不同来源的 NetCDF 文件变量名差异很大,先确认库能用,再处理具体文件。
2.3 模型权重与配置文件的放置约定
拿到项目代码后,不要急着运行推理脚本。先看目录结构。常见的气象大模型仓库会有configs/、checkpoints/、data/、scripts/这几个目录。权重文件通常是.pt或.ckpt格式,配置文件是.yaml或.json。
我一般会做三件事。第一,把权重文件放到checkpoints/下,并确认文件名和配置文件里写的路径一致。第二,打开配置文件,找到data_path、stats_path、output_dir这几个字段,改成自己机器上的绝对路径。第三,检查配置文件里的device字段,如果是cuda:0就保持,如果是cpu就改成cuda:0,否则推理速度会让你怀疑人生。
有一个细节:很多模型会附带一个stats.npz或normalization.json,里面存的是训练数据的均值和标准差。推理前必须用同样的统计量做归一化,否则输出结果完全不可信。这个文件如果缺失,模型基本等于废了,因为气象变量的量级差异很大,不做归一化,网络根本没法正常工作。
3. 推理脚本拆解:从加载权重到输出预报场
3.1 加载模型与权重的标准流程
假设项目里有一个inference.py,但直接运行往往报错。我习惯先把它拆开,逐段理解。下面是一个典型的气象大模型加载流程:
import torch import yaml from model import WeatherModel # 假设模型定义在 model.py 中 # 读取配置文件 with open("configs/inference.yaml", "r") as f: cfg = yaml.safe_load(f) # 实例化模型,注意参数要和训练时一致 model = WeatherModel( in_channels=cfg["in_channels"], # 输入变量数,如温度、风速、气压等 out_channels=cfg["out_channels"], # 输出变量数 hidden_dim=cfg["hidden_dim"], # 隐藏层维度,影响模型容量 num_layers=cfg["num_layers"], # 层数,越深越吃显存 ) # 加载权重,map_location 确保在 GPU 上加载 state_dict = torch.load(cfg["checkpoint_path"], map_location="cuda:0") model.load_state_dict(state_dict) model = model.to("cuda:0") model.eval() # 切换到推理模式,关闭 dropout 和 batchnorm 更新 print("Model loaded, params:", sum(p.numel() for p in model.parameters()))这里的关键是model.eval()。如果忘了这行,dropout 会随机丢弃神经元,每次推理结果都不一样,你会以为模型有问题,其实是模式没切对。另一个关键是map_location,如果权重是在多卡上保存的,直接加载可能会因为设备编号不匹配而报错,指定cuda:0能解决大部分问题。
3.2 输入数据的预处理:格点对齐与时间维构造
气象大模型的输入不是随便一张图,而是有严格维度顺序的张量。常见格式是(batch, time, channel, lat, lon)或(batch, channel, time, lat, lon)。顺序搞错,模型不会报错,但输出会完全离谱。
import xarray as xr import numpy as np # 读取再分析数据,假设是 NetCDF 格式 ds = xr.open_dataset("data/era5_subset.nc") # 选择需要的变量,注意变量名要和训练时一致 # 常见变量:t2m(2米温度)、u10/v10(10米风速)、msl(海平面气压) variables = ["t2m", "u10", "v10", "msl"] data = ds[variables].values # 形状为 (time, channel, lat, lon) # 归一化,使用训练时的统计量 stats = np.load("checkpoints/stats.npz") mean = stats["mean"] # 形状 (channel,) std = stats["std"] data = (data - mean[:, None, None]) / std[:, None, None] # 构造输入张量,取最近 2 个时间步作为输入 # 假设模型要求输入时间长度为 2 input_tensor = torch.tensor(data[-2:], dtype=torch.float32) input_tensor = input_tensor.unsqueeze(0).to("cuda:0") # 增加 batch 维 print("Input shape:", input_tensor.shape)这段代码里,mean[:, None, None]的广播操作是为了让每个通道减去自己的均值。如果直接减一个标量,不同量级的变量会被错误缩放。data[-2:]取最后两个时间步,是因为很多模型用过去两个时刻预测未来一个或多个时刻。具体取几个时间步,要看配置文件里的input_length。
3.3 推理与输出后处理:把张量变回气象场
模型输出的是归一化后的张量,必须反归一化才能得到物理量。
# 推理,关闭梯度计算节省显存 with torch.no_grad(): output = model(input_tensor) # 形状 (batch, out_channels, lat, lon) # 反归一化 output = output.squeeze(0).cpu().numpy() # 去掉 batch 维 output = output * std_out[:, None, None] + mean_out[:, None, None] # 保存为 NetCDF,方便后续分析和可视化 out_ds = xr.Dataset( {var: (("lat", "lon"), output[i]) for i, var in enumerate(variables)}, coords={"lat": ds.lat.values, "lon": ds.lon.values}, ) out_ds.to_netcdf("output/forecast.nc") print("Forecast saved.")反归一化用的std_out和mean_out必须和训练时输出变量的统计量一致。如果项目只提供了一个stats.npz,里面通常同时包含输入和输出的统计量,注意区分键名。保存为 NetCDF 是为了后续能用 xarray 直接做差值分析和画图,比存成 CSV 方便得多。
4. 避坑与排查:本地部署气象大模型最常见的五类翻车
4.1 显存溢出:现象、原因与解决
现象:运行推理脚本时抛出CUDA out of memory,即使把 batch size 设为 1 也一样。
原因:气象大模型的输入张量往往很大。一个全球 0.25 度格点、70 个变量、2 个时间步的输入,展开后就是几千万个浮点数。如果模型中间层还做了上采样或注意力计算,显存占用会成倍增加。
解决:先看模型是否支持混合精度推理。在torch.no_grad()外面套一层torch.cuda.amp.autocast(),能把显存占用降一半左右。如果还不行,就考虑把输入裁剪到只保留你关心的区域,而不是全球。很多区域模型本身就只接受局地输入,用全球数据反而会出错。
4.2 输出全是 NaN:归一化统计量缺失或错位
现象:推理结果保存后打开一看,全是 NaN 或者数值大得离谱。
原因:最常见的是归一化统计量没加载对。比如stats.npz里的mean是输入变量的均值,但代码里错误地用它去反归一化输出。另一种情况是变量顺序不一致,训练时通道顺序是[t2m, u10, v10, msl],推理时变成了[msl, t2m, v10, u10],归一化自然全乱。
解决:打印stats.npz的所有键,确认输入和输出统计量是分开的。然后对照配置文件里的variables列表,确保数据读取时的变量顺序和训练时完全一致。如果找不到统计量文件,可以尝试用输入数据自身的均值和标准差做近似,但效果会打折扣。
4.3 推理速度极慢:没有切到 eval 模式或误用 CPU
现象:一次推理要几分钟甚至更久,GPU 利用率却很低。
原因:忘了写model.eval(),导致 dropout 和 batchnorm 在推理时仍然活跃,计算量增加。或者模型加载时map_location写成了cpu,整个推理都在 CPU 上跑。
解决:在加载权重后立刻加model.eval()和model.to("cuda:0")。用nvidia-smi看 GPU 利用率,如果低于 30%,基本可以确定模型没在 GPU 上跑。另外,检查数据加载部分有没有.cpu()调用,把数据留在 GPU 上能省不少时间。
4.4 时间维对不上:输入长度与模型预期不符
现象:模型前向传播时报维度错误,提示Expected input time dimension 2, got 1。
原因:配置文件里写的input_length是 2,但数据读取时只取了 1 个时间步。或者数据本身的时间分辨率不对,模型训练时用的是 6 小时间隔,你给的是 1 小时间隔,时间步数量对不上。
解决:打开配置文件,找到input_length或history_length字段,确认具体数值。然后在数据预处理代码里,用data[-input_length:]来取输入。如果数据时间分辨率不同,需要先做时间重采样,用 xarray 的resample方法统一到模型要求的间隔。
4.5 输出地理坐标错位:lat/lon 顺序颠倒
现象:预报场保存后画图,发现南北颠倒或者东西镜像。
原因:NetCDF 里 lat 通常是从小到大排列,但有些模型训练时用的是从大到小。如果推理时直接沿用原始 lat 数组,而模型输出是按训练时的顺序排列的,就会错位。
解决:检查训练数据里 lat 的排列顺序。如果模型输出和原始 lat 顺序不一致,在保存前用output[::-1, :]翻转纬度维。更稳妥的做法是,在配置文件里找lat_order或类似字段,按模型要求调整。
5. 进阶技巧:用滑动窗口做连续预报与结果验证
5.1 滑动窗口推理:把单次预报串成时间序列
单次推理只能得到一个时刻的预报。要得到连续多个时刻,需要用滑动窗口:把上一次的输出作为下一次输入的一部分。这里有一个关键选择:是只用模型自己的输出做自回归,还是每次都用真实观测做修正。前者误差会累积,后者需要持续有观测数据。
# 滑动窗口推理,预报未来 4 个时刻 forecast_steps = 4 current_input = input_tensor.clone() # 初始输入,形状 (1, time, channel, lat, lon) forecasts = [] for step in range(forecast_steps): with torch.no_grad(): pred = model(current_input) # 预测下一个时刻 forecasts.append(pred.cpu().numpy()) # 把预测结果拼接到输入中,去掉最老的时间步 # 注意:这里假设模型输出通道和输入通道一致 pred_expanded = pred.unsqueeze(1) # 增加时间维 current_input = torch.cat([current_input[:, 1:], pred_expanded], dim=1) print("Forecast steps:", len(forecasts))这段代码里,current_input[:, 1:]去掉了最早的时间步,pred_expanded补到末尾,保持输入时间长度不变。如果模型输出通道和输入通道不一致,比如输出只有温度,输入有多个变量,就需要单独处理,不能直接拼接。
5.2 用再分析数据做快速验证:三个必看的指标
预报结果出来后,怎么判断它靠不靠谱?我一般会算三个指标:均方根误差、异常相关系数和偏差。均方根误差看整体误差大小,异常相关系数看空间分布是否合理,偏差看有没有系统性高估或低估。
import numpy as np # 假设 truth 是真实观测场,forecast 是预报场,形状均为 (lat, lon) def rmse(truth, forecast): return np.sqrt(np.mean((truth - forecast) ** 2)) def acc(truth, forecast, climatology): # 异常相关系数,climatology 是气候态 t_anom = truth - climatology f_anom = forecast - climatology return np.corrcoef(t_anom.flatten(), f_anom.flatten())[0, 1] def bias(truth, forecast): return np.mean(forecast - truth) # 示例用法 # truth = xr.open_dataset("data/truth.nc")["t2m"].values # forecast = xr.open_dataset("output/forecast.nc")["t2m"].values # clim = xr.open_dataset("data/climatology.nc")["t2m"].values # print("RMSE:", rmse(truth, forecast)) # print("ACC:", acc(truth, forecast, clim)) # print("Bias:", bias(truth, forecast))均方根误差对极端值敏感,如果某个区域误差特别大,会被放大。异常相关系数需要气候态数据,如果没有,可以先用时间平均代替。偏差为正说明预报偏高,为负说明偏低。这三个指标不用同时追求最优,先看偏差是否在可接受范围内,再看异常相关系数是否超过 0.5,最后才看均方根误差。
5.3 一个容易被忽略的细节:输出变量的单位
气象数据的单位经常被搞混。温度可能是摄氏度也可能是开尔文,风速可能是米每秒也可能是节。模型训练时用的单位,推理输出必须保持一致。我遇到过一种情况:模型输出的是开尔文,但可视化时按摄氏度画,结果整个图看起来像在北极。检查单位的方法很简单,看配置文件里有没有units字段,或者看训练数据 NetCDF 文件里变量的units属性。如果没有,就找一份同来源的数据对比数值范围,温度在 250 到 320 之间基本是开尔文,在 -20 到 50 之间基本是摄氏度。
本地部署气象大模型这件事,最耗时间的从来不是写推理代码,而是对齐数据格式和排查环境问题。我的习惯是每跑通一个环节就存一个检查点,比如数据预处理完存一份,模型加载完打印一次参数量,推理完先保存原始张量再后处理。这样一旦后面出错,能快速定位是哪一步的问题。希望帮到你。
本文还有配套的精品资源,点击获取