docker-composeでGPU環境(+PyTorch)を構築する

# はじめに

Google Colabratory便利ですよね。
特に何も設定しなくても無料でGPUを利用できます。

しかし、Colabratoryは不自由な部分もありできたらローカルにGPU環境を用意したいものです。
過去にGPU対応に挑戦したことがありましたが、3度ほど挑戦して失敗。

そしてつい先日WSL2のGPU対応によりWSL2でGPUが認識するようになりました。
設定は以下の記事を参考にしてみてください。

https://qiita.com/ksasaki/items/ee864abd74f95fea1efa

https://qiita.com/Sicut_study/items/d9d30e00320a4f90ab43

また、WSLのDocker環境構築は以下の記事で行えます。

https://qiita.com/Sicut_study/items/cb198375011581f3ef2c

ここまでで、WSL2上で`nvidia-smi`をすると認識しています。
今回はここからDockerコンテナ上でGPUを認識するように設定していきます。

# 環境
- GPU: GEFORCE GTX 1650
- Ubuntu20.04 LTS
- Windows

# 問題

まず、WSL2でGPUが認識する状態でいつも通りにコンテナを立ち上げます。
そして、`nvidia-smi`コマンドをコンテナ内でたたくと、

```
root@0da4a867f15b:/# nvidia-smi
bash: nvidia-smi: command not found
```

とエラーが出てしまいます。今回はコンテナ内でGPUを使えるようにしていきます。

# 試行錯誤

## 1. イメージを変更する
まず、Dockerのイメージをnvidiaのイメージを利用してみました。

[NVIDIA CUDA](https://hub.docker.com/r/nvidia/cuda)

コンテナを起動して、`nvidia-smi`を叩きましたがエラーは変わらず。。

ちなみにDockerfileではGPU対応のイメージを利用しています。
https://github.com/Kaggle/docker-python

## 2. Docker runでコンテナを立てる

[DockerコンテナでもGPUが使いたい](https://blog.mahoroi.com/posts/2019/12/docker-gpus-nvidia/)

こちらの記事のコマンドを実行してみました。

```
$ docker run --gpus all ubuntu nvidia-smi
```

```
jinwatanabe@DESKTOP-4DGUR4Q:~/workspace/data_analysis$ docker run --gpus all ubuntu nvidia-smi
Tue Oct 19 10:56:31 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 495.29.05    Driver Version: 496.13       CUDA Version: 11.5     |
|-------------------------------+----------------------+----------------------+
| 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  NVIDIA GeForce ...  On   | 00000000:01:00.0 Off |                  N/A |
| N/A   43C    P8     5W /  N/A |    134MiB /  4096MiB |     N/A      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

するとコンテナ内で認識していました。

``--gpu`というオプションで起動するとGPUがコンテナ内で使えるようになるそうでした。
これをdocker-compose.ymlで再現できるように調べていきました。

## 3. runtimeの設定

先ほどの記事にdocker-composeでのGPU設定があったので行います。

```
$ sudo apt install nvidia-container-runtime
```

nvidia-container-runtimeをインストールして、`/etc/docker/daemon.json`を設定

```daemon.json
{
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  }
}
```
そして、docker-compose.ymlに環境変数と`runtime`を設定

```docker-compose.yml
version: "3"
services:
    jupyter:
        build: ./libraries
        container_name: jupyter
        runtime: nvidia # 追加
        volumes:
            - ./notebook/:/tmp/working
        ports:
            - 80:8888
        environment: # 追加
            NVIDIA_VISIBLE_DEVICES: all # 追加
            NVIDIA_DRIVER_CAPABILITIES: all # 追加
        command: jupyter notebook --ip=0.0.0.0 --allow-root --no-browser
```

これで、docker-compose upしたところ、以下のようなエラーが発生。

```
Unsupported config option for services.nvidia: 'runtime'
```

どうも`runtime`が利用できないよう。

調べてみると、`version: "3"`にしているとruntimeが使えないよう。
ですので、設定ファイルに情報を追加します。

```daemon.json
 {
  "default-runtime": "nvidia", # 追加
  "runtimes": {
    "nvidia": {
      "path": "nvidia-container-runtime",
      "runtimeArgs": []
    }
  }
```

再起動して、docker-compose.ymlからruntimeの行を消して起動。

しかし、nvidia-smiコマンドは動かず。。。

## 4. deplpoyの設定

nvidia-dockerをdocker-composeで動かす(ver3系)

こちらの記事を参考にdocker-compose.ymlに以下を追加します。

```docker-compose.yml
(省略)
    deploy:
      resources:
        reservations:
          devices:
           - driver: nvidia
             capabilities: [utility, compute, video]
```

この状態で、コンテナを起動したところ、
deploy.resources.reservations.deviceでエラーになりました。

```
# こんな感じ (転載)
ERROR: The Compose file './docker-compose.yml' is invalid because:
services.testserver.deploy.resources.reservations value Additional properties are not allowed ('devices' was unexpected)
```

調べてみると以下の記事にたどり着きます。

[" 'devices' properties is not allowed" while creating docker-compose with nvidia gpu](https://stackoverflow.com/questions/66298638/devices-properties-is-not-allowed-while-creating-docker-compose-with-nvidia)

この記事によると`docker-compose`のバージョンに起因しているとのこと、この時点でインストールしていたのが`1.26.0`だったので、バージョンアップしました。

```
$ sudo rm -rf docker-compose
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose -v
```

これで`1.29.0`にバージョンアップできました。

この状態で、コンテナを立ち上げて、`nvidia-smi`を叩くと、

```
root@270fc08efdcb:/# nvidia-smi
Tue Oct 19 11:23:28 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 495.29.05    Driver Version: 496.13       CUDA Version: 11.5     |
|-------------------------------+----------------------+----------------------+
| 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  NVIDIA GeForce ...  On   | 00000000:01:00.0 Off |                  N/A |
| N/A   43C    P8    10W /  N/A |    134MiB /  4096MiB |     N/A      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

コンテナの中で認識されています！

次にPyTorchでGPUが使えるか確認します。

```
$ python
$ import torch
$ print(torch.cuda.is_available())

# TRUE
```

うまくいきました。

また、docker-composeを更新したところ、`runtime`がエラーにならなくなりました。
バージョンが原因でした。runtimeでもGPUを認識することができるのでこちらを採用することにしました。

# 解決方法

解決するための方法をまとめます。

NVIDIA container ToolKitをインストール

```
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ curl -s -L https://nvidia.github.io/libnvidia-container/experimental/$distribution/libnvidia-container-experimental.list | sudo tee /etc/apt/sources.list.d/libnvidia-container-experimental.list

$ sudo apt-get update
$ sudo apt-get install -y nvidia-docker2
$ sudo usermod -aG docker $USER
```

そのあとでDockerを再起動する。

```
$ sudo service docker restart
```

しないと以下のようなエラーになります。
[DockerでGPUを使おうとしたらError response from daemon: linux runtime spec devices: could not select device driver “” with capabilities: [[gpu]]](https://cocoinit23.com/docker-gpu-error-response-from-daemon-linux-runtime-spec-devices-could-not-select-device-driver-with-capabilities-gpu/)

```
$ docker run --gpus all ubuntu nvidia-smi

# エラー
Error response from daemon: linux runtime spec devices: could not select device driver “” with capabilities: [[gpu]]
```

次にdocker-composeのバージョンを上げます。

```
$ sudo rm -rf docker-compose
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
```

そのあとで、docker-compose.ymlに`runtime`とGPUの環境変数を追加します。

```docker-compose.yml
(省略)
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      - NVIDIA_DRIVER_CAPABILITIES=all
```

そしてDockerfileのイメージは`Python:3.7`や`nvidia/cuda:11.0-devel-ubuntu20.04`を利用する。
`gcr.io/kaggle-gpu-images/python`はなぜか利用できなかった。。


結果以下のようなPyTorchの環境が出来上がりました。

```Dockerfile
FROM jupyter/datascience-notebook

ENV GRANT_SUDO=yes
USER root

RUN apt-get update
RUN apt-get install -y vim
RUN pip install \
		japanize-matplotlib \
		torch

COPY ./settings/jupyter_notebook_config.py .jupyter
```

```docker-compose.yml
version: "3"
services:
    jupyter:
        build: ./libraries
        container_name: jupyter
        runtime: nvidia
        volumes:
            - ./notebook/:/tmp/working
        ports:
            - 80:8888
        environment:
            NVIDIA_VISIBLE_DEVICES: all
            NVIDIA_DRIVER_CAPABILITIES: all
```

詳しい使い方は以下のリポジトリから確認してください。

https://github.com/jinwatanabe/Docker_gpu_env

# おわりに

長い長い戦いがついに終わりました。

https://twitter.com/Sicut_study/status/1405564579178967047


# 参考
- [待ってました CUDA on WSL 2](https://qiita.com/Sicut_study/items/d9d30e00320a4f90ab43)
- [【環境構築】docker-composeでGPU(on Ubuntu20.04)を使用するためには](https://gangannikki.hatenadiary.jp/entry/2020/09/29/203000)
- [Kaggle docker image](https://github.com/Kaggle/docker-python)
- [" 'devices' properties is not allowed" while creating docker-compose with nvidia gpu](https://stackoverflow.com/questions/66298638/devices-properties-is-not-allowed-while-creating-docker-compose-with-nvidia)
- [PyTorchでGPU情報を確認（使用可能か、デバイス数など）](https://note.nkmk.me/python-pytorch-cuda-is-available-device-count/)
- [DockerでGPUを使おうとしたらError response from daemon: linux runtime spec devices: could not select device driver “” with capabilities: [[gpu]]](https://cocoinit23.com/docker-gpu-error-response-from-daemon-linux-runtime-spec-devices-could-not-select-device-driver-with-capabilities-gpu/)