背景

这次维护的目标是升级超算 GPU 节点 gpu2 的 NVIDIA 驱动,以满足某些 CUDA 二进制库对新版 NVIDIA kernel driver 的要求。

原始环境大致如下:

系统:CentOS 7
原内核:3.10.0-957.el7.x86_64
原驱动:NVIDIA 535.154.05
目标驱动:NVIDIA 595.80
GPU:RTX 3090 × 8
Slurm 分区:gpu
维护节点:gpu2
管理节点:admin1
共享目录:/public,通过 NFS 挂载
存储网络:ib0,12.12.12.0/24

这次升级过程中踩到的坑比较典型:

新驱动需要新内核,新内核需要新 GCC,驱动编译又牵涉到 RDMA/IB 模块,最后还因为存储网络没起来导致共享 home 丢失。

最终成功组合是:

Kernel: 5.4.278-1.el7.elrepo.x86_64
NVIDIA Driver: 595.80
CUDA reported by driver: 13.2
Compiler: devtoolset-9 gcc 9.3.1
Install option: --no-peermem
IB/IPoIB: mlx5_core + mlx5_ib + ib_ipoib
/public: 12.12.12.250:/public nfs4
Slurm: gpu2 idle,可正常提交 GPU 作业

维护前准备:先保护调度环境

升级 GPU 驱动和内核前,第一步不是安装软件,而是保护正在运行的作业和 Slurm 调度状态。

先查看 gpu2 上是否有作业:

squeue -w gpu2

如果有作业,需要等待结束或由管理员确认后取消:

sudo /opt/gridview/slurm/bin/scancel <jobid>

然后将节点置为维护状态:

sudo /opt/gridview/slurm/bin/scontrol update NodeName=gpu2 State=DRAIN Reason="kernel/driver upgrade"

确认节点进入 drain:

/opt/gridview/slurm/bin/sinfo -N -l | grep gpu2

这一点非常重要。不要在节点仍可接收作业时升级内核和驱动。


为什么不能只用 Docker 或虚拟机解决

一开始也考虑过 Docker 或虚拟机。结论是:

Docker 不能绕过宿主机 NVIDIA kernel driver。

容器可以换 CUDA runtime、用户态库、PyTorch 版本,但实际和 GPU 通信的内核驱动仍然来自宿主机。
如果某个二进制库要求宿主机驱动版本达到 595+,那么容器内装再新的 CUDA runtime 也无效。

虚拟机 GPU passthrough 也不是优先方案,因为它要求 IOMMU 正常启用,还会牵涉 Slurm/GRES、GPU 隔离、性能和运维复杂度。最终选择直接升级 gpu2 宿主机内核和驱动。


内核选择:不要盲目追新

最开始尝试过 ELRepo 的 kernel-ml 6.9.7。这个内核可以正常启动,但后续编译 NVIDIA 595.80 时遇到 Module.symvers parse errornvidia-uvm 构建失败。

最终选择了更保守的 ELRepo kernel-lt 5.4.278

原因是:

  1. NVIDIA 595 需要比 CentOS 7 默认 3.10 更新的内核;
  2. 但超算节点不需要最新主线内核;
  3. CentOS 7 老用户态和太新的 6.x 内核组合容易触发兼容性问题;
  4. kernel-lt 更适合作为生产环境折中方案。

admin1 上下载:

mkdir -p /tmp/gpu2-kernel-lt
cd /tmp/gpu2-kernel-lt

curl -L -O http://mirrors.coreix.net/elrepo-archive-archive/kernel/el7/x86_64/RPMS/kernel-lt-5.4.278-1.el7.elrepo.x86_64.rpm
curl -L -O http://mirrors.coreix.net/elrepo-archive-archive/kernel/el7/x86_64/RPMS/kernel-lt-devel-5.4.278-1.el7.elrepo.x86_64.rpm

rpm -K kernel-lt-5.4.278-1.el7.elrepo.x86_64.rpm
rpm -K kernel-lt-devel-5.4.278-1.el7.elrepo.x86_64.rpm

传到 gpu2

ssh gpu2 'mkdir -p /tmp/gpu2-kernel-lt'
scp /tmp/gpu2-kernel-lt/*.rpm gpu2:/tmp/gpu2-kernel-lt/

gpu2 上安装:

cd /tmp/gpu2-kernel-lt

sudo yum localinstall -y \
kernel-lt-5.4.278-1.el7.elrepo.x86_64.rpm \
kernel-lt-devel-5.4.278-1.el7.elrepo.x86_64.rpm

确认 GRUB 条目:

sudo grubby --info=ALL | egrep -i '^(index|kernel|title)='

当时看到:

index=0
kernel=/boot/vmlinuz-5.4.278-1.el7.elrepo.x86_64

index=1
kernel=/boot/vmlinuz-6.9.7-1.el7.elrepo.x86_64

index=2
kernel=/boot/vmlinuz-3.10.0-957.el7.x86_64

为了保留回退能力,先把默认内核仍设为旧的 3.10,只让下一次临时启动 5.4:

sudo grubby --set-default /boot/vmlinuz-3.10.0-957.el7.x86_64
sudo grub2-reboot 0
sudo grub2-editenv list
sudo reboot

重启后验证:

uname -r
cat /proc/version

输出为:

5.4.278-1.el7.elrepo.x86_64
gcc version 9.3.1

GCC 工具链:CentOS 7 默认 GCC 太旧

NVIDIA 595 在新内核上编译内核模块时,CentOS 7 默认 GCC 4.8.5 不够用。

之前在 6.9 内核上已经遇到过:

gcc: error: unrecognized command line option '-fpatchable-function-entry=16,16'
gcc: error: unrecognized command line option '-mrecord-mcount'
gcc: error: unrecognized command line option '-Wimplicit-fallthrough=5'

因此需要安装 devtoolset-9

admin1 上下载 devtoolset-9 相关 RPM 后,传到 gpu2 并离线安装:

cd /tmp/gpu2-devtoolset9
sudo yum localinstall -y *.rpm

验证:

source /opt/rh/devtoolset-9/enable
gcc --version
ld --version | head -1

应看到:

gcc (GCC) 9.3.1
GNU ld version 2.32

后续运行 NVIDIA 安装器时显式指定 GCC:

sudo env \
PATH=/opt/rh/devtoolset-9/root/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
CC=/opt/rh/devtoolset-9/root/usr/bin/gcc \
./NVIDIA-Linux-x86_64-595.80.run --concurrency-level=1

NVIDIA 595.80 安装:最终使用 --no-peermem

5.4.278 内核下安装 NVIDIA 595.80。

先确认 nouveau 没有加载:

lsmod | grep nouveau

然后安装:

cd /tmp
sudo rm -rf /tmp/makeself.*

sudo env \
PATH=/opt/rh/devtoolset-9/root/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
CC=/opt/rh/devtoolset-9/root/usr/bin/gcc \
./NVIDIA-Linux-x86_64-595.80.run --concurrency-level=1 --no-peermem

安装器选项:

Kernel module type: NVIDIA Proprietary
32-bit compatibility libraries: No
nvidia-xconfig: No

这里使用了 --no-peermem。原因是 nvidia-peermem 编译失败,但它不是普通 CUDA 必需模块,而是 GPUDirect RDMA 相关模块。

普通 CUDA 更关键的是:

nvidia
nvidia_uvm
nvidia_modeset

安装成功后不要立刻随意重启。因为之前默认内核仍是 3.10,需要再次指定下一次进入 5.4:

sudo grubby --set-default /boot/vmlinuz-3.10.0-957.el7.x86_64
sudo grub2-reboot 0
sudo reboot

重启后验证:

uname -r
nvidia-smi
lsmod | grep -E 'nvidia|uvm'

结果:

uname -r -> 5.4.278-1.el7.elrepo.x86_64
nvidia-smi -> Driver Version: 595.80
lsmod -> nvidia_uvm 已加载

确认成功后,将 5.4 固定为默认内核:

sudo grubby --set-default /boot/vmlinuz-5.4.278-1.el7.elrepo.x86_64
sudo grubby --default-kernel

意外问题:共享 home 丢失

驱动升级成功后,发现 SSH 到 gpu2 时 starship、zsh、个人配置都没了,登录提示也变成普通 bash:

[dingrui@gpu2 ~]$

同时登录时曾出现:

Creating home directory for dingrui.

检查后发现:

df -hT $HOME

gpu2 上显示:

/dev/sda3 ext4 ... /

而在正常的 gpu1 上:

istorage1:/public nfs4 ... /public

这说明 gpu2/public 没有挂载 NFS,共享 home 丢失,系统在本地根分区下临时创建了一个“影子 home”。

这时不要复制配置文件来修。正确做法是恢复 /public 挂载。


根因:InfiniBand/IPoIB 存储网络没起来

gpu2 上检查:

ping -c 3 istorage1
ping -c 3 12.12.12.250
getent hosts istorage1

结果:

getent hosts istorage1 -> 12.12.12.250
ping -> Network is unreachable

说明 DNS 没问题,但到存储网段没有路由。

对比 gpu1

ip -br addr
ip route

gpu1 正常:

ib0 UP 12.12.12.201/24
12.12.12.0/24 dev ib0

gpu2 异常:

没有 ib0
没有 12.12.12.0/24 路由

检查硬件:

lspci | grep -Ei 'mellanox|infiniband|network|ethernet'

看到:

Infiniband controller: Mellanox Technologies MT28908 Family [ConnectX-6]

说明硬件在,但 IPoIB 接口没起来。

加载模块:

sudo modprobe mlx5_core
sudo modprobe mlx5_ib
sudo modprobe ib_ipoib

然后 ib0 出现,但状态为 DOWN:

ip -br addr

启动接口:

sudo ifup ib0

验证:

ip -br addr | grep ib0
ip route get 12.12.12.250
ping -c 3 12.12.12.250

结果:

ib0 UP 12.12.12.202/24
12.12.12.250 dev ib0 src 12.12.12.202
ping 正常

恢复 /public NFS 挂载

存储网络恢复后,手动挂载:

sudo mount -t nfs4 \
-o rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,hard,proto=tcp,timeo=600,retrans=2,sec=sys,local_lock=none \
12.12.12.250:/public /public

验证:

df -hT /public /public/home /public/home/dingrui
findmnt /public
which starship

结果:

12.12.12.250:/public nfs4 ... /public
which starship -> ~/bin/starship

重新 SSH 后,starship prompt 恢复:

dingrui in 🌐 gpu2 in ~


让 IB 和 NFS 重启后自动恢复

这一步很关键,否则下一次重启后 /public 可能再次丢失。

写入自动加载模块:

sudo tee /etc/modules-load.d/ipoib.conf >/dev/null <<'EOF'
mlx5_core
mlx5_ib
ib_ipoib
EOF

添加 /etc/fstab

sudo cp /etc/fstab /etc/fstab.bak.$(date +%F_%H%M%S)

echo '12.12.12.250:/public /public nfs4 rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,hard,proto=tcp,timeo=600,retrans=2,sec=sys,local_lock=none,_netdev,nofail 0 0' | sudo tee -a /etc/fstab

测试:

sudo mount -a
df -hT /public

为了闭环,又做了一次受控重启测试。重启后检查:

uname -r
ip -br addr | grep ib0
df -hT /public/home/dingrui
nvidia-smi
which starship

全部正常:

Kernel: 5.4.278
ib0: UP 12.12.12.202/24
/public: nfs4
NVIDIA: 595.80
starship: ~/bin/starship

恢复 Slurm 调度

确认系统、GPU、NFS、IB 都正常后,恢复节点:

sudo /opt/gridview/slurm/bin/scontrol update NodeName=gpu2 State=RESUME

验证:

/opt/gridview/slurm/bin/sinfo -N -l | grep gpu2
/opt/gridview/slurm/bin/scontrol show node gpu2 | egrep 'State=|Reason='

结果:

gpu2 ... idle ...
State=IDLE

sinfo 中也显示:

gpu up infinite 1 idle gpu2

Slurm GPU 测试作业

测试脚本:

cat > ~/test_gpu2.sh <<'EOF'
#!/bin/bash
#SBATCH -p gpu
#SBATCH -w gpu2
#SBATCH --gres=gpu:1
#SBATCH -J test_gpu2
#SBATCH -o test_gpu2.%j.out

hostname
uname -r
nvidia-smi
df -hT /public/home/$USER
EOF

提交:

sbatch -A dingrui ~/test_gpu2.sh

查看输出:

cat ~/test_gpu2.<jobid>.out

结果显示:

gpu2
5.4.278-1.el7.elrepo.x86_64
NVIDIA-SMI 595.80
Driver Version: 595.80
CUDA Version: 13.2
12.12.12.250:/public nfs4 ... /public

因为脚本里申请的是:

#SBATCH --gres=gpu:1

所以作业环境里只看到 1 张 GPU 是正常的,说明 Slurm GRES 隔离生效。


关于 nvidia-peermem

这次安装使用了:

--no-peermem

原因是 nvidia-peermem 模块构建失败。

nvidia-peermem 主要用于:

GPUDirect RDMA
InfiniBand 直接访问 GPU 显存
多节点 GPU 通信
部分 NCCL over IB 高性能路径

普通 CUDA 计算依赖的核心模块是:

nvidia
nvidia_uvm
nvidia_modeset

当前这些模块都已正常加载:

lsmod | grep -E 'nvidia|uvm'

所以普通 CUDA、PyTorch、TensorFlow、自定义 CUDA 二进制库通常不受影响。

不过,nvidia-peermem 失败可能与当时 RDMA/IB 栈没有完整加载有关。升级后最初只有 mlx5_core,没有完整的:

mlx5_ib
ib_ipoib
ib_core

因此如果后续确实需要 GPUDirect RDMA,可以在维护窗口重新尝试安装 peermem:

sudo /opt/gridview/slurm/bin/scontrol update NodeName=gpu2 State=DRAIN Reason="try nvidia-peermem"

ip -br addr | grep ib0
lsmod | grep -E 'mlx5_ib|ib_core|ib_ipoib|nvidia'

cd /tmp
sudo rm -rf /tmp/makeself.*

sudo env \
PATH=/opt/rh/devtoolset-9/root/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
CC=/opt/rh/devtoolset-9/root/usr/bin/gcc \
./NVIDIA-Linux-x86_64-595.80.run --concurrency-level=1

如果失败,再回到已知成功方案:

sudo env \
PATH=/opt/rh/devtoolset-9/root/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
CC=/opt/rh/devtoolset-9/root/usr/bin/gcc \
./NVIDIA-Linux-x86_64-595.80.run --concurrency-level=1 --no-peermem

回退方案

整个过程中始终保留旧内核,这是非常重要的。

查看内核:

sudo grubby --info=ALL | egrep -i '^(index|kernel|title)='

如果需要回退到旧内核:

sudo grubby --set-default /boot/vmlinuz-3.10.0-957.el7.x86_64
sudo reboot

如果只是下一次临时启动某个内核,可以使用:

sudo grub2-reboot <index>
sudo reboot

这次实践中,“默认旧内核 + 下一次临时启动新内核”的方式非常有用。

它可以避免新内核启动失败后节点长期卡在不可用状态。


最终检查清单

升级完成后,应至少检查以下项目:

uname -r
sudo grubby --default-kernel

nvidia-smi
nvidia-smi -L
lsmod | grep -E 'nvidia|uvm'

ip -br addr | grep ib0
ip route get 12.12.12.250
df -hT /public/home/$USER
which starship

/opt/gridview/slurm/bin/sinfo -N -l | grep gpu2
/opt/gridview/slurm/bin/scontrol show node gpu2 | egrep 'State=|Reason='

建议记录成功状态:

mkdir -p ~/gpu2-driver-upgrade-record

{
date
hostname
uname -r
cat /proc/version
sudo grubby --default-kernel
ip -br addr | grep ib0
df -hT /public/home/$USER
nvidia-smi
lsmod | grep -E 'nvidia|uvm|mlx5|ipoib'
/opt/gridview/slurm/bin/sinfo -N -l | grep gpu2
} | tee ~/gpu2-driver-upgrade-record/gpu2_final_success.txt

经验总结

这次维护最大的经验是:GPU 驱动升级不是单独的 NVIDIA 问题,而是内核、编译器、网络、存储、Slurm 权限共同作用的问题。

几个关键注意事项:

  1. 先 Drain 节点,再维护。
    不要让用户作业在维护过程中进入节点。

  2. 不要盲目追最新内核。
    对 CentOS 7 来说,kernel-lt 5.4kernel-ml 6.9 更稳。

  3. 保留旧内核回退。
    使用 grub2-reboot 做一次性启动,比直接改默认内核更安全。

  4. 新内核需要匹配的新 GCC。
    CentOS 7 默认 GCC 4.8.5 很容易无法编译新版 NVIDIA 模块。

  5. 普通 CUDA 不一定需要 nvidia-peermem
    没有 GPUDirect RDMA 需求时,可以使用 --no-peermem

  6. 不要忽略共享存储。
    登录后配置丢失、starship 消失,不一定是 shell 配置问题,可能是 /public 没挂上。

  7. IB/IPoIB 要随内核一起检查。
    新内核启动后,mlx5_ibib_ipoib 可能没有自动加载,导致 NFS 存储网络不可达。

  8. Slurm 测试必须通过真实作业完成。
    nvidia-smi 在 SSH 中正常不等于 Slurm 作业环境正常。

  9. 临时修改 Slurm 分区权限要写回配置。
    scontrol update PartitionName=... 只是运行时状态,长期生效需要改 slurm.conf

  10. 每一步都要验证,而不是假设成功。
    这次问题能排完,靠的就是每一步都对比 gpu1gpu2 的实际状态。


最终状态

最终 gpu2 已恢复为:

Node: gpu2
State: IDLE
Kernel: 5.4.278-1.el7.elrepo.x86_64
NVIDIA Driver: 595.80
CUDA reported by driver: 13.2
GPU: RTX 3090 × 8
NFS: 12.12.12.250:/public mounted on /public
IB: ib0 12.12.12.202/24
Slurm GPU job test: passed

到这里,gpu2 的内核、驱动、共享存储、IB 网络和 Slurm 作业验证全部闭环。