138
88

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NVIDIA Container Toolkit (NVIDIA Docker) は何をしてくれるか

Last updated at Posted at 2021-04-23

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 が使えるように、マウントなどの準備を自動で行ってくれるものです。

image.png

ホスト側の 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 つの方法があります。

  1. Docker の --gpus オプションを利用する
  2. 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" の設定を追加します。

/etc/docker/daemon.json
{
    "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) を指定しているので、意識せず指定している場合があります。

なお、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 をトップレベルパッケージとして利用することが推奨されています。

image.png

細かくコンポーネントが分かれている理由は、関連するコンポーネントを見ると理解しやすいです。例えば最終的に 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-hookpreStart 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

image.png

--gpus オプションを利用した場合

  1. Docker CLI
    1. --gpus オプションが指定される
    2. Capabilitiesgpu などが DeviceRequests に設定される
    3. DeviceRequests が Docker Engine に設定として渡される
  2. Docker Engine
    1. deviceDriver 指定が空のとき、自動的に対応可能なドライバが選択される (20.10.6 時点では gpu は nvidia のみ)
    2. deviceDriver として登録された setNvidiaGPUs() が呼ばれる
    3. nvidia-container-runtime-hookpreStart Hook に設定する
  3. containerd
    1. runc を呼出す
  4. runc
    1. preStart Hook に設定された nvidia-container-runtime-hook を呼出す
    2. フック内で nvidia-container-cli呼び出される
    3. GPU が準備された状態でコンテナプロセスが起動する :tada:

image.png

以下は --gpus=all を指定したコンテナプロセスを docker inspect で見た DeviceRequests の設定です。Capabilities に "gpu" が指定されていることがわかります。

            "DeviceRequests": [
                {
                    "Driver": "",
                    "Count": -1,
                    "DeviceIDs": null,
                    "Capabilities": [
                        [
                            "gpu"
                        ]
                    ],
                    "Options": {}
                }
            ],

nvidia ランタイムを利用した場合

  1. Docker CLI
    1. --runtime オプションで nvidia を指定 (デフォルトランタイムの場合は不要)
    2. Runtime オプションに nvidia が指定される Docker Engine に渡される
  2. Docker Engine
    1. containerd のランタイムとして nvidia ランタイムを指定
  3. containerd
    1. runc の代わりに nvidia ランタイムを呼出す
  4. nvidia ランタイム
    1. nvidia-container-runtime-hookpreStart Hook に設定する
    2. runc が呼び出される
      1. preStart Hook に設定された nvidia-container-runtime-hook を呼出す
      2. フック内で nvidia-container-cli呼び出される
      3. GPU が準備された状態でコンテナプロセスが起動する :tada:

image.png

補足: 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     |
|-------------------------------+----------------------+----------------------+
...
  1. ctr コマンド
  2. --gpus オプションが指定される
  3. containerd
  4. NVIDIA 用の WithGPUs() が呼ばる
  5. nvidia-container-cli が containerd の oci-hook サブコマンド経由で preStart Hook として登録される
    1. oci-hook を使うのは動的に引数を渡せるようにするため
  6. runc
  7. preStart Hook に設定された oci-hook を呼出す
  8. oci-hook 内で nvidia-container-cli呼び出される
  9. GPU が準備された状態でコンテナプロセスが起動する :tada:

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 はサポートされる
  • 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

参考

138
88
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
138
88

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?