cGPU服务可以隔离GPU资源,实现多个容器共用一张显卡。本章节介绍如何在GPU实例上安装和使用cGPU服务。

前提条件

安装cGPU服务前,请确保GPU实例满足以下要求:
  • GPU实例规格为gn6i、gn6v、gn6e、gn5i、gn5、ebmgn6i或ebmgn6e。
  • GPU实例操作系统为CentOS 7.6、CentOS 7.7、Ubuntu 16.04、Ubuntu 18.04或Aliyun Linux。
  • GPU实例已安装418.87.01或更高版本的NVIDIA驱动。
  • GPU实例已安装19.03.5或更高版本的Docker。

背景信息

为了提高GPU硬件资源的利用率,需要在单张显卡上运行多个容器,并在多个容器间隔离GPU应用。

阿里云cGPU服务通过自研的内核驱动为容器提供虚拟的GPU设备,在保证性能的前提下隔离显存和算力,为充分利用GPU硬件资源进行训练和推理提供有效保障。您可以通过命令方便地配置容器内的虚拟GPU设备。

使用cGPU服务具有以下优势:
  • 适配开源标准的Kubernetes和NVIDIA Docker方案。
  • 无需重编译AI应用,无需替换CUDA库,升级CUDA、cuDNN的版本后无需重新配置。
  • 支持同时隔离显存和算力。

安装cGPU服务

  1. 下载cGPU安装包。
    wget http://cgpu.oss-cn-hangzhou.aliyuncs.com/cgpu.tar.gz
  2. 解压安装包。
    tar -xvf cgpu.tar.gz
  3. 查看安装包文件。
    说明 安装包版本不同时,安装包中包含的文件可能不同。
    # cd cgpu
    # ls
    cgpu-container-wrapper  cgpu-km.c  cgpu.o  cgpu-procfs.c  install.sh  Makefile  os-interface.c  README  uninstall.sh  upgrade.sh  version.h
  4. 安装cGPU服务。
    sh install.sh
  5. 验证安装结果。
    # lsmod | grep cgpu
    cgpu_km                71355  0

    显示cgpu服务的状态信息,表示已成功安装cGPU服务。

运行cGPU服务

影响cGPU服务的环境变量如下表所示,您可以在创建容器时指定环境变量的值,控制容器可以通过cGPU服务获得的算力。
环境变量名称 取值类型 说明 示例
CGPU_DISABLE Boolean 是否启用cGPU服务,取值范围:
  • CGPU_DISABLE=false:启用cGPU服务。
  • CGPU_DISABLE=true:禁用cGPU服务,使用默认的NVIDIA容器服务。
ALIYUN_COM_GPU_MEM_DEV Integer 设置GPU实例上每张显卡的总显存大小,和实例规格有关。
说明 显存大小按GiB取整数。
一台GPU实例规格为ecs.gn6i-c4g1.xlarge,配备1张NVIDIA ® Tesla ® T4显卡。在GPU实例上执行nvidia-smi查看总显存大小为15109 MiB,取整数为15 GiB。
ALIYUN_COM_GPU_MEM_CONTAINER Integer 设置容器内可见的显存大小,和ALIYUN_COM_GPU_MEM_DEV结合使用。如果不指定本参数或指定为0,则不使用cGPU服务,使用默认的NVIDIA容器服务。 在一张总显存大小为15 GiB的显卡上,设置环境变量ALIYUN_COM_GPU_MEM_DEV=15ALIYUN_COM_GPU_MEM_CONTAINER=1,效果是为容器分配1 GiB的显存。
ALIYUN_COM_GPU_VISIBLE_DEVICES Integer或uuid 指定容器内可见的GPU显卡。 在一台有4张显卡的GPU实例上,执行nvidia-smi -L查看GPU显卡设备号和UUID。返回示例如下所示:
GPU 0: Tesla T4 (UUID: GPU-b084ae33-e244-0959-cd97-83****)
GPU 1: Tesla T4 (UUID: GPU-3eb465ad-407c-4a23-0c5f-bb****)
GPU 2: Tesla T4 (UUID: GPU-2fce61ea-2424-27ec-a2f1-8b****)
GPU 3: Tesla T4 (UUID: GPU-22401369-db12-c6ce-fc48-d7****)
然后,设置以下环境变量:
  • ALIYUN_COM_GPU_VISIBLE_DEVICES=0,1,效果是为容器分配第1和第2张显卡。
  • ALIYUN_COM_GPU_VISIBLE_DEVICES=GPU-b084ae33-e244-0959-cd97-83****,GPU-3eb465ad-407c-4a23-0c5f-bb****,GPU-2fce61ea-2424-27ec-a2f1-8b****,效果是为容器分配3张指定UUID的显卡。
ALIYUN_COM_GPU_SCHD_WEIGHT Integer 设置容器的算力权重,取值范围:1~min(max_inst, 16)。

以ecs.gn6i-c4g1.xlarge为例演示2个容器共用1张显卡。

  1. 执行以下命令创建容器并设置容器内可见的显存。
    docker run -d -t --gpus all --privileged --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 --name gpu_test1 -v /mnt:/mnt -e ALIYUN_COM_GPU_MEM_CONTAINER=6 -e ALIYUN_COM_GPU_MEM_DEV=15 nvcr.io/nvidia/tensorflow:19.10-py3
    docker run -d -t --gpus all --privileged --shm-size=1g --ulimit memlock=-1 --ulimit stack=67108864 --name gpu_test2 -v /mnt:/mnt -e ALIYUN_COM_GPU_MEM_CONTAINER=8 -e ALIYUN_COM_GPU_MEM_DEV=15 nvcr.io/nvidia/tensorflow:19.10-py3
    说明 该命令以使用TensorFlow镜像nvcr.io/nvidia/tensorflow:19.10-py3为例,请根据实际情况更换为您自己的容器镜像。使用TensorFlow镜像搭建TensorFlow深度学习框架的操作,请参见在GPU实例上部署NGC环境
    本示例中,通过设置环境变量ALIYUN_COM_GPU_MEM_CONTAINER和ALIYUN_COM_GPU_MEM_DEV指定显卡的总显存和容器内可见的显存。命令执行结果是创建了2个容器:
    • gpu_test1:分配6 GiB显存。
    • gpu_test2:分配8 GiB显存。
  2. 进入容器。
    本示例中,以gpu_test1为例。
    docker exec -it gpu_test1 bash
  3. 执行以下命令查看显存等GPU信息。
    nvidia-smi
    容器gpu_test1中可见的显存为6043 MiB,如下图所示。gpu_test1

查看procfs节点

cGPU服务运行时会在/proc/cgpu_km下生成并自动管理多个procfs节点,您可以通过procfs节点查看和配置cGPU服务相关的信息。下面介绍各procfs节点的用途。

  1. 执行以下命令查看节点信息。
    # ls /proc/cgpu_km/
    0  default_memsize  inst_ctl  major  version
    节点信息说明如下表所示。
    节点 读写类型 说明
    0 读写 cGPU服务会针对GPU实例中的每张显卡生成一个的目录,并使用数字作为目录名称,例如0、1、2。本示例中只有一张显卡,对应的目录ID为0。
    default_memsize 读写 如果没有设置ALIYUN_COM_GPU_MEM_CONTAINER参数,默认为新创建的容器分配的显存大小。
    inst_ctl 读写 控制节点。
    major 只读 cGPU内核驱动的主设备号。
    version 只读 cGPU的版本。
  2. 执行以下命令查看显卡对应的目录内容。
    本示例中,以显卡0为例。
    # ls /proc/cgpu_km/0
    012b2edccd7a   0852a381c0cf   free_weight    max_inst    policy
    目录内容说明如下表所示。
    节点 读写类型 说明
    容器对应的目录 读写 cGPU服务会针对运行在GPU实例中的每个容器生成一个的目录,并使用容器ID作为目录名称。您可以执行docker ps查看已创建的容器。
    free_weigt 只读 用于查询和修改可用的权重。如果free_weight=0,新创建容器的权重值为0,该容器不能获取GPU算力,不能用于运行需要GPU算力的应用。
    max_inst 读写 用于设置容器的最大数量,取值范围为1~16。
    policy 读写 cGPU服务支持以下算力调度策略:
    • 0:平均调度。每个容器占用固定的时间片,时间片占比为1/max_inst
    • 1:抢占调度。每个容器占用尽量多的时间片,时间片占比为1/当前容器数
    • 2:权重抢占调度。当ALIYUN_COM_GPU_SCHD_WEIGHT的取值大于1时,自动使用权重抢占调度。

    您可以通过修改policy的值实时调整调度策略。更多调度策略说明,请参见cGPU服务算力调度示例

  3. 执行以下命令查看容器对应的目录内容。
    本示例中,以012b2edccd7a容器为例。
    # ls /proc/cgpu_km/0/012b2edccd7a
    id  meminfo  memsize  weight
    目录内容说明如下表所示。
    节点 读写类型 说明
    id 只读 容器的ID。
    memsize 读写 用于设置容器内的显存大小。cGPU服务会根据ALIYUN_COM_GPU_MEM_DEV参数自动设定此值。
    meminfo 只读 包括容器内剩余显存容量、正在使用GPU的进程ID及其显存用量。输出如下所示:
    Free: 6730809344
    PID: 19772 Mem: 200278016
    weight 读写 用于设置容器获取显卡最大算力的权重,默认值为1。所有运行中的容器的权重之和必须小于等于max_inst。
了解procfs节点的用途后,您可以在GPU实例中执行命令进行切换调度策略、修改权重等操作,示例命令如下表所示。
命令 效果
echo 2 > /proc/cgpu_km/0/policy 将调度策略切换为权重抢占调度。
cat /proc/cgpu_km/0/free_weight 查看显卡上可用的权重。 如果free_weight=0,新创建容器的权重值为0,该容器不能获取GPU算力,不能用于运行需要GPU算力的应用。
cat /proc/cgpu_km/0/$dockerid/weight 查看指定容器的权重。
echo 4 > /proc/cgpu_km/0/$dockerid/weight 修改容器获取GPU算力的权重。

升级cGPU服务

  1. 关闭所有运行中的容器。
    docker stop (docker ps -a | awk '{ print $1}' | tail -n +2)
  2. 执行upgrade.sh脚本升级cGPU服务至最新版本。
    sh upgrade.sh

卸载cGPU服务

  1. 关闭所有运行中的容器。
    docker stop (docker ps -a | awk '{ print $1}' | tail -n +2)
  2. 执行sh uninstall.sh脚本卸载cGPU服务。
    sh uninstall.sh

cGPU服务算力调度示例

cGPU服务加载cgpu_km的模块时,会按照容器最大数量(max_inst)为每张显卡设置时间片(X ms),用于为容器分配GPU算力,本示例中以Slice 1、Slice 2...Slice N表示。使用不同调度策略时的调度示例如下所示。
  • 平均调度
    在创建容器时,为容器分配时间片。cGPU服务会从Slice 1时间片开始调度,提交任务到物理GPU,并执行一个时间片(X ms)的时间,然后切换到下一个时间片。每个容器获得的算力相同,都为1/max_inst。如下图所示。平均调度
  • 抢占调度

    在创建容器时,为容器分配时间片。cGPU服务会从Slice 1开始调度,但如果没有使用某个容器,或者容器内没有进程打开GPU设备,则跳过调度,切换到下一个时间片。

    示例如下:
    1. 只创建一个容器Docker 1,获得Slice 1时间片,在Docker 1中运行2个TensorFlow进程,此时Docker 1最大获得整个物理GPU的算力。
    2. 再创建一个容器Docker 2,获得Slice 2时间片。如果Docker 2内没有进程打开GPU设备,调度时会跳过Docker 2的时间片Slice 2。
    3. 当Docker 2内有进程打开GPU设备时,Slice 1和Slice 2都加入调度,Docker 1和Docker 2最大分别获得1/2物理GPU的算力。如下图所示。抢占调度
  • 权重抢占调度

    如果在创建容器时设置ALIYUN_COM_GPU_SCHD_WEIGHT大于1,则自动使用权重抢占调度。cGPU服务按照容器数量(max_inst)将物理GPU算力划分成max_inst份,但如果ALIYUN_COM_GPU_SCHD_WEIGHT大于1,cGPU服务会将数个时间片组合成一个更大的时间片分配给容器。

    设置示例如下:
    • Docker 1:ALIYUN_COM_GPU_SCHD_WEIGHT=m
    • Docker 2:ALIYUN_COM_GPU_SCHD_WEIGHT=n
    调度效果如下:
    • 如果只有Docker 1运行, Docker 1抢占整个物理GPU的算力。
    • 如果Docker 1和Docker 2同时运行,Docker 1和Docker 2获得的理论算力比例是m:n。和抢占调度不同的是,即使Docker 2中没有GPU进程也会占用n个时间片的时间。
      说明 m:n设置为2:1和8:4时的运行表现存在差别。在1秒内切换时间片的次数,前者是后者的4倍。
    权重抢占调度
    权重抢占调度限制了容器使用GPU算力的理论最大值。但对算力很强的显卡(例如NVIDIA ® V100显卡),如果显存使用的较少,在一个时间片内即可完成计算任务。此时如果m:n值设置为8:4,则剩余时间片内GPU算力会闲置,限制基本失效。因此建议根据显卡算力设置适当的权重值,例如:
    • 如果使用NVIDIA ® V100显卡,将m:n设置为2:1,尽量降低权重值,避免GPU算力闲置。
    • 如果使用NVIDIA ® Telsa ® T4显卡,将m:n设置为8:4, 尽量增大权重值,保证分配足够的GPU算力。