06 容器内如何使用gpu

一、cpu与gpu的区别

cpu:擅长逻辑控制,串行的运算,cpu就好像是一个厉害的老博士,任何复杂的数学题都会

gpu:擅长大规模并发计算,gpu就好像是一群只会简单加减法的小学生,每个小学生都只能算简单的题目,但是gpu是组织了一大群小学生一起来算

现实场景,比如人工智能领域的图像识别,需要的不是大量复杂的逻辑判断与运算,而是大量的简单运算,此时cpu就不好用了,gpu最合适

GPU的工作大部分就是这样,计算量大,但没什么技术含量,而且要重复很多很多次。就像你有个工作需要算几亿次一百以内加减乘除一样,最好的办法就是雇上几十个小学生一起算,一人算一部分,反正这些计算也没什么技术含量,纯粹体力活而已。而CPU就像老教授,积分微分都会算,就是工资高,一个老教授资顶二十个小学生,你要是富士康你雇哪个?GPU就是这样,用很多简单的计算单元去完成大量的计算任务,纯粹的人海战术。这种策略基于一个前提,就是小学生A和小学生B的工作没有什么依赖性,是互相独立的。很多涉及到大量计算的问题基本都有这种特性,比如你说的破解密码,挖矿和很多图形学的计算。这些计算可以分解为多个相同的简单小任务,每个任务就可以分给一个小学生去做。但还有一些任务涉及到“流”的问题。比如你去相亲,双方看着顺眼才能继续发展。总不能你这边还没见面呢,那边找人把证都给领了。这种比较复杂的问题都是CPU来做的。  总而言之,CPU和GPU因为最初用来处理的任务就不同,所以设计上有不小的区别。而某些任务和GPU最初用来解决的问题比较相似,所以用GPU来算了。GPU的运算速度取决于雇了多少小学生,CPU的运算速度取决于请了多么厉害的教授。教授处理复杂任务的能力是碾压小学生的,但是对于没那么复杂的任务,还是顶不住人多。
当然现在的GPU也能做一些稍微复杂的工作了,相当于升级成初中生高中生的水平。但还需要CPU来把数据喂到嘴边才能开始干活,究竟还是靠CPU来管的。
cpu与gpu的设计区别:https://egonlin.com/?p=7543

拓展阅读

Nvidia中文名英伟达,1999年,NVIDIA定义了GPU,这极大地推动了PC游戏市场的发展,重新定义了现代计算机图形技术,并彻底改变了并行计算

NVIDIA Tesla GPU系列P4、T4、P40以及V100是Tesla GPU系列的明星产品

当下计算行业正在从只使用CPU的“中央处理”向CPU与GPU并用的“协同处理”发展。

CPU负责数据的逻辑处理,然后将处理好的数据交给GPU进行大规模的运算。

为此,NVIDIA™(英伟达™)发明了CUDA这一编程模型,它提供了可以直接调用GPU硬件接口,可用于在应用程序中充分利用CPU和GPU各自的优点

二、在容器中使用gpu

2.1 nvidia-docker1.0

NVIDIA于2016年开始设计NVIDIA-Docker已便于容器使用NVIDIA GPUs。
第一代nvidia-docker1.0实现了对docker client的封装,并在容器启动时,将必要的GPU device和libraries挂载到容器中。但是这种设计的方式高度的与docker运行时耦合,缺乏灵活性。存在的缺陷具体如下:

  • 设计高度与docker耦合,不支持其它的容器运行时。如: LXC, CRI-O及未来可能会增加的容器运行时。
  • 不能更好的利用docker生态的其它工具。如: docker compose。
  • 不能将GPU作为调度系统的一种资源来进行灵活的调度。
  • 完善容器运行时对GPU的支持。如: 自动的获取用户层面的NVIDIA Driver libraries, NVIDIA kernel modules, device ordering等。

基于上面描述的这些弊端,NVIDIA开始了对下一代容器运行时的设计: nvidia-docker2.0。

2.2 nvidia-docker2.0

先简单介绍下nvidia-docker 2.0与ontainerd、vidia-container-runtime、ibnvidia-container以及runc之间的关系
它们之间的关系可以通过下面这张图关联起来:
file

  • 1、nvidia-docker 2.0
    nvidia-docker2.0 是一个简单的包,它主要通过修改docker的配置文件/etc/docker/daemon.json来让docker使用NVIDIA Container runtime。
  • 2、nvidia-container-runtime
    nvidia-container-runtime 才是真正的核心部分,它在原有的docker容器运行时runc的基础上增加一个prestart hook,用于调用libnvidia-container库。
  • 3、libnvidia-container
    libnvidia-container 提供一个库和一个简单的CLI工具,使用这个库可以使NVIDIA GPU被Linux容器使用。
  • 4、Containerd
    Containerd主要负责的工作是:

    • 管理容器的生命周期(从容器的创建到销毁)
    • 拉取/推送容器镜像
    • 存储管理(管理镜像及容器数据的存储)
    • 调用runc 运行容器
    • 管理容器的网络接口及网络
      containerd接收到请求之后,做好相关的准备工作,可以选择自己调用runc也可以通过创建containerd-shim再去调用runc,而runc基于OCI文件对容器进行创建。
  • 5、RunC
    RunC 是一个轻量级的工具,它是用来运行容器的,只用来做这一件事,并且这一件事要做好。我们可以认为它就是个命令行小工具,可以不用通过 docker 引擎,直接Runc运行容器。事实上,runC 是标准化的产物,它根据 OCI 标准来创建和运行容器。而 OCI(Open Container Initiative)组织,旨在围绕容器格式和运行时制定一个开放的工业化标准。直接使用RunC的命令行即可以完成创建一个容器,并提供了简单的交互能力。

上面已经介绍个各个组件的作用以及它们之间的关系,接下来详细的描述下这张图:
file

  • 1、正常创建一个容器的流程是这样的:

    docker –> dockerd –> containerd–> containerd-shim –>runc –> container-process

    docker客户端将创建容器的请求发送给dockerd, 当dockerd收到请求任务之后将请求发送给containerd, containerd经过查看校验启动containerd-shim或者自己来启动容器进程。
    创建一个使用GPU的容器

  • 2、创建GPU容器的流程如下:

    docker–> dockerd –> containerd –> containerd-shim–> nvidia-container-runtime –> nvidia-container-runtime-hook –> libnvidia-container –> runc — > container-process

    基本流程和不使用GPU的容器差不多,只是把docker默认的运行时替换成了NVIDIA自家的nvidia-container-runtime。
    这样当nvidia-container-runtime创建容器时,先执行nvidia-container-runtime-hook这个hook去检查容器是否需要使用GPU(通过环境变NVIDIA_VISIBLE_DEVICES来判断)。如果需要则调用libnvidia-container来暴露GPU给容器使用。否则走默认的runc逻辑。

说到这里nvidia-docker2.0的大体机制基本就通了。但是涉及到的nvidia-container-runtime, libnvidia-container, containerd,runc这些项目, 这本篇文章里面就不一一介绍了。如果感兴趣可以自行去探索学习。这些项目的地址在文章中都已经做个相关的链接。

参考
NVidia Containter Runtime
NVidia Dev Blogs
OpenContainers Runtime Spec Config
OpenContainers Runtime Spec
NVidia Container Runtime
RunC
libnvidia container

2.3 docker19使用GPU

随着docker的不断升级,对GPU的支持也越来越友好,尤其是docker19.03之后,不再需要安装nvidia-docker了。只安装NVIDIA-CONTAINER-RUNTIME就可以使用了,并且支持docker-compose。
本文记录和梳理一下想要在docker中使用GPU,都需要做些什么。

(1)下载GPU的驱动

NVIDIA驱动程序页面下载对应的驱动。

(2)安装NVIDIA-CONTAINER-RUNTIME

在https://nvidia.github.io/nvidia-container-runtime/查看支持的操作系统和版本,并根据对应选项,添加源,因为我是centos7.6,所以添加方式为:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.repo | \
sudo tee /etc/yum.repos.d/nvidia-container-runtime.repo

在参考https://github.com/NVIDIA/nvidia-container-runtime后,得知直接安装即可

sudo yum install nvidia-container-runtime

(3) 直接使用

此时就准备好了GPU环境,在docker19下就能通过–gpus参数直接使用了,安装docker19略

查看–gpus 参数是否安装成功:

docker run --help | grep -i gpus
      --gpus gpu-request               GPU devices to add to the container ('all' to pass all GPUs)

从Docker 19.03开始,安装好docker之后,只需要使用 –gpus 即可指定容器使用显卡。


1、# 如果镜像里没有cuda环境,那看不到cuda信息,显示结果为CUDA Version: N/A 
docker run -it --rm --gpus all centos nvidia-smi 
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.64.00       Driver Version: 440.64.00      CUDA Version: N/A  |

2、 强调采用的镜像里必须包含cuda
docker run -it --rm --gpus all nvidia/cuda:9.0-base nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.64.00       Driver Version: 440.64.00       CUDA Version: 10.2 |

以上就完成了GPU的准备,可以使用了。(如果指定某一张卡可以使用选项:–gpus "device=0")

强调:容器内可以使用gpu的必备条件
1、物理机主机插有gpu卡
2、物理机操作系统安装了gpu驱动程序
3、安装了docker19.0.3版本
4、启动容器时采用临时参数–gpus指定启用gpu,或者修改配置文件永久修改
5、容器镜像必须包含cuda环境才能使用
当你发现容器内部发现CUDA Version: N/A,就是因为容器内没有cuda的driver接口,需要在镜像里定制好 ,如下制作一个带有cuda环境的java镜像(注意cuda版本)。

ROM         nvidia/cuda:10.1-base
MAINTAINER    scsyn
COPY ./jre1.8.0_271 /jre
ENV JRE_HOME=/jre
ENV CLASSPATH=$JRE_HOME/lib/rt.jar:$JRE_HOME/lib/ext
ENV PATH=$PATH:$JRE_HOME/bin

使用docker build -t nvidia-java:1.0.0 .打包镜像。
之后再使用的话在此基础上进行封装就可以了,比如:

FROM nvidia-java:1.0.0
MAINTAINER scsyn
COPY ./en2zh /pn
ENV LD_LIBRARY_PATH=/pn/lib
WORKDIR "/pn/bin"
ENTRYPOINT ["java","-jar","localdeployment-pn-0.0.1-SNAPSHOT.jar"]

更多操作

所有显卡都对容器可见:
docker run --gpus all --name 容器名 -d -t 镜像id

只有显卡1对容器可见:
docker run --gpus="1" --name 容器名 -d -t 镜像id

如果不指定 --gpus ,运行nvidia-smi 会提示Command not found

注意:
1. 显卡驱动在所有方式中,都要先安装好,容器是不会有显卡驱动的,一台物理机的显卡只对应一个显卡驱动,当显卡驱动安装好后(即使未安装cuda),也可以使用命令nvidia-smi
2. nvidia-smi显示的是显卡驱动对应的cuda版本,nvcc -V 显示的运行是cuda的版本 
启动容器时,容器如果想使用gpu,镜像里必须有cuda环境,就是说,针对想使用gpu的容器,镜像在制作时必须吧cuda环境打进去

下面三个参数代表的都是是容器内可以使用物理机的所有gpu卡
 --gpus all
 NVIDIA_VISIBLE_DEVICES=all
 --runtime=nvida

  NVIDIA_VISIBLE_DEVICES=2 只公开两个gpu,容器内只能用两个gpu

  举例如下:
# 使用所有GPU
$ docker run --gpus all nvidia/cuda:9.0-base nvidia-smi

# 使用两个GPU
$ docker run --gpus 2 nvidia/cuda:9.0-base nvidia-smi

# 指定GPU运行
$ docker run --gpus '"device=1,2"' nvidia/cuda:9.0-base nvidia-smi
$ docker run --gpus '"device=UUID-ABCDEF,1"' nvidia/cuda:9.0-base nvidia-smi

2.4 修改容器的runtime为nvdia-runtime

如果我们嫌弃每次启动容器是都指定–gpus参数很麻烦,那么我们可以直接把默认的Runc替换为nvidia-runtime,这样每次你启动容器是,就不用加–gpus参数了,默认容器内就可以用gpu,当然容器镜像里有cuda环境

为docker环境安装Nvidia-container-runtime
1、对于已经安装好docker的CentOS系统

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.repo | sudo tee /etc/yum.repos.d/nvidia-container-runtime.repo
yum install nvidia-container-runtime -y

# 离线现在,去本节的软件包目录获取nvida-runtime.tar.gz
解压后,执行yum localinstall即可

2、对于已经安装好docker的Ubuntu系统

(1). 首先配置nvidia源

curl -s -L https://nvidia.github.io/nvidia-container-runtime/gpgkey | \
  sudo apt-key add -

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)

curl -s -L https://nvidia.github.io/nvidia-container-runtime/$distribution/nvidia-container-runtime.list | \
  sudo tee /etc/apt/sources.list.d/nvidia-container-runtime.list

sudo apt-get update

(2)、参考https://gitee.com/wuxler/nvidia-container-runtime?_from=gitee_search 安装nvidia-container-runtime

sudo apt-get install nvidia-container-runtime
systemctl stop docker

把运行时添加到docker中,这个只是临时添加
dockerd --add-runtime=nvidia=/usr/bin/nvidia-container-runtime

把运行时添加到docker中,永久添加
[root@yq01-aip-aikefu19 ~]# systemctl stop docker

[root@yq01-aip-aikefu19 ~]# cat /etc/docker/daemon.json 
{
  "insecure-registries":["xxx-registry:5000", "xxx.xxx-int.com"],
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",  # 指定路径,还有id为nvida
            "runtimeArgs": []
        }
    },
    "default-runtime": "nvidia",  # 然后设定默认的runtime
    "data-root": "/data/lib/docker"
}

生效前查看默认的Runc
[root@yq01-aip-aikefu19 ~]# docker info |grep -iw runtime:
 Default Runtime: nvidia

此时,启动容器如果不指定--gpus参数,那根本无法使用nvida-smi命令,会报exec format error执行格式错误
[root@yq01-aip-aikefu19 ~]# docker run --rm --name test nvidia/cuda:9.0-base nvidia-smi

重启让修改生效
[root@yq01-aip-aikefu19 ~]# systemctl start docker

重新查看,发现默认的runtime已经被替换为了nvidia-runtime
[root@yq01-aip-aikefu19 ~]# docker info |grep -iw runtime:
 Default Runtime: runc

(3)、验证

无需加--gpus参数,在容器内也能使用gpu
[root@yq01-aip-aikefu19 ~]# docker run --rm --name test nvidia/cuda:9.0-base nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.64.00    Driver Version: 440.64.00    CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+

2.5 关于gpu信息的说明

显示所有GPU的当前信息状态:

nvidia-smi

查询所有GPU的当前详细信息:

nvidia-smi -q

设备监控命令,以滚动条形式显示GPU设备统计信息:

nvidia-smi dmon

进程监控命令,以滚动条形式显示GPU进程状态信息:

nvidia-smi pmon

实时监测并高亮显示状态:

watch -n 1 -d nvidia-smi,1代表间隔1s刷新。

上一篇
下一篇
Copyright © 2022 Egon的知识星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术