nvidia-dockerのbuild時にGPU、関連ライブラリを有効にする

## 2行まとめ

* nvidia-dockerバージョン2.0系では`docker build`時にもGPU、関連ライブラリを使うことができる。
* そのためには、Dockerデーモンの設定を変更し、デフォルトランタイムを`nvidia`に切り替える必要がある。

## 概要

[nvidia-docker](https://github.com/NVIDIA/nvidia-docker)のバージョン2.0系では、Dockerの「ランタイム」という仕組みを用いてGPUデバイスや関連ライブラリのマウントを行っています。（バージョン1.0系では`nvidia-docker`というラッパコマンドが存在しました）
通常、ランタイムはその名の通り、`docker run`時に指定します。そのため`docker build`時には有効にならず、ビルド時にGPUや関連ライブラリを必要とするソフトウェアをDockerイメージ化する場合に困ることになります。

そんな中、nvidia-dockerの公式Wiki「[Advanced topics](https://github.com/NVIDIA/nvidia-docker/wiki/Advanced-topics)」に「デフォルトランタイムを変更するとGPUにアクセスできるよ」との記述を見つけたので実験してみました。

以下、引用です。

> Default runtime
>
> The default runtime used by the Docker® Engine is `runc`, our runtime can become the default one by configuring the docker daemon with `--default-runtime=nvidia`. Doing so will remove the need to add the `--runtime=nvidia` argument to docker run. It is also the only way to have GPU access during docker build.

## 環境

評価を行った環境は、以下の通りです。

```
yuya@host$ uname -a
Linux xxx 4.4.0-83-generic #106-Ubuntu SMP Mon Jun 26 17:54:43 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

yuya@host$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.3 LTS"

yuya@host$ docker version
Client:
 Version:       17.12.0-ce
 API version:   1.35
 Go version:    go1.9.2
 Git commit:    c97c6d6
 Built: Wed Dec 27 20:11:19 2017
 OS/Arch:       linux/amd64

Server:
 Engine:
  Version:      17.12.0-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.2
  Git commit:   c97c6d6
  Built:        Wed Dec 27 20:09:53 2017
  OS/Arch:      linux/amd64
  Experimental: false

yuya@host$ dpkg -l | grep nvidia-docker
ii  nvidia-docker2    2.0.2+docker17.12.0-1    all    nvidia-docker CLI wrapper
```

## デフォルトランタイムが`runc`の場合（変更前）

デフォルトランタイムは`docker info`コマンドで確認することができます。変更前のデフォルトランタイムは`runc`です。

```
yuya@host$ docker info 2> /dev/null | grep -i runtime
Runtimes: nvidia runc
Default Runtime: runc

yuya@host$ sudo cat /etc/docker/daemon.json
{
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}
```

デフォルトランタイムが`runc`の場合でも、`docker run`コマンドに`--runtime nvidia`オプションを指定することで`nvidia`ランタイムを使用することができます。実行例を以下に示します。GPUが認識されていること、関連ライブラリにアクセスできることが確認できます。

```
yuya@host$ docker pull nvidia/cuda:latest
latest: Pulling from nvidia/cuda
Digest: sha256:74570aef804e30486eb74b284fd3091d14ca7144736c44fb48c13e5e6afb963f
Status: Image is up to date for nvidia/cuda:latest

yuya@host$ docker run --interactive --tty --rm --runtime nvidia nvidia/cuda:latest nvidia-smi
Mon Apr 16 11:02:32 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 108...  Off  | 00000000:03:00.0  On |                  N/A |
|  0%   34C    P8    11W / 280W |     61MiB / 11169MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

yuya@host$ docker run --interactive --tty --rm --runtime nvidia nvidia/cuda:latest ls -l /usr/lib/x86_64-linux-gnu/libcuda.so
lrwxrwxrwx 1 root root 17 Apr 16 11:12 /usr/lib/x86_64-linux-gnu/libcuda.so -> libcuda.so.387.34
```

なお、ランタイムを指定しない場合（デフォルトの`runc`を使う場合）、`nvidia-smi`も`libcuda.so`も参照できません。

```
yuya@host$ docker run --interactive --tty --rm nvidia/cuda:latest nvidia-smi
docker: Error response from daemon: OCI runtime create failed: container_linux.go:296: starting container process caused "exec: \"nvidia-smi\": executable file not found in $PATH": unknown.

yuya@host$ docker run --interactive --tty --rm nvidia/cuda:latest ls -l /usr/lib/x86_64-linux-gnu/libcuda.so
ls: cannot access '/usr/lib/x86_64-linux-gnu/libcuda.so': No such file or directory
```

また、`docker build`時にも同様に、`nvidia-smi`や`libcuda.so`を参照できません。

```
yuya@host$ cat Dockerfile
FROM nvidia/cuda:latest
RUN ls -l /usr/lib/x86_64-linux-gnu/libcuda.so || true
RUN nvidia-smi

yuya@host$ docker build --no-cache --tag ${USER}/runtime_runc .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM nvidia/cuda:latest
 ---> 9b8a74e9dc76
Step 2/3 : RUN ls -l /usr/lib/x86_64-linux-gnu/libcuda.so || true
 ---> Running in 6f9187fc3fab
ls: cannot access '/usr/lib/x86_64-linux-gnu/libcuda.so': No such file or directory
Removing intermediate container 6f9187fc3fab
 ---> c0b7ad290600
Step 3/3 : RUN nvidia-smi
 ---> Running in cb7a837d9096
/bin/sh: 1: nvidia-smi: not found
The command '/bin/sh -c nvidia-smi' returned a non-zero code: 127
```

## デフォルトランタイムが`nvidia`の場合（変更後）

次に、デフォルトランタイムが`nvidia`の場合を見てみます。まずは、Dockerデーモンの設定ファイル`/etc/docker/daemon.json`を変更し、設定を反映するためにDockerデーモンを再起動します。`docker info`コマンドの結果から、デフォルトランタイムが`nvidia`に切り替わっていることが確認できます。

```
yuya@host$ sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.20180416
yuya@host$ sudo vi /etc/docker/daemon.json
yuya@host$ sudo cat /etc/docker/daemon.json
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}

yuya@host$ sudo diff -U 2 /etc/docker/daemon.json.20180416 /etc/docker/daemon.json
--- /etc/docker/daemon.json.20180416    2018-04-16 19:10:59.018030162 +0900
+++ /etc/docker/daemon.json     2018-04-16 19:11:50.002551905 +0900
@@ -1,3 +1,4 @@
 {
+    "default-runtime": "nvidia",
     "runtimes": {
         "nvidia": {

yuya@host$ sudo systemctl restart docker
yuya@host$ docker info 2> /dev/null | grep -i runtime
Runtimes: nvidia runc
Default Runtime: nvidia
```

デフォルトランタイムが`nvidia`に切り替わったため、`docker run`時に`--runtime nvidia`を指定しなくても`nvidia`ランタイムが使用されます。実行例を以下に示します。

```
yuya@host$ docker run --interactive --tty --rm nvidia/cuda:latest nvidia-smi
Wed Apr 18 02:21:18 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 108...  Off  | 00000000:03:00.0  On |                  N/A |
|  0%   34C    P8    10W / 280W |     61MiB / 11169MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

yuya@host$ docker run --interactive --tty --rm nvidia/cuda:latest ls -l /usr/lib/x86_64-linux-gnu/libcuda.so
lrwxrwxrwx 1 root root 17 Apr 18 02:21 /usr/lib/x86_64-linux-gnu/libcuda.so -> libcuda.so.387.34
```

デフォルトランタイムは`docker build`時にも使用されます。そのため、デフォルトランタイムが`runc`の時には参照できなかった`nvidia-smi`と`libcuda.so`が参照できています。実行例を以下に示します。

```
yuya@host$ cat Dockerfile
FROM nvidia/cuda:latest
RUN ls -l /usr/lib/x86_64-linux-gnu/libcuda.so || true
RUN nvidia-smi

yuya@host$ docker build --no-cache --tag ${USER}/runtime_nvidia .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM nvidia/cuda:latest
 ---> 9b8a74e9dc76
Step 2/3 : RUN ls -l /usr/lib/x86_64-linux-gnu/libcuda.so || true
 ---> Running in 6be189daae11
lrwxrwxrwx 1 root root 17 Apr 18 02:40 /usr/lib/x86_64-linux-gnu/libcuda.so -> libcuda.so.387.34
Removing intermediate container 6be189daae11
 ---> e1b2e960906a
Step 3/3 : RUN nvidia-smi
 ---> Running in d140ec1d9142
Wed Apr 18 02:40:19 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 387.34                 Driver Version: 387.34                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 108...  Off  | 00000000:03:00.0  On |                  N/A |
|  0%   34C    P8    11W / 280W |     61MiB / 11169MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
Removing intermediate container d140ec1d9142
 ---> 260bc963500d
Successfully built 260bc963500d
Successfully tagged yuya/runtime_nvidia:latest
```

これで`docker build`時にGPUや関連ライブラリを使うソフトウェアをDockerイメージ化できますね！

## 参考

* [NVIDIA/nvidia-docker: Build and run Docker containers leveraging NVIDIA GPUs](https://github.com/NVIDIA/nvidia-docker)
    * nvidia-dockerの本家サイト。