Docker で NVIDIA GPU を利用する場合は、NVIDIA Container Toolkit (NVIDIA Docker) が必要になります。この記事では、なぜ NVIDIA Container Toolkit がなぜ必要か、どう動くかについて個人的に調べたことを記載します。2021 年 4 月時点での情報になります。今後のバージョンアップで内容が古くなっている可能性がある点にご注意ください (#確認した環境)。
NVIDIA Container Toolkit (NVIDIA Docker) 自体のインストール方法や歴史については、NVIDIA Japan のNVIDIA Docker って今どうなってるの? (20.09 版) という記事が非常によくまとまっているため、そちらをご覧ください。
内容が長いため、「まとめ」の章を目次として見ていただければと思います。
なぜ NVIDIA Container Toolkit が必要か
NVIDIA GPU はホストのデバイスファイル (/dev/nvidia0
など) として登録されています。また、CUDA ライブラリ(libcuda.so
など)はホストにインストールされたバージョンと一致したものが必要です。そのためコンテナから GPU を利用する場合、これらをすべてホストからマウントする必要があります。
しかし、デバイス名やライブラリのバージョンは可変であり、設定は煩雑です。マウントしたライブラリの利用前には ldconfig
を実行して、共有ライブラリの更新をするなどの前処理も必要になります。
NVIDIA Container Toolkit は、コンテナで NVIDIA GPU が使えるように、マウントなどの準備を自動で行ってくれるものです。
ホスト側の NVIDIA デバイスやライブラリをマウントするため、ホスト側に NVIDIA ドライバがインストールされている必要があります。NVIDIA ドライバのインストールはいくつかの方法がありますが、cuda-drivers
パッケージでインストールするのが簡単です。
マウントされる内容を見てみる
Docker の --gpus
オプションの有無でマウントの差分を比較すると、NVIDIA Container Toolkit によってマウントされた内容が確認できます。マウントされる内容は、指定した GPU オプションによって多少異なります。マウント以外にも共有ライブラリの更新を行う ldconfig
の実行なども自動で行われています。
diff -U 0 \
<(sudo docker run --runtime=runc --entrypoint /bin/mount nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda10.2) \
<(sudo docker run --runtime=runc --gpus=all --entrypoint /bin/mount nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda10.2)
--- /dev/fd/63 2021-04-08 08:18:42.567678185 +0000
+++ /dev/fd/62 2021-04-08 08:18:42.567678185 +0000
@@ -1 +1 @@
-overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/2V65PYVZQEQEQUKYGYKL7HR5FR:/var/lib/docker/overlay2/l/7AIBFJWODCJRWMDLILRWOHB4Q7:/var/lib/docker/overlay2/l/V4OWAINIOOG5CUMPSAKM3VJZ5Q:/var/lib/docker/overlay2/l/LKGX4OMPAYSA47UDWAQDI2X5T3:/var/lib/docker/overlay2/l/NMVYKU22UFTYCR746XX4YXYZAJ:/var/lib/docker/overlay2/l/KH33H3HWPX7J54CSBPGEHVODCH:/var/lib/docker/overlay2/l/NPFWWK26TM64CQQJK3GUYF7RPO:/var/lib/docker/overlay2/l/A76FTIXHIEOSZVT427WCLXYTVP:/var/lib/docker/overlay2/l/SZJWHW7O4WNEYUYJ6S2DZ7M22K,upperdir=/var/lib/docker/overlay2/4e868bfba66474c89e5afe795f9cba4e8c30f03b33e8270bceda5f8f718f1b72/diff,workdir=/var/lib/docker/overlay2/4e868bfba66474c89e5afe795f9cba4e8c30f03b33e8270bceda5f8f718f1b72/work,xino=off)
+overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/GPNKN2XS3ZRFTOLF67IUBLPJOG:/var/lib/docker/overlay2/l/7AIBFJWODCJRWMDLILRWOHB4Q7:/var/lib/docker/overlay2/l/V4OWAINIOOG5CUMPSAKM3VJZ5Q:/var/lib/docker/overlay2/l/LKGX4OMPAYSA47UDWAQDI2X5T3:/var/lib/docker/overlay2/l/NMVYKU22UFTYCR746XX4YXYZAJ:/var/lib/docker/overlay2/l/KH33H3HWPX7J54CSBPGEHVODCH:/var/lib/docker/overlay2/l/NPFWWK26TM64CQQJK3GUYF7RPO:/var/lib/docker/overlay2/l/A76FTIXHIEOSZVT427WCLXYTVP:/var/lib/docker/overlay2/l/SZJWHW7O4WNEYUYJ6S2DZ7M22K,upperdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/diff,workdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/work,xino=off)
@@ -23,0 +24,22 @@
+tmpfs on /proc/driver/nvidia type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=555)
+/dev/vda1 on /usr/bin/nvidia-smi type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/bin/nvidia-debugdump type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/bin/nvidia-persistenced type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/bin/nvidia-cuda-mps-control type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/bin/nvidia-cuda-mps-server type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-cfg.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libcuda.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-allocator.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+/dev/vda1 on /usr/lib/x86_64-linux-gnu/libnvidia-compiler.so.460.32.03 type ext4 (ro,nosuid,nodev,relatime)
+overlay on /usr/lib/x86_64-linux-gnu/libcuda.so.440.118.02 type overlay (ro,nosuid,nodev,relatime,lowerdir=/var/lib/docker/overlay2/l/GPNKN2XS3ZRFTOLF67IUBLPJOG:/var/lib/docker/overlay2/l/7AIBFJWODCJRWMDLILRWOHB4Q7:/var/lib/docker/overlay2/l/V4OWAINIOOG5CUMPSAKM3VJZ5Q:/var/lib/docker/overlay2/l/LKGX4OMPAYSA47UDWAQDI2X5T3:/var/lib/docker/overlay2/l/NMVYKU22UFTYCR746XX4YXYZAJ:/var/lib/docker/overlay2/l/KH33H3HWPX7J54CSBPGEHVODCH:/var/lib/docker/overlay2/l/NPFWWK26TM64CQQJK3GUYF7RPO:/var/lib/docker/overlay2/l/A76FTIXHIEOSZVT427WCLXYTVP:/var/lib/docker/overlay2/l/SZJWHW7O4WNEYUYJ6S2DZ7M22K,upperdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/diff,workdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/work,xino=off)
+overlay on /usr/lib/x86_64-linux-gnu/libnvidia-fatbinaryloader.so.440.118.02 type overlay (ro,nosuid,nodev,relatime,lowerdir=/var/lib/docker/overlay2/l/GPNKN2XS3ZRFTOLF67IUBLPJOG:/var/lib/docker/overlay2/l/7AIBFJWODCJRWMDLILRWOHB4Q7:/var/lib/docker/overlay2/l/V4OWAINIOOG5CUMPSAKM3VJZ5Q:/var/lib/docker/overlay2/l/LKGX4OMPAYSA47UDWAQDI2X5T3:/var/lib/docker/overlay2/l/NMVYKU22UFTYCR746XX4YXYZAJ:/var/lib/docker/overlay2/l/KH33H3HWPX7J54CSBPGEHVODCH:/var/lib/docker/overlay2/l/NPFWWK26TM64CQQJK3GUYF7RPO:/var/lib/docker/overlay2/l/A76FTIXHIEOSZVT427WCLXYTVP:/var/lib/docker/overlay2/l/SZJWHW7O4WNEYUYJ6S2DZ7M22K,upperdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/diff,workdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/work,xino=off)
+overlay on /usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.440.118.02 type overlay (ro,nosuid,nodev,relatime,lowerdir=/var/lib/docker/overlay2/l/GPNKN2XS3ZRFTOLF67IUBLPJOG:/var/lib/docker/overlay2/l/7AIBFJWODCJRWMDLILRWOHB4Q7:/var/lib/docker/overlay2/l/V4OWAINIOOG5CUMPSAKM3VJZ5Q:/var/lib/docker/overlay2/l/LKGX4OMPAYSA47UDWAQDI2X5T3:/var/lib/docker/overlay2/l/NMVYKU22UFTYCR746XX4YXYZAJ:/var/lib/docker/overlay2/l/KH33H3HWPX7J54CSBPGEHVODCH:/var/lib/docker/overlay2/l/NPFWWK26TM64CQQJK3GUYF7RPO:/var/lib/docker/overlay2/l/A76FTIXHIEOSZVT427WCLXYTVP:/var/lib/docker/overlay2/l/SZJWHW7O4WNEYUYJ6S2DZ7M22K,upperdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/diff,workdir=/var/lib/docker/overlay2/a4aaa0c474a91c9856d083d06618161c34516d4a3a361032c08f34c44bbab08f/work,xino=off)
+tmpfs on /run/nvidia-persistenced/socket type tmpfs (rw,nosuid,nodev,noexec,relatime,size=11344316k,mode=755)
+udev on /dev/nvidiactl type devtmpfs (ro,nosuid,noexec,relatime,size=56704416k,nr_inodes=14176104,mode=755)
+udev on /dev/nvidia-uvm type devtmpfs (ro,nosuid,noexec,relatime,size=56704416k,nr_inodes=14176104,mode=755)
+udev on /dev/nvidia-uvm-tools type devtmpfs (ro,nosuid,noexec,relatime,size=56704416k,nr_inodes=14176104,mode=755)
+udev on /dev/nvidia0 type devtmpfs (ro,nosuid,noexec,relatime,size=56704416k,nr_inodes=14176104,mode=755)
+proc on /proc/driver/nvidia/gpus/0000:00:05.0 type proc (ro,nosuid,nodev,noexec,relatime)
Docker での使い方
Docker から NVIDIA GPU を使う場合、主に 2 つの方法があります。
- Docker の
--gpus
オプションを利用する - Docker のランタイムで
nvidia
を利用する
--gpus
オプションの方が、後から実装されたものになります。このあたりの歴史的経緯については、NVIDIA Japan のNVIDIA Docker って今どうなってるの? (20.09 版) が詳しいです。
どちらも最終的には、Docker で使われているコンテナランタイム runc
の起動前に、NVIDIA Container Toolkit の preStart Hook で実行されるフック nvidia-container-runtime-hook
によって GPU の設定がなされます。それぞれの処理の流れは「NVIDIA Container Toolkit の処理の流れ」の章で説明します。
後方互換のため nvidia-docker
というコマンドも残っていますが、内部的には runtime に nvidia
を指定して Docker を呼出す(2 番目の方法)だけのラッパースクリプトとなっています。
1. Docker の --gpus
オプションを利用する
Docker 19.03 からは --gpus
オプションが使えるため、Docker 単体では --gpus
オプションを利用するのが良いでしょうです。次のコマンドは Docker で --gpus
オプションを指定した例です。
docker run --gpus=all nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda10.2
2. Docker のランタイムで nvidia
を利用する
Kubernetes は、v1.21 時点では Docker の --gpus
オプションに対応していません。そのため、Kubernetes で NVIDIA GPU を利用する場合は、nvidia
ランタイムをデフォルトランタイムに設定して利用する必要があります。(NVIDIA/k8s-device-plugin の Prerequisites を参照)
次のコマンドは、Docker で nvidia
ランタイムを指定する例です。なお、デフォルトランタイムを nvidia
に指定した場合 --runtime
指定は不要になります。環境変数で GPU の指定が必要です。
docker run --runtime=nvidia nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda10.2
デフォルトランタイムの指定
nvidia
ランタイムをデフォルトランタイムに指定する場合は、/etc/docker/daemon.json
に "default-runtime": "nvidia"
の設定を追加します。
{
"default-runtime": "nvidia",
# 上記を追加。他の設定はここでは省略
}
GPU デバイスとオプションの指定
--gpus
オプションと nvidia
ランタイムでは、NVIDIA GPU のオプションの指定の仕方が異なります。この指定がない場合、GPU デバイスなどはマウントされません。指定方法の詳細は、公式ドキュメントの Environment variables (OCI spec) をご覧ください。
利用方法 | 指定の仕方 |
---|---|
--gpus オプション |
--gpus オプションの引数 |
nvidia ランタイム |
NVIDIA_VISIBLE_DEVICES などの環境変数で指定 |
なお、--gpus
オプション指定時も内部的には環境変数が設定されています。
指定できるオプションは次の通りです。使用する GPU の列挙のみ必須です。ベースイメージに NVIDIA CUDA を指定している場合などは、ベースイメージ側で環境変数 (NVIDIA_VISIBLE_DEVICES=all
) を指定しているので、意識せず指定している場合があります。
-
使用する GPU の列挙
- どの GPU デバイスを利用するか指定する
-
all
の場合全て。0,1,2
のインデックスや UUID でも指定できる
-
ドライバ Capabilities
- マウントするドライバの種類を指定 e.g.
compute, utility
- マウントするドライバの種類を指定 e.g.
-
CUDA バージョンなどの制限
- 環境変数での設定のみ (
--gpus
オプションでは対応していない)
- 環境変数での設定のみ (
なお、Kubernetes で利用する場合、GPU 列挙の指定はリソースリクエストの nvidia.com/gpu
指定に応じて、NVIDIA device plugin 側で設定されます。
アーキテクチャ
公式ドキュメントの Architecture Overview を参考に、NVIDIA Container Toolkit のアーキテクチャを紹介します。
Docker の場合、NVIDIA Container Toolkit は主に次のコンポーネントで構成されています。コンポーネントは上から下の順で依存する形になっています。利用方法によって、本当に依存しているコンポーネントは変わってきます。しかし、公式ドキュメントの Which package should I use then? では、シンプルさと過去の互換性のため、すべてを含む nvidia-docker2
をトップレベルパッケージとして利用することが推奨されています。
細かくコンポーネントが分かれている理由は、関連するコンポーネントを見ると理解しやすいです。例えば最終的に NVIDIA GPU の設定を行う libnvidia-container
は、上位レイヤの OCI Spec には依存せず、どのコンテナランタイムでも利用できる作りになっています。
nvidia-docker2
Docker 用のトップレベルパッケージです。このパッケージを入れれば、以下のコンポーネントは依存パッケージとして自動的にインストールされます。NVIDIA ドライバ(e.g. cuda-drivers
)は別途インストールする必要があります。
このパッケージには、nvidia-container-runtime
のランタイム nvidia
を Docker に追加する以下の設定 (/etc/docker/daemon.json) が含まれています。すでに daemon.json
の設定ファイルがある場合は、手動での解決が必要になるのでご注意ください。(dpkg の場合は conffiles 指定となっている)
{
"runtimes": {
"nvidia": {
"path": "nvidia-container-runtime",
"runtimeArgs": []
}
}
}
nvidia-container-runtime
runc をラップした OCI ランタイム準拠の nvidia
ランタイムが含まれています。Docker に --runtime=nvidia
をつけたときに、このランタイムが呼ばれます。
この nvidia
ランタイムは、NVIDIA の設定処理を行うフック nvidia-container-runtime-hook
を preStart Hook に設定して、runc を実行するだけのラッパーとなっています。ソースコードも 200 行程度の main.go だけとシンプルです。
nvidia-container-runtime-hook
は次項のコンポーネント nvidia-container-toolkit
に含まれバイナリです。symlink となっており、実体は nvidia-container-toolkit
というバイナリです。
$ ls -l /usr/bin/nvidia-container-runtime-hook
lrwxrwxrwx 1 root root 33 Apr 13 09:12 /usr/bin/nvidia-container-runtime-hook -> /usr/bin/nvidia-container-toolkit
nvidia-container-toolkit
runc が呼び出す OCI の preStart Hook です。このフックは実際の NVIDIA 設定処理を行う nvidia-container-cli
を呼出します。Docker の--gpus
オプションを利用する場合、本当に依存しているのは nvidia-container-toolkit
以下のコンポーネントだけになります。GPU の設定は、環境変数経由で行われます。
OCI の preStart Hook として実装されているため runc
に限らず、OCI Runtime の仕様 に沿ったコンテナランタイムであれば、利用できる仕組みになっています。
libnvidia-container
Linux コンテナに対して NVIDIA GPU に必要な設定を実際に行う nvidia-container-cli
が含まれています。Ubuntu などのパッケージは libnvidia-container1
(ライブラリ) と libnvidia-container-tools
(CLI) に分かれています。C で書かれており、namespace など Linux コンテナの機能を直接利用しているため、Linux コンテナの実装にしない仕組みとなっています。GPU デバイスやライブラリのマウントだけでなく、ldconfig
の実行やカーネルモジュールのロードといった機能も含まれています。
以下 Docker での nvidia-container-cli
の呼び出される引数を整形した例です。
nvidia-container-cli \
--load-kmods \ # NVIDIA のカーネルモジュールをロードする
configure \ # コンテナの設定を行うサブコマンド
--ldconfig=@/sbin/ldconfig.real \ # ldconfig バイナリの指定
--device=all \ # 使用 GPU デバイスを指定
--utility \ # utility ドライバを有効にする
--compute \ # compute ドライバを有効にする
--pid=57316 \ # コンテナの PID を指定
/var/lib/docker/overlay2/6a683460c03ce2c1017d7b3efdb274c6c281e039da4de6e079b268e71832f21e/merged
# ↑rootfs の指定
また、list
というサブコマンドを使うと、GPU サポートに必要なファイルのリストを確認できます。
$ nvidia-container-cli list
/dev/nvidiactl
/dev/nvidia-uvm
/dev/nvidia-uvm-tools
/dev/nvidia-modeset
/dev/nvidia0
/usr/bin/nvidia-smi
/usr/bin/nvidia-debugdump
/usr/bin/nvidia-persistenced
/usr/bin/nvidia-cuda-mps-control
/usr/bin/nvidia-cuda-mps-server
/usr/lib/x86_64-linux-gnu/libnvidia-ml.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-cfg.so.460.56
/usr/lib/x86_64-linux-gnu/libcuda.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-opencl.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-allocator.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-compiler.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-ngx.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-encode.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-opticalflow.so.460.56
/usr/lib/x86_64-linux-gnu/libnvcuvid.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-eglcore.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-glcore.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-tls.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-glsi.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-fbc.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-ifr.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-rtcore.so.460.56
/usr/lib/x86_64-linux-gnu/libnvoptix.so.460.56
/usr/lib/x86_64-linux-gnu/libGLX_nvidia.so.460.56
/usr/lib/x86_64-linux-gnu/libEGL_nvidia.so.460.56
/usr/lib/x86_64-linux-gnu/libGLESv2_nvidia.so.460.56
/usr/lib/x86_64-linux-gnu/libGLESv1_CM_nvidia.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-glvkspirv.so.460.56
/usr/lib/x86_64-linux-gnu/libnvidia-cbl.so.460.56
/run/nvidia-persistenced/socket
NVIDIA Container Toolkit の処理の流れ
Docker の --gpus
オプションを利用した場合と、ランタイムで nvidia
を利用した場合で若干処理の流れが異なります。どちらも最終的には、nvidia-container-toolkit
が runc の preStart Hook として設定され、実際の設定を行う nvidia-container-cli
が呼ばれることになります。
Docker の復習
docker run
でコンテナを実行した場合、主に次のようにコンポーネントが呼ばれます。最終的にコンテナを実行するランタイムが runc です。(shim は省略しています)
コンポーネント | 概要 | レポジトリ |
---|---|---|
docker CLI | Docker の CLI | docker/cli |
dockerd | Docker Engine | moby/moby |
containerd | 実際のコンテナやイメージ管理を行う | containerd/containerd |
runc | OCI Runtime Spec 準拠のコンテナランタイム | opencontainers/runc |
--gpus
オプションを利用した場合
- Docker CLI
-
--gpus
オプションが指定される -
Capabilities
のgpu
などが DeviceRequests に設定される - DeviceRequests が Docker Engine に設定として渡される
-
- Docker Engine
- deviceDriver 指定が空のとき、自動的に対応可能なドライバが選択される (20.10.6 時点では gpu は
nvidia
のみ) - deviceDriver として登録された setNvidiaGPUs() が呼ばれる
-
nvidia-container-runtime-hook
を preStart Hook に設定する
- deviceDriver 指定が空のとき、自動的に対応可能なドライバが選択される (20.10.6 時点では gpu は
- containerd
- runc を呼出す
- runc
- preStart Hook に設定された
nvidia-container-runtime-hook
を呼出す - フック内で
nvidia-container-cli
が呼び出される - GPU が準備された状態でコンテナプロセスが起動する
- preStart Hook に設定された
以下は --gpus=all
を指定したコンテナプロセスを docker inspect
で見た DeviceRequests の設定です。Capabilities
に "gpu" が指定されていることがわかります。
"DeviceRequests": [
{
"Driver": "",
"Count": -1,
"DeviceIDs": null,
"Capabilities": [
[
"gpu"
]
],
"Options": {}
}
],
nvidia
ランタイムを利用した場合
- Docker CLI
-
--runtime
オプションでnvidia
を指定 (デフォルトランタイムの場合は不要) -
Runtime
オプションにnvidia
が指定される Docker Engine に渡される
-
- Docker Engine
- containerd のランタイムとして
nvidia
ランタイムを指定
- containerd のランタイムとして
- containerd
- runc の代わりに
nvidia
ランタイムを呼出す
- runc の代わりに
-
nvidia ランタイム
-
nvidia-container-runtime-hook
を preStart Hook に設定する -
runc が呼び出される
- preStart Hook に設定された
nvidia-container-runtime-hook
を呼出す - フック内で
nvidia-container-cli
が呼び出される - GPU が準備された状態でコンテナプロセスが起動する
- preStart Hook に設定された
-
補足: containerd
単体でも NVIDIA GPU はサポートされる
containerd 単体でも NVIDIA GPU オプションはサポートされています。containerd の CLI である ctr
コマンドでも --gpus
オプションをサポートしています。
# 実行用イメージを pull
$ sudo ctr image pull docker.io/library/debian:buster-slim
# --gpus オプション付きで実行。nvidia-smi で GPU が有効であることを確認できる
$ sudo ctr run --gpus 0 --rm docker.io/library/debian:buster-slim debian1 nvidia-smi
Tue Apr 20 04:45:47 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
...
-
ctr
コマンド -
--gpus
オプションが指定される - containerd
- NVIDIA 用の WithGPUs() が呼ばる
-
nvidia-container-cli
が containerd の oci-hook サブコマンド経由で preStart Hook として登録される- oci-hook を使うのは動的に引数を渡せるようにするため
- runc
- preStart Hook に設定された oci-hook を呼出す
- oci-hook 内で
nvidia-container-cli
が呼び出される - GPU が準備された状態でコンテナプロセスが起動する
Tips: nvidia-smi の実行
コンテナ内で NVIDA GPU が使えることを確認する場合、nvidia-smi
(NVIDIA System Management Interface) コマンドを使うことがよくあります。
公式ドキュメントの Setting up NVIDIA Container Toolkit でも以下のコマンドが確認手順として載っています。
sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
このとき、nvidia-smi
コマンドはマウントされたホストのものが使われています。次のように --gpus
オプションをなしにすると、 nvidia/cuda:11.0-base
イメージに nvidia-smi
のバイナリが含まれないことを確認できます。
sudo docker run --rm --runtime=runc nvidia/cuda:11.0-base which nvidia-smi
# --gpus オプションなしだと何も表示されない
--gpus
オプションさえ指定されていれば、他のコンテナイメージでも nvidia-smi
コマンドを実行できます。例えば、以下は debian:buster-slim
イメージを使って実行した例です。外部のコンテナレジストリにアクセスできないようなエアギャップ環境だと、この方法を知っていると便利です。
sudo docker run --rm --gpus all debian:buster-slim nvidia-smi
Fri Apr 9 00:49:57 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Tesla P4 On | 00000000:00:05.0 Off | 0 |
| N/A 36C P8 7W / 75W | 0MiB / 7611MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
まとめ
-
なぜ NVIDIA Container Toolkit が必要か
- ホストの GPU デバイスや CUDA ライブラリをマウントする必要があるため
- マウント先は可変であり、設定は煩雑
- NVIDIA Container Toolkit はマウントなど事前準備を自動で行ってくれるもの
-
Docker での使い方
- 「
--gpus
オプションの指定」と「nvidia
ランタイムの使用」の 2 パターンがある - Dokcer 単体は
--gpus
が推奨、Kubernetes で使う場合はnvidia
ランタイムをデフォルトにする必要がある
- 「
-
GPU デバイスとオプションの指定
-
--gpus
オプションはオプションの引数で指定 -
nvidia
ランタイムは環境変数で指定
-
-
アーキテクチャ
- 主に
nvidia-docker2
,nvidia-container-runtime
,nvidia-container-toolkit
,libnvidia-container
の 4 つで構成 - Docker の場合はトップレベルパッケージの
nvidia-docker2
をインストールするのが推奨される - 最終的なコンテナ設定は
nvidia-container-cli
が行う
- 主に
-
NVIDIA Container Toolkit の処理の流れ
- Docker の
--gpus
オプションを利用した場合と、ランタイムでnvidia
を利用した場合で若干異なる - どちらも
nvidia-container-toolkit
が runc の preStart Hook として設定される - containerd 単体でも NVIDIA GPU はサポートされる
- Docker の
-
Tips: nvidia-smi の実行
-
nvidia-smi
は--gpus
オプションを指定すれば、コンテナイメージは任意のもので良い
-
確認した環境
この記事で確認に使った環境です。
- OS: Ubuntu 20.04
- GPU: NVIDIA T4
- パッケージ
-
cuda-drivers
: 460.32.03-1 -
nvidia-docker2
: 2.5.0-1 -
nvidia-container-toolkit
: 1.4.2-1 -
nvidia-container-runtime
: 3.4.2-1 -
libnvidia-container1
: 1.3.3-1 -
docker-ce
: 5:20.10.6~3-0~ubuntu-focal (20.10.6) -
containerd.io
: 1.4.4-1
-
参考
- Docker Engine の GPU サポート issue, PR
- Docker CLI の GPU サポートの issue, PR
- containerd の GPU サポートの PR