Docker上でGPUに対応したディープラーニング(chainer)の環境作成に一苦労した話

## やりたかったこと

DockerでGPUに対応したchainerの環境を作りたかった



## 環境

OS/Arch: linux/amd64
cuda: 11.0
Docker version: 19.03.13
nvidia-docker2
chainer: 7.7.0



## やったこと

まず最初に[chainer公式のDockerfile](https://github.com/chainer/chainer/blob/master/docker/python3/Dockerfile)を参考に以下のようなDockerfileを作成しました．
(少し余計なライブラリも含まれています)

```dockerfile
FROM nvidia/cuda:11.0-base
# Install basics
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends \
    python3-dev \
    python3-pip \
    python3-wheel \
    python3-setuptools \
    cmake \
    wget \
    g++
RUN rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

# Install python library
RUN pip3 install -U pip
RUN pip install chainer==7.7.0
RUN pip install cupy-cuda110==8.4.0
```



このDockerfileを`chainer`という名前でビルドして，

```sh
docker build -t chainer .
```

インタラクティブで起動します．この時`--gpus=1`を指定してGPUが使えるようにします．

```sh
docker run --it --gpus=1 chainer
```

この状態で`nvidia-smi`を叩くと

```txt
$ nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 450.80.02    Driver Version: 450.80.02    CUDA Version: 11.0     |
|-------------------------------+----------------------+----------------------+
| 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  A100-PCIE-40GB      On   | 00000000:41:00.0 Off |                    0 |
| N/A   30C    P0    32W / 250W |      0MiB / 40537MiB |      0%      Default |
|                               |                      |             Disabled |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

きちんとGPUがコンテナからみえていることがわかりました！

## 遭遇したエラー

続いてpythonを起動してchainerでGPUが使えるかを以下のように確認したところ，

```python
$ python3
>>> import chainer
>>> chainer.print_runtime_info()
Platform: Linux-3.10.0-1160.6.1.el7.x86_64-x86_64-with-glibc2.29
Chainer: 7.7.0
ChainerX: Not Available
NumPy: 1.20.1
CuPy: Not Available
iDeep: Not Available
```

`CuPy`がNot AvailableになっておりGPUが認識されていないようです．

試しに`CuPy`をインポートできるか試したところ

```python
$ python3
>>> import cupy
(途中略)
ImportError: libcublas.so.11: cannot open shared object file: No such file or directory
```

`libcublas.so.11`が見つからないのでインポートできないと言われました．

`libcublas.so.11`って何？という感じだったのですが，どうやらcuda関連のライブラリらしいです．

cuda関連のライブラリはコンテナ上の`/usr/local/cuda/lib64`に置いてあるものらしいのですが(環境による?)，確認してみても`libcublas.so.11`は見つかりませんでした．

```sh
# コンテナ上
$ ls /usr/local/cuda/lib64
libcudart.so.11.0  libcudart.so.11.0.221
# libcublas.so.11が無い
```

そこで，ホストマシン上に`libcublas.so.11`があるかを確認したところ，

```sh
# ホストマシン上
$ ls /usr/local/cuda/lib64 | grep libcublas.so.11
libcublas.so.11
```

ホストマシン上には`libcublas.so.11`は確かに存在していることが分かりました．

これをどうやってコンテナ上に持っていけば良いんだ？ボリュームを使ってマウントすれば良いのか？などと最初に考えました．(実際にボリュームを使ってみたらいけました.`docker run -it -gpus=1 -v /usr/local/cuda:/usr/local/cuda chainer`)

しかしわざわざマウントするのは面倒で，何か他のやり方があるだろうと思って色々探した結果こんな情報を見つけました．

![スクリーンショット 2021-02-17 19.29.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/401124/aec6be80-7d93-6f74-66bf-ae1cec9ad2dc.png)
https://github.com/NVIDIA/nvidia-docker/issues/1160

nvccがコンテナから使えないのは何故？あとcudnn関連のファイルがコンテナにないのはなぜ？ということを質問している人がいて，

それに対する回答がCUDA devolopment toolsにアクセスするには`devel`イメージを使わないといけない．ということでした．

もしかして`libcublas.so.11`が見つからないのも同じ原因？と考えてDockerfileに記述していたイメージを以下のように`nvidia/cuda:11.0-base`から`nvidia/cuda:11.0-devel`に変更しました．

```dockerfile
# FROM nvidia/cuda:11.0-base baseではなくdevelにする
FROM nvidia/cuda:11.0-devel
```

そしてコンテナを起動して`/usr/local/cuda/lib64`を確認したところ`libcublas.so.11`が置いてありました！

```sh
# コンテナ上
$ ls /usr/local/cuda/lib64 | grep libcublas.so.11
libcublas.so.11
```

この状態でcupyをインポートしたところ，正常にインポートされました！

どうやらcudaのイメージには`base`, `runtime`, `devel`の3種類のイメージがあるようです．

`base`にはCUDAのランタイム，`runtime`には`base`+CUDAのMATHライブラリとNCCL, `devel`には`runtime`+ヘッダーとCUDAのイメージをビルドするための開発ツールが含まれているようです．

詳細は[dockerhub nvidia/cuda](https://hub.docker.com/r/nvidia/cuda )に書いてありました．

今回の場合，`runtime`または`devel`を選択すれば良かったようです．

これでcupyを使えるようになりました！



## それでもchainerがcupyを認識できない

cupyは正しくimportできるのに，chainerからcupyを使えなかった

```python
$ python3
>>> import cupy
>>> import chainer
>>> chainer.print_runtime_info()
Platform: Linux-3.10.0-1160.6.1.el7.x86_64-x86_64-with-glibc2.29
Chainer: 7.7.0
ChainerX: Not Available
NumPy: 1.20.1
CuPy: Not Available
iDeep: Not Available
```

これに関しては全く同じ現象に遭遇している方がいました．[Chainer 7.7.0 のインストールでハマった話](https://izumo27.hatenablog.com/entry/2020/11/21/195100)

cupyのversion 8.0以降の問題らしいので，それ以前のバージョンをインストールすればいいそうです．

```sh
pip install cupy-cuda110==7.8.0
```

上記コマンドでcupyをインストールしたところ，

```sh
$ python3
>>> import chainer
>>> chainer.print_runtime_info()
Platform: Linux-3.10.0-1160.6.1.el7.x86_64-x86_64-with-glibc2.29
Chainer: 7.7.0
ChainerX: Not Available
NumPy: 1.20.1
CuPy:
  CuPy Version          : 7.8.0
  CUDA Root             : /usr/local/cuda
  CUDA Build Version    : 11000
  CUDA Driver Version   : 11000
  CUDA Runtime Version  : 11000
  cuBLAS Version        : 11200
  cuFFT Version         : 10201
  cuRAND Version        : 10201
  cuSOLVER Version      : (10, 6, 0)
  cuSPARSE Version      : 11101
  NVRTC Version         : (11, 0)
  cuDNN Build Version   : None
  cuDNN Version         : None
  NCCL Build Version    : 2708
  NCCL Runtime Version  : 2708
  CUB Version           : Enabled
  cuTENSOR Version      : None
iDeep: Not Available
```

ようやくCuPyがchainerから認識されました！

## Dockerfile完成系
最後に完成したDockerfileを書いておきます．

```dockerfile
FROM nvidia/cuda:11.0-runtime
# Install basics
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends \
    python3-dev \
    python3-pip \
    python3-wheel \
    python3-setuptools \
    cmake \
    wget \
    g++
RUN rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

# Install python library
RUN pip3 install -U pip
RUN pip install chainer==7.7.0
RUN pip install cupy-cuda110==7.8.0
```



## まとめ

DockerでGPUに対応したchainerの実行環境を作成しようとしたところ，

1. cupyが使えない 
2. chainerからcupyを使えない 

という二つの問題が発生しました．

この二つの問題に対してそれぞれ以下のように対処を行いました．

1. 誤ったcudaのイメージをベースにDockerfileを記述していたので，正しいcudaのイメージに修正
2. cupyのバージョンの問題だったので，正しいバージョンをインストール

その結果無事にGPUに対応したchainer実行環境をDockerで作成することができました．



## 参考記事

[NVIDIA Docker って今どうなってるの？ (20.09 版)](https://medium.com/nvidiajapan/nvidia-docker-%E3%81%A3%E3%81%A6%E4%BB%8A%E3%81%A9%E3%81%86%E3%81%AA%E3%81%A3%E3%81%A6%E3%82%8B%E3%81%AE-20-09-%E7%89%88-558fae883f44)

https://github.com/NVIDIA/nvidia-docker/issues/1160

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

[Chainer 7.7.0 のインストールでハマった話](https://izumo27.hatenablog.com/entry/2020/11/21/195100)