如何选择在哪个GPU上运行任务?

155

在多GPU计算机中,我该如何指定CUDA作业应在哪个GPU上运行?

例如,在安装CUDA时,我选择安装了NVIDIA_CUDA-<#.#>_Samples,然后运行了几个nbody模拟实例,但它们都在GPU 0上运行; GPU 1完全空闲(使用watch -n 1 nvidia-dmi进行监视)。 使用以下命令检查CUDA_VISIBLE_DEVICES

echo $CUDA_VISIBLE_DEVICES

我发现这个没有设置。我试过使用以下方法进行设置:

CUDA_VISIBLE_DEVICES=1

然后再次运行 nbody,但它也转到了GPU 0。

我查看了相关问题 如何选择指定的GPU来运行CUDA程序?,但是 deviceQuery 命令不在CUDA 8.0 bin目录中。除了 $CUDA_VISIBLE_DEVICES$,我看到其他帖子提到环境变量 $CUDA_DEVICES,但这些没有设置,我也没有找到如何使用它的信息。

虽然与我的问题没有直接关系,但使用 nbody -device = 1 我能够使应用程序在GPU 1上运行,但是使用 nbody -numdevices = 2 没有在GPU 0和1上运行。

我正在测试一个在CentOS 6.8上使用bash shell、CUDA 8.0、2个GTX 1080 GPU和NVIDIA驱动程序367.44的系统。

我知道在使用CUDA编写时可以管理和控制使用哪些CUDA资源,但当运行已编译的CUDA可执行文件时,我该如何从命令行管理这些资源呢?


nbody 应用程序具有命令行选项,可选择要运行的 GPU-您可能需要研究该代码。 对于更一般的情况,“CUDA_VISIBLE_DEVICES”应该可以使用。 如果不行,您可能没有正确使用它,您应该给出一个完整的尝试示例。 您还应该指示您正在使用哪个操作系统,对于Linux,要使用什么shell(例如bash、csh等)。 deviceQuery 对此没有必要,它只是一个示例应用程序,用于演示 CUDA_VISIBLE_DEVICES 的行为。 正确的环境变量名称中没有 $ 符号。 - Robert Crovella
9
你需要学习更多关于你正在使用的 bash shell。这个命令:CUDA_VISIBLE_DEVICES=1并不能永久地设置环境变量(实际上,如果你在命令行上只输入这个命令,它几乎没有什么用处)。这个命令:export CUDA_VISIBLE_DEVICES=1将永久地为该会话设置它。你可能想要学习关于环境变量在bash中如何工作、各种命令对它们产生的影响以及持续时间等方面的知识。 - Robert Crovella
2
deviceQuery 是 CUDA 8 提供的,但您需要构建它。如果您阅读了 Linux 的 CUDA 8 安装指南,它将解释如何构建 deviceQuery - Robert Crovella
在 /usr/local/cuda/bin 目录下,有一个 cuda-install-samples-<version>.sh 脚本,如果示例未安装,则可以使用该脚本。然后,在 NVIDIA_Samples 安装目录的 1_Utilities 文件夹中,您将找到 deviceQuery。只需在该文件夹中调用 make 命令即可为您编译它。如果我没记错的话,它会将二进制文件复制到同一文件夹中。 - Mircea
2
应该是 watch -n 1 nvidia-smi ... - oliversm
对于随机GPU,您可以执行以下操作:export CUDA_VISIBLE_DEVICES=$((( RANDOM % 8 ))) - Charlie Parker
6个回答

232

问题是由于未正确在shell中设置CUDA_VISIBLE_DEVICES变量引起的。

例如,要指定CUDA设备1,您可以使用以下方式设置CUDA_VISIBLE_DEVICES

export CUDA_VISIBLE_DEVICES=1
或者
CUDA_VISIBLE_DEVICES=1 ./cuda_executable

前者为当前shell的生命周期设置变量,后者仅为特定可执行文件调用的寿命设置变量。

如果您想指定多个设备,请使用

export CUDA_VISIBLE_DEVICES=0,1
或者
CUDA_VISIBLE_DEVICES=0,1 ./cuda_executable

1
如果CUDA_VISIBLE_DEVICE=0会发生什么? - Kurian Benoy
3
@KurianBenoy 设置 CUDA_VISIBLE_DEVICE=0 将选择 GPU 0 来执行任何 CUDA 任务。我认为这是默认行为,因为在设置变量之前,所有我的 GPU 任务都会发送到 GPU 0,所以根据您的用例,实际上可能不需要设置它。 - Steven C. Howell
@StevenC.Howell 我一直认为 CUDA_VISIBLE_DEVICE=0 表示的是 CPU 系统。谢谢你澄清了我的疑惑。 - Kurian Benoy
4
@KurianBenoy CUDA_VISIBLE_DEVICES="" 的意思是使用 CPU。 - runDOSrun
如果我在源代码中使用cudaSetDevice,同时将CUDA_VISIBLE_DEVICE设置为0会怎么样? - Ziqi Fan

45

如果有人在使用Python时遇到问题,尝试在导入pycuda和tensorflow之前进行设置。

I.e.:

import os
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
...
import pycuda.autoinit
import tensorflow as tf
...

正如在这里所看到的。


1
这很好用!我在终端上使用它,而不是Python: export CUDA_DEVICE_ORDER=PCI_BUS_ID 然后 export CUDA_VISIBLE_DEVICES=<GPU_NUMBER> - Mann

22

您可以在命令行中设置GPU,这样就不需要将设备硬编码到脚本中(可能会在没有多个GPU的系统上失败)。假如您想要在第5个GPU上运行脚本,您可以在命令行中键入以下内容,它将只在GPU#5上运行您的脚本:

CUDA_VISIBLE_DEVICES=5, python test_script.py

19

请设置以下两个环境变量:

NVIDIA_VISIBLE_DEVICES=$gpu_id
CUDA_VISIBLE_DEVICES=0

其中gpu_id是您选择的GPU的ID,如在主机系统的nvidia-smi中所见(从0开始的整数),将其提供给客户机系统(例如Docker容器环境)。

您可以通过检查在客户机系统终端中运行的nvidia-smi中的Bus-Id参数来验证每个gpu_id值选择了不同的卡。

更多信息

基于NVIDIA_VISIBLE_DEVICES的此方法仅向系统公开单个卡(本地ID为零),因此我们还将另一个变量CUDA_VISIBLE_DEVICES硬编码为0(主要是防止它默认为空字符串,这将表示没有GPU)。

请注意,在启动客户机系统之前应设置环境变量(因此无法在Jupyter Notebook的终端中执行此操作),例如使用docker run -e NVIDIA_VISIBLE_DEVICES=0或Kubernetes或Openshift中的env

如果要进行GPU负载平衡,请在每次客户机系统启动时使gpu_id随机。

如果使用Python设置此项,请确保对所有环境变量(包括数字变量)使用字符串

您可以通过检查在客户机系统中运行的终端中的nvidia-smi的Bus-Id参数来验证每个gpu_id值选择了不同的卡。

基于 {{CUDA_VISIBLE_DEVICES}} 的已接受解决方案不能隐藏其他卡(与固定卡不同),因此如果您尝试在启用GPU的Python软件包中使用它们,则会导致访问错误。使用此解决方案,其他卡对于客户系统不可见,但其他用户仍然可以访问它们并平等地共享它们的计算能力,就像使用CPU一样(已验证)。
这也比使用Kubernetes / Openshift控制器({{resources.limits.nvidia.com/gpu}})的解决方案更可取,后者将对分配的卡进行锁定,从可用资源池中删除它(因此具有GPU访问权限的容器数量不能超过物理卡的数量)。
在运行Ubuntu 18.04或20.04的docker容器中,并由Openshift 3.11编排,经过CUDA 8.0、9.0、10.1和11.2测试。

7

更新

在评论中,lukaszzenko提供了一个修改后的解决方案,使用了相同的思路并得到了相同的输出。建议使用该解决方案,因为它更简洁:

export CUDA_VISIBLE_DEVICES=$(nvidia-smi --query-gpu=memory.free,index --format=csv,nounits,noheader | sort -nr | head -1 | awk '{ print $NF }')

选择利用率最低的GPU(原始解决方案)

在将xml2json添加到您的路径后,您可以选择利用率最低的N个GPU:

export CUDA_VISIBLE_DEVICES=$(nvidia-smi -x -q | xml2json | jq '.' | python -c 'import json;import sys;print(",".join([str(gpu[0]) for gpu in sorted([(int(gpu["minor_number"]), float(gpu["utilization"]["gpu_util"].split(" ")[0])) for gpu in json.load(sys.stdin)["nvidia_smi_log"]["gpu"]], key=lambda x: x[1])[:2]]))')

如果你只需要一张GPU,或者根据你可用的最大GPU数量,将[:2]替换为[:1]

10
更简单的做法是使用以下命令:export CUDA_VISIBLE_DEVICES=$(nvidia-smi --query-gpu=memory.free,index --format=csv,nounits,noheader | sort -nr | head -1 | awk '{ print $NF }')。该命令不需要修改原意,但可以使语言更加通俗易懂,同时不提供额外解释或信息。 - lukaszzenko
嘿!感谢你简化我的原始想法;你的方法确实使其更加简洁。对于任何发现这有用的人,也请考虑给原帖点赞。这有助于确保多样化的方法得到关注和赞赏。伟大的合作! - Jan

3
对于一个随机的GPU,你可以这样做:

export CUDA_VISIBLE_DEVICES=$((( RANDOM % 8 )))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接