多卡服务器上怎么给不同用户配置独立的CUDA环境变量

文章导读
在多人共用的多卡服务器上,给不同用户配置独立的 CUDA 环境,通常的做法是把 CUDA_VISIBLE_DEVICES、CUDA_HOME、PATH、LD_LIBRARY_PATH 这几项写进各用户自己的 shell 启动文件,而不是写进全局的 /etc/profile。这件事真正需要先想清楚的是:这些变量只改变进程看到的设备和库路径,不构成权限隔离,同一台机器上的用户仍然能改回来或者从别的入口
📋 目录
  1. Ⅰ 先确认当前场景和目标
  2. Ⅱ 按这个顺序处理
  3. Ⅲ 改完后怎么验证
  4. Ⅳ 容易忽略的边界
A A

在多人共用的多卡服务器上,给不同用户配置独立的 CUDA 环境,通常的做法是把 CUDA_VISIBLE_DEVICES、CUDA_HOME、PATH、LD_LIBRARY_PATH 这几项写进各用户自己的 shell 启动文件,而不是写进全局的 /etc/profile。这件事真正需要先想清楚的是:这些变量只改变进程看到的设备和库路径,不构成权限隔离,同一台机器上的用户仍然能改回来或者从别的入口占用同一批卡。

多人共用多卡机器时,把 CUDA_VISIBLE_DEVICES、CUDA_HOME、PATH、LD_LIBRARY_PATH 分别写进各用户的 shell 启动文件,一般就能让每人用各自的卡和工具链;但这只是进程级约定,不是权限控制。改完重新登录,用 env 输出和 torch.cuda.device_count() 交叉核对;若目标包含防止互相抢占,需要改用容器、cgroup 设备限制或 MIG。

先确认当前场景和目标

这个问题成立的前提是:一台服务器上有多个用户共用同一批 GPU,各自可能需要不同版本的 CUDA toolkit、不同框架,或者只被分配其中几张卡。如果你的目标只是“每人默认用自己那几张卡、默认用到自己那套工具链”,用户级变量够用;如果目标是“任何人都不能抢别人的卡”,那环境变量解决不了,得靠容器、cgroup 设备规则、MIG 实例或调度系统(如 Slurm)来划分。

动手前先把这几项确认清楚,后面的写法都依赖它们:

  • 卡的数量与编号:执行 nvidia-smi -L,记下 GPU 序号和 UUID;编号是否稳定取决于 CUDA_DEVICE_ORDER,建议统一用 PCI_BUS_ID。
  • 用户 shell 类型:bash 还是 zsh、登录 shell 还是非交互 shell,决定变量写进 ~/.bashrc、~/.profile 还是 ~/.zshrc。
  • 你是否拥有 root:只有 root 才能写 /etc/profile.d/,普通用户只能改自己的家目录。
  • 任务入口:是 ssh 登录后手动跑脚本,还是通过 Jupyter、systemd 服务、cron、Docker 启动。后几种通常不读用户 shell 的启动文件。
  • 是否已有 conda 或 module 体系:如果有,优先用环境本身管理 CUDA 运行时,环境变量只做补充。

按这个顺序处理

第一步,先把变量清单定下来,不要一次加太多。多数场景只需要 CUDA_VISIBLE_DEVICES 和 CUDA_HOME 两项;PATH 用于让 nvcc 指向正确版本;LD_LIBRARY_PATH 只在确实需要时才加,因为全局拼库路径容易和框架自带的运行时冲突。第二步,选落点:普通用户改自己的 ~/.bashrc 最省事;需要统一管理且你有 root 时,可以在 /etc/profile.d/ 下放一个按用户名分支的脚本。

下面是一个可替换的用户级片段,放在 ~/.bashrc 末尾,把卡号、CUDA 目录换成你机器上的实际值:

多卡服务器上怎么给不同用户配置独立的CUDA环境变量
# 用户级 CUDA 变量示例,按实际情况替换卡号与路径
export CUDA_DEVICE_ORDER=PCI_BUS_ID
export CUDA_VISIBLE_DEVICES=0,1
export CUDA_HOME=/usr/local/cuda-12.x
export PATH="$CUDA_HOME/bin:$PATH"
# 仅在需要时启用,避免与框架自带 runtime 冲突
# export LD_LIBRARY_PATH="$CUDA_HOME/lib64:${LD_LIBRARY_PATH}"

如果你有 root,希望集中维护,可以用按用户分支的写法。注意 /etc/profile.d/ 里的脚本只对登录 shell 生效,ssh 直接执行单条命令、systemd 服务和部分 IDE 终端不会读到它:

# /etc/profile.d/cuda-user.sh,需 root,仅登录 shell 生效
case "$USER" in
  alice) export CUDA_VISIBLE_DEVICES=0,1 ;;
  bob)   export CUDA_VISIBLE_DEVICES=2,3 ;;
esac
export CUDA_DEVICE_ORDER=PCI_BUS_ID

第三步,让改动在干净会话里生效。不要在当前 shell 里靠一次 export 试完就结束,那样验证的只是临时状态;重新登录或至少新开一个终端,才是用户日常真正会遇到的环境。

改完后怎么验证

验证要分两层:变量有没有落到位,以及程序实际看到了什么。先看变量:

env | grep -E 'CUDA|LD_LIBRARY_PATH'
nvcc --version

再让运行时自己报数,这一步比看变量更可靠。以 PyTorch 为例,预期信号是进程可见卡数等于 CUDA_VISIBLE_DEVICES 里列出的条目数,且编号被重映射为从 0 开始:

多卡服务器上怎么给不同用户配置独立的CUDA环境变量
python -c "import torch; print(torch.cuda.device_count()); print(torch.version.cuda)"

如果分配了两张卡,device_count() 应返回 2;返回 0 说明变量没生效或驱动、运行时版本不匹配;返回全部卡数说明进程读到的还是全局环境。同时用 nvidia-smi 确认:其他卡依然可见是正常现象,可见性限制只作用于当前进程。

最后换另一个用户登录,重复上面两条命令,确认两人拿到的卡号和 CUDA 版本互不影响。建议把这几条检查写成一个简单的自检脚本,让新用户自己跑一遍,比口头交代更省事。

容易忽略的边界

CUDA_VISIBLE_DEVICES 只是约定,不是强制隔离。用户完全可以取消这个变量,或者换一个不走你配置的入口去占卡,所以不要把“配了环境变量”当成资源配额。真要防止互相抢占,需要容器加设备限制、cgroup 设备规则、MIG 切分或调度系统,这些属于另一套方案,不能靠 shell 变量替代。

几个常踩的坑:sudo 默认会重置环境(env_reset 与 secure_path),在 sudo 下通常看不到用户变量,改 sudoers 要谨慎;systemd 服务、cron、Jupyter kernel、远程开发容器都不会读 ~/.bashrc,需要在各自的启动配置里单独注入;LD_LIBRARY_PATH 全局写死容易让不同框架互相污染,优先用 conda 环境或容器镜像来隔离运行时;同一用户要切换多个 CUDA 版本时,不要让两份变量同时生效,用 alias 或一次性脚本切换更清楚。

还有一个版本边界:能安装哪个 CUDA 运行时受驱动版本限制,只改 CUDA_HOME 并不能绕开驱动支持的上限。回退也很简单,把新增片段注释掉或删掉,重新登录即可恢复原状;如果你不确定改动的影响,可以先把配置写在一个独立的 ~/.cuda_env 文件里,在启动文件末尾用 source 引入,出问题时只处理这一处。