GPU 编号进阶:CUDA_VISIBLE_DEVICES、多进程与容器化陷阱
本篇是系列第二篇。第一篇介绍了
CUDA_DEVICE_ORDER的基本概念与修复方法。本篇在此基础上,深入讲解CUDA_VISIBLE_DEVICES与CUDA_DEVICE_ORDER的叠加效应,分析多进程训练框架(torch.distributed、DeepSpeed)中的潜在陷阱,并介绍 Docker 和 Kubernetes 环境下的特殊处理方式,最后给出混合 GPU 环境下的正确架构设计思路。

作者:吴佳浩
撰稿时间:2026-3-19
最后更新:2026-3-22
测试版本:pytorch 2.8.0
一、CUDA 设备枚举机制的完整流程
在讲进阶内容之前,先把 CUDA Runtime 枚举 GPU 设备的完整流程梳理清楚。这个流程涉及两个环境变量,它们的生效顺序是固定的,必须理解清楚才能正确使用。
1.1 枚举流程详解
1.2 两个环境变量的职责分工
这两个环境变量虽然都影响 CUDA 设备编号,但职责完全不同:
CUDA_DEVICE_ORDER:决定排序规则。在所有 GPU 可见的前提下,按什么顺序给它们编号。这是"排序"操作。CUDA_VISIBLE_DEVICES:决定可见性。哪些 GPU 对这个进程可见,不在列表中的 GPU 就像不存在一样。这是"过滤"操作。
两者是串行生效的:先由 CUDA_DEVICE_ORDER 确定所有 GPU 的排序,然后由 CUDA_VISIBLE_DEVICES 从这个排序结果中选取指定的 GPU,并对选出的 GPU 重新从 0 开始编号。
1.3 CUDA_VISIBLE_DEVICES 的语法
CUDA_VISIBLE_DEVICES 的值是一个逗号分隔的索引列表,这些索引对应的是 CUDA_DEVICE_ORDER 决定的排序结果中的位置:
bash
# 只让 cuda:0 和 cuda:2 可见(基于当前 CUDA_DEVICE_ORDER 的排序)
export CUDA_VISIBLE_DEVICES=0,2
# 完全禁用 GPU,让程序跑在 CPU 上
export CUDA_VISIBLE_DEVICES=""
# 也可以用逗号分隔的列表,顺序决定最终的 cuda 编号
# 例如 CUDA_VISIBLE_DEVICES=2,0 会让原来的 cuda:2 变成 cuda:0,cuda:0 变成 cuda:1
export CUDA_VISIBLE_DEVICES=2,0
这里有一个很容易被忽视的细节:CUDA_VISIBLE_DEVICES 中的数字,指的是当前 CUDA_DEVICE_ORDER 策略下的 cuda 编号,而不是 nvidia-smi 的物理编号(除非你已经设置了 CUDA_DEVICE_ORDER=PCI_BUS_ID)。
二、CUDA_VISIBLE_DEVICES 与 CUDA_DEVICE_ORDER 的叠加效应
这是最容易出现混乱的地方。两个环境变量叠加使用时,最终的设备编号可能与直觉完全不同。
2.1 场景一:仅设置 CUDA_VISIBLE_DEVICES,不设置 CUDA_DEVICE_ORDER
此时 CUDA_DEVICE_ORDER 使用默认的 FASTEST_FIRST,在本文的服务器上,PyTorch 的初始映射是:
ini
cuda:0 = 4090(sm_89,最强)
cuda:1 = T4(Bus 5E)
cuda:2 = T4(Bus AF)
cuda:3 = T4(Bus D8)
现在设置 CUDA_VISIBLE_DEVICES=1,2,意思是"只让初始映射中 cuda:1 和 cuda:2 对应的 GPU 可见":
bash
export CUDA_VISIBLE_DEVICES=1,2
python -c "
import torch
for i in range(torch.cuda.device_count()):
print(f'cuda:{i} -> {torch.cuda.get_device_name(i)}')
"
输出:
makefile
cuda:0 -> Tesla T4 (原 cuda:1,即 Bus 5E 的 T4,重新编号为 0)
cuda:1 -> Tesla T4 (原 cuda:2,即 Bus AF 的 T4,重新编号为 1)
此时 4090 被完全隐藏,进程只能看到两张 T4,cuda:0 是 Bus 5E 的那张,cuda:1 是 Bus AF 的那张。
2.2 场景二:同时设置 PCI_BUS_ID 和 CUDA_VISIBLE_DEVICES
此时 CUDA_DEVICE_ORDER=PCI_BUS_ID,PyTorch 的初始映射与 nvidia-smi 一致:
ini
cuda:0 = T4(Bus 5E)
cuda:1 = 4090(Bus 86)
cuda:2 = T4(Bus AF)
cuda:3 = T4(Bus D8)
同样设置 CUDA_VISIBLE_DEVICES=1,2:
bash
export CUDA_DEVICE_ORDER=PCI_BUS_ID
export CUDA_VISIBLE_DEVICES=1,2
python -c "
import torch
for i in range(torch.cuda.device_count()):
print(f'cuda:{i} -> {torch.cuda.get_device_name(i)}')
"
输出:
makefile
cuda:0 -> NVIDIA GeForce RTX 4090 (原 cuda:1,即 4090,重新编号为 0)
cuda:1 -> Tesla T4 (原 cuda:2,即 Bus AF 的 T4,重新编号为 1)
同样是 CUDA_VISIBLE_DEVICES=1,2,但因为 CUDA_DEVICE_ORDER 不同,两个场景的最终结果完全不同!场景一选出了两张 T4,场景二选出了一张 4090 和一张 T4。
2.3 叠加效应的完整流程图
核心规则:CUDA_VISIBLE_DEVICES 中的索引,指向的是 CUDA_DEVICE_ORDER 先确定的初始排序结果中的位置。两者的顺序不能颠倒,必须先理解 CUDA_DEVICE_ORDER 的结果,才能正确使用 CUDA_VISIBLE_DEVICES。
2.4 实际工程中的最佳实践
为了避免叠加效应带来的混乱,生产环境中建议遵循以下原则:
第一,始终先设置 CUDA_DEVICE_ORDER=PCI_BUS_ID ,让两套编号统一。这样 CUDA_VISIBLE_DEVICES 中的索引就与 nvidia-smi 里的 GPU 编号一一对应,直观可靠。
第二,用 CUDA_VISIBLE_DEVICES 做进程隔离,而不是用它来改变编号顺序。每个服务进程只看到自己需要的那张 GPU,避免进程之间误用彼此的 GPU:
bash
export CUDA_DEVICE_ORDER=PCI_BUS_ID
# modeA 服务:只看到 GPU 0(T4)
CUDA_VISIBLE_DEVICES=0 python xxxxxxx.py &
# modelB 服务:只看到 GPU 1(4090)
CUDA_VISIBLE_DEVICES=1 python xxxxxxx.py &
# 另外两个 modeA 服务
CUDA_VISIBLE_DEVICES=2 python xxxxxxx.py &
CUDA_VISIBLE_DEVICES=3 python xxxxxxx.py &
这种写法的好处是:每个服务进程里,cuda:0 就是它自己的那张 GPU,不需要在代码里指定复杂的设备编号,也不会因为编号错乱而用错卡。
三、多进程训练框架中的陷阱
在使用 torch.distributed、DeepSpeed 等分布式训练框架时,GPU 编号问题会出现新的变体,因为这些框架会在背后自动操作 CUDA_VISIBLE_DEVICES。
3.1 torch.distributed.run 的自动行为
使用 python -m torch.distributed.run --nproc_per_node=4 train.py 启动多进程训练时,框架会自动为每个 Worker 进程设置 CUDA_VISIBLE_DEVICES,让每个 Worker 只看到一张 GPU:
关键点在于:torch.distributed.run 设置的 CUDA_VISIBLE_DEVICES=0、CUDA_VISIBLE_DEVICES=1 等,这里的 0、1、2、3 是什么含义?它们是 CUDA_DEVICE_ORDER 策略下的排序索引。如果 CUDA_DEVICE_ORDER 是默认的 FASTEST_FIRST,那么:
- Worker 0 的
CUDA_VISIBLE_DEVICES=0指向的是 4090(FASTEST_FIRST 排序下的第 0 张) - Worker 1 的
CUDA_VISIBLE_DEVICES=1指向的是第一张 T4(FASTEST_FIRST 排序下的第 1 张) - 以此类推
这与 nvidia-smi 显示的物理编号完全不同,但只要在调用 torch.distributed.run 之前设置了 export CUDA_DEVICE_ORDER=PCI_BUS_ID,这个行为就会与 nvidia-smi 保持一致,不会有意外。
3.2 DeepSpeed 训练速度异常案例
场景描述
使用 DeepSpeed ZeRO-3 在本文的 4 张混合 GPU(1 张 4090 + 3 张 T4)上做分布式训练,训练速度比单用 4 张 T4 的服务器还慢,完全出乎意料。
按照 nvidia-smi 显示,GPU 0 是 T4,GPU 1 是 4090,GPU 2、3 是 T4。但在 FASTEST_FIRST 排序下,cuda:0 对应 4090,cuda:1/2/3 对应三张 T4。
问题分析
这个案例揭示了混合 GPU 做数据并行训练的本质矛盾:数据并行要求所有 Worker 在每个 step 结束时同步梯度(All-Reduce 操作),因此整体速度受到最慢的那张卡的制约。4090 虽然快 13 倍,但它 92% 的时间都在空等三张 T4,实际利用率极低。
从效率角度看,这种配置下的 4 卡训练速度,大约等于 4 张 T4 的训练速度,4090 几乎没有发挥作用。
根本原因
即使编号设置正确(设置了 PCI_BUS_ID),在混合型号 GPU 上做数据并行训练也是低效的。编号问题和架构问题是两个独立的问题,解决了编号问题不等于解决了架构问题。
正确的做法是:不要在异构 GPU 上做数据并行,而是按任务类型分配 GPU(详见第五节)。
3.3 安全的多进程启动方式
无论使用什么分布式训练框架,安全的多进程启动模板如下:
bash
#!/bin/bash
# 第一步:无论如何,先统一编号体系
# 这行必须在所有 python 调用之前
export CUDA_DEVICE_ORDER=PCI_BUS_ID
# 方式一:让 torch.distributed.run 自动分配(编号已与 nvidia-smi 统一,可信赖)
python -m torch.distributed.run \
--nproc_per_node=4 \
--master_addr=localhost \
--master_port=12355 \
train.py
# 方式二:手动隔离每个进程(更明确,生产环境推荐)
# 设置了 PCI_BUS_ID 之后,CUDA_VISIBLE_DEVICES 的编号与 nvidia-smi 一致
CUDA_VISIBLE_DEVICES=0 python xxxxxx.py & # T4,Bus 5E
CUDA_VISIBLE_DEVICES=1 python xxxxxx.py & # 4090,Bus 86
CUDA_VISIBLE_DEVICES=2 python xxxxxx.py & # T4,Bus AF
CUDA_VISIBLE_DEVICES=3 python xxxxxx.py & # T4,Bus D8
wait # 等待所有后台进程结束
3.4 进程内验证环境变量的生效情况
在分布式训练的 Worker 进程里,可以加入启动时的验证代码,确认每个进程确实拿到了预期的 GPU:
python
import os
import torch
import torch.distributed as dist
def worker_gpu_check():
"""在每个 Worker 进程启动时验证 GPU 分配"""
local_rank = int(os.environ.get("LOCAL_RANK", 0))
device_order = os.environ.get("CUDA_DEVICE_ORDER", "NOT SET")
visible = os.environ.get("CUDA_VISIBLE_DEVICES", "NOT SET")
# 每个 Worker 进程都只看到一张 GPU(框架设置了 CUDA_VISIBLE_DEVICES)
# 所以 cuda:0 就是这个 Worker 的 GPU
gpu_name = torch.cuda.get_device_name(0)
print(f"[Worker {local_rank}] "
f"CUDA_DEVICE_ORDER={device_order}, "
f"CUDA_VISIBLE_DEVICES={visible}, "
f"cuda:0 = {gpu_name}")
# 在训练脚本开头调用
worker_gpu_check()
输出示例(设置了 PCI_BUS_ID,使用方式二手动隔离):
ini
[Worker 0] CUDA_DEVICE_ORDER=PCI_BUS_ID, CUDA_VISIBLE_DEVICES=0, cuda:0 = Tesla T4
[Worker 1] CUDA_DEVICE_ORDER=PCI_BUS_ID, CUDA_VISIBLE_DEVICES=1, cuda:0 = NVIDIA GeForce RTX 4090
[Worker 2] CUDA_DEVICE_ORDER=PCI_BUS_ID, CUDA_VISIBLE_DEVICES=2, cuda:0 = Tesla T4
[Worker 3] CUDA_DEVICE_ORDER=PCI_BUS_ID, CUDA_VISIBLE_DEVICES=3, cuda:0 = Tesla T4
四、容器化环境的特殊处理
Docker 和 Kubernetes 是 GPU 服务最常见的部署环境。在容器化环境中,GPU 编号问题有额外的复杂性。
4.1 Docker 中的 GPU 映射基础
使用 --gpus 参数启动 Docker 容器时,可以指定容器能看到哪些 GPU:
bash
# 让容器能看到所有 GPU
docker run --gpus all my_image python train.py
# 只让容器看到宿主机的 GPU 1
docker run --gpus '"device=1"' my_image python train.py
# 让容器看到宿主机的 GPU 0 和 GPU 2
docker run --gpus '"device=0,2"' my_image python train.py
关键理解 :容器内永远只看到从 0 开始的连续编号。如果你指定 device=1,容器内只有 cuda:0,它对应的是宿主机上编号为 1 的 GPU(这个"1"的含义取决于 CUDA_DEVICE_ORDER)。
在容器里,cuda:0 就是 4090,这是符合预期的。容器内的代码写 cuda:0 即可,不需要关心宿主机上的编号。
4.2 CUDA_DEVICE_ORDER 对 Docker device= 编号的影响
这里有一个重要的细节:docker run --gpus '"device=1"' 里的 1,指的是宿主机上按 CUDA_DEVICE_ORDER 排序后的 cuda 编号,而不一定是 nvidia-smi 的物理编号。
bash
# 情形一:宿主机未设置 CUDA_DEVICE_ORDER(FASTEST_FIRST)
# 宿主机的编号:cuda:0=4090, cuda:1=T4(Bus 5E), cuda:2=T4(AF), cuda:3=T4(D8)
# 所以 device=1 指向的是 T4(Bus 5E),不是 4090
docker run --gpus '"device=1"' my_image nvidia-smi
# 容器内看到:Tesla T4
# 情形二:宿主机设置了 CUDA_DEVICE_ORDER=PCI_BUS_ID
# 宿主机的编号:cuda:0=T4(5E), cuda:1=4090(86), cuda:2=T4(AF), cuda:3=T4(D8)
# 所以 device=1 指向的是 4090,与 nvidia-smi 显示一致
docker run -e CUDA_DEVICE_ORDER=PCI_BUS_ID --gpus '"device=1"' my_image nvidia-smi
# 容器内看到:NVIDIA GeForce RTX 4090
同样是 device=1,两种情形下容器得到的是完全不同的 GPU。这个细节非常容易踩坑,尤其是在脚本中写死了 device= 的编号时。
4.3 最安全的 Docker GPU 指定方式
最安全的方法是使用 PCI Bus ID 直接指定 GPU,完全绕过任何排序策略:
bash
# 先用 nvidia-smi 查出各 GPU 的 Bus ID
nvidia-smi --query-gpu=index,name,pci.bus_id --format=csv,noheader
# 输出:
# 0, Tesla T4, 00000000:5E:00.0
# 1, NVIDIA GeForce RTX 4090, 00000000:86:00.0
# 2, Tesla T4, 00000000:AF:00.0
# 3, Tesla T4, 00000000:D8:00.0
# 用 Bus ID 直接指定,不受任何排序策略影响
# 无论宿主机 CUDA_DEVICE_ORDER 怎么设置,这里的 4090 永远是 4090
docker run --gpus '"device=00000000:86:00.0"' my_image python train.py
这种方式的优点:完全确定性,不受 CUDA_DEVICE_ORDER 影响,无论宿主机环境怎么变化,指定的始终是那张物理 GPU。
4.4 在容器内透传环境变量
另一个常见的做法是在容器启动时传入 CUDA_DEVICE_ORDER 环境变量,让容器内的行为与宿主机一致:
bash
# 方法一:通过 -e 参数传入
docker run \
-e CUDA_DEVICE_ORDER=PCI_BUS_ID \
--gpus all \
my_image python train.py
# 方法二:通过 --env-file 传入(适合有很多环境变量的场景)
cat > /tmp/gpu_env.txt << EOF
CUDA_DEVICE_ORDER=PCI_BUS_ID
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
EOF
docker run \
--env-file /tmp/gpu_env.txt \
--gpus all \
my_image python train.py
# 方法三:在 Dockerfile 中设置(对所有使用这个镜像的容器生效)
# Dockerfile:
# ENV CUDA_DEVICE_ORDER=PCI_BUS_ID
4.5 Kubernetes 中的 GPU 调度
在 Kubernetes 环境中,GPU 编号问题的表现形式有所不同:
单 GPU Pod 的情况 :每个 Pod 只分配一张 GPU,容器内只有 cuda:0,编号问题不存在。代码里直接写 cuda:0 就是正确的。
多 GPU Pod 的情况 :如果一个 Pod 请求多张 GPU(nvidia.com/gpu: 4),那么 Pod 内的编号规则就又回到了 CUDA_DEVICE_ORDER 控制的范畴。此时需要在 Pod 的环境变量中设置 CUDA_DEVICE_ORDER=PCI_BUS_ID:
yaml
# kubernetes deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: training
env:
- name: CUDA_DEVICE_ORDER
value: "PCI_BUS_ID"
- name: PYTORCH_CUDA_ALLOC_CONF
value: "expandable_segments:True"
resources:
limits:
nvidia.com/gpu: 4
五、混合 GPU 环境的架构设计
解决了编号问题之后,还需要思考混合 GPU 环境下的任务分配架构。即使编号完全正确,不合理的架构设计仍然会导致性能问题。
5.1 错误的架构:异构 GPU 做数据并行
数据并行(Data Parallelism)是最常见的多卡训练策略,它把每个训练 batch 切分成若干 mini-batch,分别在不同 GPU 上并行计算,最后汇总梯度。
这种架构浪费了 4090 的大部分算力。购买 4090 的钱,却只得到了 T4 水平的训练效率。
5.2 正确的架构:按任务类型分配
混合 GPU 环境的正确设计原则是:不同性能的 GPU 跑不同性质的任务,而不是让所有 GPU 做相同的事情。
在这个架构下:
- 4090 专门负责计算量最大的 modelB,能充分发挥其 330 TFLOPS 的 FP16 算力
- 三张 T4 并行处理 modeA 请求,通过横向扩展提高吞吐量
- 各服务之间独立运行,互不干扰,GPU 利用率最大化
5.3 推理服务的设备分配代码
python
import os
import sys
import torch
# 必须在最顶部设置
os.environ.setdefault("CUDA_DEVICE_ORDER", "PCI_BUS_ID")
def verify_gpu_setup(expected_device_id: int, expected_gpu_name_keyword: str):
"""
服务启动时验证 GPU 分配是否正确。
参数:
expected_device_id: 预期使用的 cuda 设备编号
expected_gpu_name_keyword: 预期 GPU 名称中包含的关键词
例如 "4090"、"T4"、"A100"
"""
if not torch.cuda.is_available():
print("[FATAL] CUDA 不可用,请检查驱动安装")
sys.exit(1)
device_count = torch.cuda.device_count()
if expected_device_id >= device_count:
print(f"[FATAL] 请求 cuda:{expected_device_id},"
f"但系统只有 {device_count} 张 GPU(cuda:0 到 cuda:{device_count - 1})")
sys.exit(1)
device_order = os.environ.get("CUDA_DEVICE_ORDER", "NOT SET")
if device_order != "PCI_BUS_ID":
print(f"[WARNING] CUDA_DEVICE_ORDER = {device_order},不是 PCI_BUS_ID")
print("[WARNING] GPU 编号可能与 nvidia-smi 不一致,建议设置 PCI_BUS_ID")
actual_name = torch.cuda.get_device_name(expected_device_id)
props = torch.cuda.get_device_properties(expected_device_id)
print(f"[INFO] cuda:{expected_device_id} -> {actual_name} "
f"(sm_{props.major}{props.minor}, {props.total_mem / 1024**3:.1f}GB)")
if expected_gpu_name_keyword not in actual_name:
print(f"[FATAL] 预期 GPU 包含 '{expected_gpu_name_keyword}',"
f"但实际是 '{actual_name}'")
print("[FATAL] GPU 分配错误,服务拒绝启动")
sys.exit(1)
print(f"[OK] GPU 验证通过:cuda:{expected_device_id} 确实是 {actual_name}")
return f"cuda:{expected_device_id}"
# modelB 服务启动时验证(确保 cuda:1 是 4090)
modeB_DEVICE = verify_gpu_setup(1, "4090")
# modeA 服务启动时验证(确保 cuda:0 是 T4)
modeA_DEVICE = verify_gpu_setup(0, "T4")
六、真实案例深度分析
6.1 案例一:LoRA 微调写错卡导致 OOM
背景
开发者在一台 4 卡服务器(1 张 4090 + 3 张 T4)上做 Qwen-8B 模型的 LoRA 微调。8B 模型的 FP16 权重约需要 16GB 显存,而 T4 只有 15GB 显存,装不下。计划是把模型放到 4090 的 49GB 显存上。
错误代码
python
# 开发者认为 CUDA_VISIBLE_DEVICES=1 会让进程只看到 GPU 1(4090)
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
import torch
from transformers import AutoModelForCausalLM
# device_map="auto" 会自动把模型放到可用的 GPU 上
# 由于设置了 CUDA_VISIBLE_DEVICES=1,开发者以为只有 4090 可见
model = AutoModelForCausalLM.from_pretrained("Qwen-8B", device_map="auto")
问题分析
错误信息:
erlang
torch.cuda.OutOfMemoryError: CUDA out of memory.
Tried to allocate 2.00 GiB.
GPU 0 has a total capacity of 14.56 GiB of which 512.00 MiB is free.
开发者看到 14.56 GiB 就蒙了:4090 有 49GB,怎么只有 14.56GB?14.56 GiB 正好是 T4 的实际可用显存大小(名义 15GB,实际约 14.56GiB)。
根因 :在 CUDA_VISIBLE_DEVICES=1 设置之前没有设置 CUDA_DEVICE_ORDER=PCI_BUS_ID,导致 1 指向的是 FASTEST_FIRST 排序下的第 1 号 GPU,即 T4,而不是 nvidia-smi 里的 GPU 1(4090)。
正确写法:
python
import os
# 顺序不能错:先设置 DEVICE_ORDER,再设置 VISIBLE_DEVICES,最后 import torch
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "1" # 现在 1 就是 nvidia-smi 里的 GPU 1(4090)
import torch
print(torch.cuda.get_device_name(0)) # 输出:NVIDIA GeForce RTX 4090
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("Qwen-8B", device_map="auto")
6.2 案例二:模型服务迁移后 QPS 骤降
背景
一个在线推理服务,原来部署在单卡 4090 服务器上,QPS(每秒请求数)为 120。为了降低成本,把服务迁移到一台混合 GPU 服务器(1 张 4090 + 3 张 T4),代码完全不改,QPS 降到了 15。
这个案例更加诡异:代码写的是 cuda:0,在 FASTEST_FIRST 排序下,cuda:0 应该是 4090(算力最强),为什么性能反而下降了?
根因 :更深入的排查发现,这台混合 GPU 服务器上还运行着另一个服务,那个服务通过 CUDA_VISIBLE_DEVICES=0 占用了 cuda:0(即 4090),导致迁移过来的服务在争用 cuda:0 时被切换到了其他可用的 GPU(T4)上。
实际上,这个案例反映了两个独立的问题:(1)没有用 CUDA_VISIBLE_DEVICES 做进程隔离,各服务之间可能抢占 GPU;(2)没有设置 CUDA_DEVICE_ORDER=PCI_BUS_ID 来确保编号稳定可预期。
正确的修复方案:
bash
export CUDA_DEVICE_ORDER=PCI_BUS_ID
# 每个服务用 CUDA_VISIBLE_DEVICES 隔离,确保独占各自的 GPU
CUDA_VISIBLE_DEVICES=0 python server_modelA_0.py & # 独占 T4(Bus 5E)
CUDA_VISIBLE_DEVICES=1 python server_modelB.py & # 独占 4090(Bus 86)
CUDA_VISIBLE_DEVICES=2 python server_modelA_1.py & # 独占 T4(Bus AF)
CUDA_VISIBLE_DEVICES=3 python server_modelA_2.py & # 独占 T4(Bus D8)
七、总结
两个环境变量的完整使用模板:
bash
#!/bin/bash
# 正确的完整模板
# 第一步:统一排序规则(必须在所有 python 调用之前)
export CUDA_DEVICE_ORDER=PCI_BUS_ID
# 第二步:用 CUDA_VISIBLE_DEVICES 做进程隔离
# 此时索引号与 nvidia-smi 的 GPU 编号一一对应,直观可靠
CUDA_VISIBLE_DEVICES=0 python server_modelA_0.py & # nvidia-smi GPU 0
CUDA_VISIBLE_DEVICES=1 python server_modelB.py & # nvidia-smi GPU 1
CUDA_VISIBLE_DEVICES=2 python server_modelA_1.py & # nvidia-smi GPU 2
CUDA_VISIBLE_DEVICES=3 python server_modelA_2.py & # nvidia-smi GPU 3