2. 开发环境配置:操作系统与基础工具
做 AI 模型的可重复性,第一步其实不是写代码,而是把脚下这块地整平。说白了就是操作系统和基础工具链。我见过太多项目后期翻车,不是因为模型不行,而是环境差异导致的。所以这一章咱们就把地基打牢。
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 的滚动版——你复现的时候别人可能已经更新了好几轮库,版本对不上。
2.2 基础工具链——编译器和构建系统
AI 模型绕不开 C++ 和 CUDA。即使你只用 PyTorch,底层还是调用了这些。我建议一开始就装好:
| 工具 | 推荐版本 | 备注 |
|---|---|---|
| GCC / G++ | 11.4 | Ubuntu 22.04 默认;不要用 9.x 以下 |
| Clang / Clang++ | 16.0 | 配合 LLVM,用于调试、ASAN 检查 |
| CMake | 3.27+ | 构建跨平台,必须 3.20+ 支持 CUDA 架构 |
| Make | 4.3 | 系统自带,但注意不要有旧版残留 |
| Git | 2.40+ | 子模组、lfs 都要支持 |
| CUDA Toolkit | 12.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
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
这张图是我自己画的环境配置逻辑。你看,每个分支最终都汇到“容器化”这步。但容器化不是银弹——你必须确保基础镜像里没有未锁定的依赖。
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 就能跑。
.env-version 文件。一旦版本不一致就亮红灯。2.8 常见陷阱汇总
- glibc 版本不兼容:预编译的 .so 需要 glibc 2.28+,但 CentOS 7 是 2.17。解决:用 Ubuntu 22.04 或使用
manylinux兼容 wheel。 - PATH 污染:不同的 CUDA 版本混在 PATH 里。解决:每个项目用独立的 shell 脚本设置
CUDA_HOME。 - pip 缓存旧包:即便变了 requirements.txt,pip 也可能用缓存。解决:
pip install --no-cache-dir或清理~/.cache/pip。 - conda 通道优先级:conda-forge 和 defaults 混合可能导致包版本冲突。解决:锁定 conda-lock。
- 软链接指向错误:
/usr/local/cuda容易被更新。解决:用绝对路径,不用软链接。
嗯,这一章内容不少。但说到底,就是一句话:把每个环节的版本白纸黑字写下来,用脚本验证,用容器兜底。做到这三点,你的环境配置就不会成为别人复现的绊脚石。