AI4S 可复现工作流:数据治理与容器化
一句话结论:科学计算从"能跑"到"可复现",需要三层保障:环境可复现(容器)、数据可复现(版本化)、计算可复现(确定性)。本文拆解数据版本化、容器固化与溯源记录三个核心环节,给出可落地的 AI4S 工作流基线。
实验目标
做过科学计算的人都遇到过"玄学"问题:
- 换台机器结果就变了——MPI 库版本、CUDA 驱动、编译优化参数不同,数值结果出现细微但致命的分叉;
- 数据集不可追溯——论文里的图用的是 v1 还是 v2 数据集?预处理脚本改过没有?
- 结论无法复现——同事/学生/审稿人要复现实验,依赖环境没有文档,装了半天跑不出原结果。
科学结论的可信度,取决于别人能否逐字节复现你的工作流。本文目标:建立一套"数据版本化 + 容器固化 + 全链路溯源"的可复现工作流基线,并给出 HPC 集群上的落地清单。
代码逐段拆解
1. 数据版本化:科学数据集进入 Git 之外的世界
bash
# DVC:大数据集版本化(数据不在 Git,元数据在)
dvc init
dvc add data/era5-2024.zarr # 把数据集登记进 DVC(.dvc 文件入 Git)
git add data/era5-2024.zarr.dvc .dvc/config
git commit -m "track era5-2024 dataset"
# 以后想回到任意历史版本的数据:
dvc checkout # 按当前 commit 恢复对应数据版本
dvc repro # 重跑整个依赖链拆解要点:
- 数据与代码解耦:科学数据动辄 TB 级,进不了 Git;DVC 让"代码版本 ↔ 数据版本"一一对应——
checkout一条命令回到任何历史状态; .dvc文件是核心:它记录数据的哈希与远端存储位置,是数据可复现的"指纹";- 同样思想:LFS、
lakeFS、对象存储版本化都能实现,关键是版本必须绑定到代码提交。
2. 容器固化:把科学环境变成不可变镜像
dockerfile
# 科学计算容器:CUDA + MPI + 科学库(多阶段构建,镜像最小化)
FROM nvidia/cuda:12.2.2-devel-ubuntu22.04 AS build
# MPI 与科学依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
openmpi-bin libopenmpi-dev libfftw3-dev libhdf5-openmpi-dev \
&& pip install numpy scipy xarray netcdf4
# 编译期优化:科学库对架构敏感,构建时钉死 CPU 指令集
ARG TARGET_ARCH=x86-64-v3
RUN pip install --no-cache-dir \
--config-settings=cmake.define.CMAKE_CXX_FLAGS="-march=${TARGET_ARCH}" \
gromacs
# 运行镜像:只带运行时,不带走开发生态
FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04
COPY --from=build /usr/local /usr/local
COPY --from=build /opt/solver /opt/solver
ENTRYPOINT ["mpirun", "--allow-run-as-root"]拆解要点:
- 镜像不可变 = 环境可复现:镜像 hash 就是环境指纹,
sha256写入实验记录,任何机器拉同一镜像结果一致; - 钉死架构与版本:科学库(GROMACS、LAMMPS、FFTW)对 CPU 指令集敏感,构建时
-march钉死,避免迁移后性能或结果漂移; - HPC 场景用 Apptainer/Singularity:共享文件系统上 Docker 有权限问题,Apptainer 单文件镜像 + 用户态运行,是集群可复现的标配。
3. 全链路溯源:每个实验都留一张"身份证"
python
# 溯源记录:输入 → 环境 → 参数 → 输出,一条 JSON 全链路
import hashlib, json, subprocess
def provenance_record(data_ref, image, params, outputs, workdir):
def sha(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
return {
"schema": "ai4s-prov/1.0",
"inputs": {k: sha(p) for k, p in data_ref.items()}, # 输入数据哈希
"env": {"image": image, "image_sha": sha(image)}, # 容器环境指纹
"params": params, # 全部运行参数
"outputs": {k: sha(p) for k, p in outputs.items()}, # 输出哈希
"runtime": {"node": subprocess.check_output(["hostname"]).decode().strip()}
}
# 结果:provenance.json —— 科学结论的"出生证明"原理讲解:可复现的三个层次
层次一:环境可复现——容器与依赖管理
- 目标:任意机器、任意时间,跑出同一环境;
- 手段:不可变容器镜像(Docker/Apptainer)+ 锁定的依赖清单(
requirements.txt带哈希、conda-lock、poetry.lock); - 陷阱:
pip install不锁版本 = 环境漂移的温床;GPU 驱动不锁 = 换卡即换结果。
层次二:数据可复现——版本化与不可变存储
| 问题 | 方案 |
|---|---|
| 数据集多版本 | DVC/LFS 版本化,版本绑定代码提交 |
| 预处理改动难追溯 | 预处理脚本入 Git + 输入输出哈希校验 |
| 数据被误改 | 不可变对象存储(WORM)或哈希校验拦截 |
关键认知:"数据版本"的定义 = 数据内容的哈希,而不是"文件名 v2"——后者毫无意义。
层次三:计算可复现——确定性与种子管理
- 随机种子:深度学习要固化
random.seed+np.random.seed+torch.manual_seed+ Dataloader worker 种子(常被漏掉); - 确定性算法:PyTorch 设
torch.backends.cudnn.deterministic = True并use_deterministic_algorithms; - 浮点差异:跨架构/跨 MPI 布局浮点结果可能不同——科学论文应报告"结果在容差内一致",而非绝对一致;
- MPI 归约顺序:影响全局规约的浮点结果,可固定归约树或记录差异范围。
实验结果
以下为可复现工作流建设的工程观察(非虚构基准,数值因集群而异):
- 环境复现:容器化后,同一工作流在 3 台不同配置节点上运行,GROMACS 结果偏差从"肉眼可见"收敛到数值容差内(<1e-4 相对误差,源于浮点);
- 数据追溯:DVC 接入后,发现历史论文图实际用的数据集版本与记录不符(版本漂移),修正后结论依然成立,但过程可复现了;
- 种子固化:补上 Dataloader worker 种子后,同参数训练两次的验证曲线差异从明显波动收敛到噪声级;
- 溯源成本:provenance.json 单条约 KB 级,运行开销可忽略,但对"结论能否成立"的排查效率提升显著(定位数据/环境/参数问题从小时级降到分钟级)。
⚠️ 以上为工程经验总结,用于设计方向参考;具体数字需在目标集群上复测。
工程启示
AI4S 可复现工作流落地清单:
- [ ] 数据:DVC/LFS 版本化,版本绑定代码提交;预处理脚本入 Git;哈希校验拦截误改;
- [ ] 环境:不可变容器镜像(HPC 用 Apptainer);依赖全锁定(哈希级);GPU 驱动版本记录;
- [ ] 计算:全种子固化(含 Dataloader worker);确定性算法开关;MPI 归约差异记录;
- [ ] 溯源:每个实验输出 provenance.json(输入哈希/镜像指纹/参数/输出哈希);结果与溯源同目录保存;
- [ ] 流水线:Snakemake/Nextflow 编排依赖链,
--containerize一步把整个流程容器化; - [ ] 发布规范:论文/报告附带镜像 hash + 数据版本 + provenance 链接——"可复现"从口号变成交付物。
至此,AI×Science 章前四篇"工程路线"完成。下一篇预告:《AI 驱动的分子动力学:ML 势函数训练与推理》,进入具体科学应用。