2. 开发环境配置:操作系统与基础工具

做 AI 模型的可重复性,第一步其实不是写代码,而是把脚下这块地整平。说白了就是操作系统和基础工具链。我见过太多项目后期翻车,不是因为模型不行,而是环境差异导致的。所以这一章咱们就把地基打牢。

核心原则:操作系统、编译器、包管理器、Python 版本、CUDA 版本必须锁定并文档化。任何一条链上的松动,都可能让别人的复现变成灾难。

2.1 操作系统选型——Linux 是唯一答案

我知道有人还在 macOS 上跑模型,Windows 上也不是不行。但如果你想做可重复性发布,Linux 是唯一选择。原因很简单:Linux 下的依赖管理最透明,容器化最成熟。我个人习惯用 Ubuntu 22.04 LTS 或 Rocky Linux 9。为什么?

  • Ubuntu:社区活跃,apt 包新,文档全。适合快速原型。
  • Rocky Linux:完全兼容 RHEL,稳定到发指。适合生产部署。

如果你问我个人偏好,我倾向于 Ubuntu 22.04。因为它的 glibc 版本够新(2.35),能兼容大多数预编译 wheel。但注意,尽量别用 Arch 或 Fedora 的滚动版——你复现的时候别人可能已经更新了好几轮库,版本对不上。

曾经有个教训:某团队用 Ubuntu 20.04 开发,但发布到 Docker 时用了 Ubuntu 22.04 的基础镜像。结果 OpenBLAS 的线程冲突导致推理结果不稳定。后来我们在 README 里明确写了“Ubuntu 22.04 + specific apt snapshot date”,问题才解决。所以,请记录系统的完整版本号,最好连 apt 源快照日期也记下。

2.2 基础工具链——编译器和构建系统

AI 模型绕不开 C++ 和 CUDA。即使你只用 PyTorch,底层还是调用了这些。我建议一开始就装好:

工具推荐版本备注
GCC / G++11.4Ubuntu 22.04 默认;不要用 9.x 以下
Clang / Clang++16.0配合 LLVM,用于调试、ASAN 检查
CMake3.27+构建跨平台,必须 3.20+ 支持 CUDA 架构
Make4.3系统自带,但注意不要有旧版残留
Git2.40+子模组、lfs 都要支持
CUDA Toolkit12.4 / 12.1取决于 PyTorch 版本

安装命令很简单(Ubuntu 22.04):

sudo apt update
sudo apt install build-essential cmake git curl wget
# 检查版本
gcc --version
cmake --version
git --version

为什么强调版本?因为不同 GCC 对 C++17 支持有细微差异。我遇到过同事用了 GCC 7.5 编译出的 .so,在 GCC 11 环境下跑出随机 segfault。你想想这多难查。

个人技巧:用 update-alternatives 管理多个 GCC 版本。这样可以在不同项目间快速切换:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100

2.3 Python 版本管理——没人想踩 pyenv 的坑

Python 版本是复现的命门。千万不要用系统自带的 Python 3.10 直接 pip install。我强烈推荐 pyenv 或 conda。我自己用 pyenv + virtualenv 组合,因为轻量且和 repo 内的 .python-version 文件配合完美。

先装 pyenv:

curl https://pyenv.run | bash
# 然后加入 .bashrc
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

接下来安装 Python 3.10.12:

pyenv install 3.10.12
pyenv global 3.10.12
python --version  # 确认

注意,pyenv 依赖一些编译库。万一报错,安装:

sudo apt install libssl-dev zlib1g-dev libbz2-dev \
  libreadline-dev libsqlite3-dev llvm libncurses5-dev \
  xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

为什么是 3.10.12?因为 PyTorch 2.3 对 3.11 兼容性刚完善,3.12 还有些第三方 wheel 缺失。我建议锁定一个被广泛测试的微版本。

重要:在项目根目录创建 .python-version 文件,内容只写 3.10.12。然后 pyenv 自动切换。配合 requirements.txt 锁定 pip 包版本,复现性就有了。

2.4 CUDA 与 cuDNN——踩坑重灾区

GPU 环境是 AI 复现中最容易出妖的地方。我踩过的坑包括:

  • CUDA 版本和 nvidia-driver 不兼容,跑不了 torch.cuda.is_available()
  • cuDNN 版本和 CUDA 不匹配,训练 loss 直接 NaN
  • 一个机器多个 CUDA 环境,链接错了 libcudart.so

我的做法是:不装系统级 CUDA,全部用 conda 或 pip 的 NVIDIA 镜像。但这有时不够灵活。如果你需要编译自定义 CUDA kernel,还是得装 Toolkit。

推荐安装方式:

# 安装 nvidia-driver-545(或适配你显卡的最新驱动)
sudo apt install nvidia-driver-545
# 用 runfile 安装 CUDA 12.4
wget https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run
sudo sh cuda_12.4.0_550.54.14_linux.run --toolkit --samples --silent

然后设置环境变量:

export PATH=/usr/local/cuda-12.4/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH
曾经出过事故:项目里有人把 CUDA 装到了 /usr/local/cuda 软链接,结果另一个团队装 CUDA 12.0 把软链接改写了。所有依赖 cuBLAS 11.x 的代码全线崩溃。自此以后,我坚持每个项目用独立的 CUDA 根目录,并且不共用软链接。

2.5 包管理器与虚拟环境——锁定一切

Python 包锁定,我推荐 pip-tools 或 poetry。简单粗暴就用 pip freeze,但颗粒度不够。我习惯用 pip-compile 生成 requirements.txt:

# 在 setup.py 或 pyproject.toml 中只写顶层依赖
pip install pip-tools
pip-compile --output-file=requirements.txt pyproject.toml
# 得到完整锁定的 requirements.txt
pip-sync

另外,conda-lock 也值得了解,它能锁定 conda 通道和包版本。

对于 C++ 依赖,建议用 CMake 的 FetchContent 或 vcpkg,不要手动下载源码。锁定 commit hash:

FetchContent_Declare(
  nlohmann_json
  GIT_REPOSITORY https://github.com/nlohmann/json
  GIT_TAG v3.11.2
)
FetchContent_MakeAvailable(nlohmann_json)

2.6 容器化——Docker / Podman 是复现的最终保险

不管你本地环境配得多完美,别人一签出你的代码,还是可能因为系统差异出错。所以我总是要求项目附带 Dockerfile。甚至更激进:用 docker compose 把数据集、代码、模型权重挂载好。

一个典型的最小 Dockerfile:

FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04

RUN apt update && apt install -y python3.10 python3-pip git

WORKDIR /workspace
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
CMD ["python", "train.py"]

但注意,基础镜像的 cuda:12.4.0-runtime 不含 cuDNN。如果需要,改为 cuda:12.4.0-cudnn-runtime-ubuntu22.04。我建议把镜像的 digest 也记录到文档里,因为同名 tag 的 digest 可能被更新。

可复现的黄金组合:
1. 操作系统:Ubuntu 22.04 LTS
2. 编译器:GCC 11.4 + CMake 3.27
3. Python:3.10.12 (pyenv)
4. CUDA:12.4.0 + cuDNN 8.9.7
5. 依赖管理:pip-tools (requirements.txt)
6. 容器化:Docker + docker compose + image digest
开发环境可复现配置逻辑 锁定 & 文档化 Linux 发行版 + 快照 GCC + CMake + Git pyenv + pip-tools CUDA + cuDNN 固化 FetchContent / lock 文件 .python-version Docker + 镜像 Digest 每个节点都需要记录版本号

这张图是我自己画的环境配置逻辑。你看,每个分支最终都汇到“容器化”这步。但容器化不是银弹——你必须确保基础镜像里没有未锁定的依赖。

2.7 快速验证:环境一致性检查

配置完后,我习惯跑一个脚本,打印所有关键组件版本:

#!/bin/bash
echo "=== System ==="
cat /etc/os-release | grep -E "^(PRETTY_NAME|VERSION_ID)"
echo ""
echo "=== Compiler ==="
gcc --version | head -1
cmake --version | head -1
echo ""
echo "=== Python ==="
python --version
pyenv --version | head -1
echo ""
echo "=== CUDA ==="
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader
nvcc --version | tail -1
echo ""
echo "=== cuDNN ==="
cat /usr/include/cudnn_version.h | grep CUDNN_MAJOR -A 2
echo ""
echo "=== Git ==="
git --version
echo "=== Docker ==="
docker --version

把这个脚本保存为 check_env.sh,放到仓库根目录。别人一 clone 就能跑。

进阶:可以写 GitHub Actions 或 GitLab CI,每次 push 自动运行检查,并对比分支的 .env-version 文件。一旦版本不一致就亮红灯。

2.8 常见陷阱汇总

  1. glibc 版本不兼容:预编译的 .so 需要 glibc 2.28+,但 CentOS 7 是 2.17。解决:用 Ubuntu 22.04 或使用 manylinux 兼容 wheel。
  2. PATH 污染:不同的 CUDA 版本混在 PATH 里。解决:每个项目用独立的 shell 脚本设置 CUDA_HOME。
  3. pip 缓存旧包:即便变了 requirements.txt,pip 也可能用缓存。解决:pip install --no-cache-dir 或清理 ~/.cache/pip。
  4. conda 通道优先级:conda-forge 和 defaults 混合可能导致包版本冲突。解决:锁定 conda-lock。
  5. 软链接指向错误:/usr/local/cuda 容易被更新。解决:用绝对路径,不用软链接。

嗯,这一章内容不少。但说到底,就是一句话:把每个环节的版本白纸黑字写下来,用脚本验证,用容器兜底。做到这三点,你的环境配置就不会成为别人复现的绊脚石。