在 Docker 中使用GPU
我们在 GPU 与 CUDA 编程入门 这篇博客中初步介绍了如何 Linux 上使用 GPU 的方法,随着容器和 k8s 的迅猛发展,人们对于在容器中使用 GPU 的需求越发强烈。本文将基于前文,继续介绍如何在容器中使用 GPU,进一步地,介绍在 Kubernetes 中如何调度 GPU,并以 Tensorflow 为例,介绍如何基于 Docker 搭建部署了 GPU 的深度学习开发环境。
背景介绍
容器最早是用于无缝部署基于 CPU 的应用,它们对于硬件和平台是无感知的,但是显然这种使用场景对于 GPU 并不适用。对于不同的 GPU,需要机器安装不同的硬件驱动,这极大限制了在容器中使用 GPU。为了解决这个问题,最早的一种使用方法是在容器中完全重新安装一次 NVIDIA 驱动,然后将在容器启动的时候将 GPU 以字符设备 /dev/nvidia0 的方式传递给容器。然而这种方法要求容器中安装的驱动版本与 Host 上的驱动版本完全一致,同一个 Docker Image 不能在各个机器上复用,这极大的限制了容器的扩展性。
为了解决上述问题,容器必须对于 NVIDIA 驱动是无感知的,基于此 NVIDIA 推出了 NVIDIA Container Toolkit:
如上图所示, NVIDIA 将原来 CUDA 应用依赖的 API 环境划分为两个部分:
- 驱动级 API:由
libcuda.so.major.minor动态库和内核 module 提供支持,图中表示为 CUDA Driver- 驱动级 API 属于底层 API,每当 NVIDIA 公司释放出某一个版本的驱动时,如果你要升级主机上的驱动,那么内核模块和
libcuda.so.major.minor这 2 个文件就必须同时升级到同一个版本,这样原有的程序才能正常工作, - 不同版本的驱动不能同时存在于宿主机上
- 驱动级 API 属于底层 API,每当 NVIDIA 公司释放出某一个版本的驱动时,如果你要升级主机上的驱动,那么内核模块和
- 非驱动级 API:由动态库
libcublas.so等用户空间级别的 API 组成,图中表示为 CUDA Toolkit- 非驱动级 API 的版本号是以 Toolkit 自身的版本号来管理, 比如 cuda-10,cuda-11
- 不同版本的 Toolkit 可以同时运行在相同的宿主机上
- 非驱动级 API 算是对驱动级 API 的一种更高级的封装,最终还是要调用驱动级 API 来实现功能
为了让使用 GPU 的容器更具可扩展性,关于非驱动级的 API 被 NVIDIA 打包进了 NVIDIA Container Toolkit,因此在容器中使用 GPU 之前,每个机器需要先安装好 NVIDIA 驱动,之后配置好 NVIDIA Container Toolkit 之后,就可以在容器中方便使用 GPU 了。
整体架构
NVIDIA 的容器工具包本质是使用一个 nvidia-runc的方式来提供 GPU 容器的创建, 在用户创建出来的 OCI spec 上补上几个 hook 函数,来达到 GPU 设备运行的准备工作。具体包括以下几个组件,从上到下展示如图:
nvidia-docker2nvidia-container-runtimenvidia-container-toolkitlibnvidia-container
下面对这几个组件依次介绍:
libnvidia-container
libnvidia-container 提供了一个 library 和一个配置 GNU/Linux 的 Container 使用 NVIDIA GPU 的 client nvidia-container-cli。libnvidia-container 的实现依赖于 kernel primitives,并且是对于 container runtime 是无关的。
nvidia-container-cli 的主要作用就是将 NVIDIA GPU 注入到容器中,包括 /dev/nvidia0 设备挂载等操作。下面是抓到的日志信息,可以看到其主要操作包括:
- 加载内核模块,包括 nvidia/nvidia_uvm/nvidia_modeset 等
- 创建字符设备,包括 nvidia0,nvidiactl,nvidia-uvm,nvidia-modeset 等
- Mount GPU 设备、NVIDIA 相关库等
|
|
到 libnvidia-container 代码中查看,可以看到
|
|
具体就是将 /dev/nvidia0 设备 bind mount 到 container roofs 的 /dev/nvidia0上。
下面是详细日志信息:
|
|
nvidia-container-toolkit
nvidia-container-toolkit 被 runC 在 PreStart Hook 的时候调用,此时 Container 已经被创建,但是还没有启动。nvidia-container-toolkit 的主要作用是搜集信息(比如 container 的 roofs 路径),搜集在 config.json 的信息,拼凑起来 nvidia-container-cli 的参数,并调用 nvidia-container-cli,调用参数为:
|
|
nvidia-container-runtime
在执行 docker run 的时候,加上 --runtime=nvidia 参数,就会将 docker 的 runtime 从 runC 变成 nvidia-container-runtime。nvidia-docker-runtime 本质上就是对 runC 的一个简单封装,它把 runC Spec 当作输入,将 nvidia-container-toolkit 作为 PreStart Hook,然后调用 runC。
|
|
注意,这里的 nvidia-container-runtime-hook 实际上就是执行 /usr/bin/nvidia-container-toolkit 的软链接。
当安装了 nvidia-container-runtime 之后,需要修改 Docker 的 daemon.json 来使其生效,或者显示制定 --runtime 参数。
|
|
nvidia-docker2
nvidia-docker2 是整个 NVIDIA Container Toolkit 中唯一与 docker 相关的包,它的作用在用户 docker run/create 的时候,添加 --runtime=nvidia 的参数,然后调用上面的 nvidia-container-runtime 进行后面的一系列操作,将 GPU 注入到容器中。它也支持设置 NV_GPU 参数来指定哪一个 GPU 来注射到 容器中。
nvidia-docker 本质上就是一个 Shell 脚本,内容如下所示:
|
|
部署验证
这里仍然基于腾讯云的 CentOS 7 机器为例演示如何在安装配置 NVIDIA Container Toolkit,对于更多的平台可以参考其官方文档。
安装 Docker CE
|
|
安装 NVIDIA Container Toolkit
Setup the stable repository and the GPG key:
|
|
Install the nvidia-docker2 package (and dependencies) after updating the package listing:
|
|
|
|
Restart the Docker daemon to complete the installation after setting the default runtime:
|
|
At this point, a working setup can be tested by running a base CUDA container:
|
|
This should result in a console output shown below:
|
|
配置 NVIDIA Runtime
To register the nvidia runtime, use the method below that is best suited to your environment. You might need to merge the new argument with your existing configuration. Three options are available:
Systemd drop-in file
|
|
|
|
|
|
Daemon configuration file
The nvidia runtime can also be registered with Docker using the daemon.json configuration file:
|
|
|
|
You can optionally reconfigure the default runtime by adding the following to /etc/docker/daemon.json:
|
|
Command Line
Use dockerd to add the nvidia runtime:
|
|
在 k8s 中管理 GPU
为了在 k8s 中管理和使用 GPU,我们除了需要配置 NVIDIA Container Toolkit,还需要安装 NVIDIA 推出的 NVIDIA/k8s-device-plugin,具体安装可以参考 我的这篇博文。上面的步骤加起来显得还是有些繁琐,如果你直接使用腾讯云 TKE 的话,在集群添加装有 GPU 的 Node 时候,就会自动帮你安装配置好 NVIDIA Container Toolkit 和 NVIDIA/k8s-device-plugin,十分方便。接下来我们以 Tensorflow 为例,演示在 k8s 环境运行有 GPU 的 Tensorflow。
单机版的 Tensorflow,执行 kubectl apply -f tensorflow.yaml来运行 Jupiter Notebook。
|
|
我们看到容器很快运行起来,根据 http:<nodeIP>:<nodePort> 可以访问到 Jupiter Notebook,但是显示需要 token:
查看 Tensorflow 日志,可以获得 token:aa06c9f12d80adac1a6288b97bf8030522cecc92202dbb20
|
|
登陆之后即可看到 Jupiter Notebook:
新建 Notebook,运行命令如下:
可以看到,TensorFlow 支持在 GPU 上的运算
"/device:GPU:0":TensorFlow 可见的机器上第一个 GPU 的速记表示法。"/job:localhost/replica:0/task:0/device:GPU:0":TensorFlow 可见的机器上第一个 GPU 的完全限定名称。
参考资料
-
No backlinks found.