EC2のGPU環境でtensorflow動かしたメモ

機械学習の勉強でTensorflowをCPU環境で動かしていたけど、GPU環境で動かすとどれくらい変わるのか確かめたかった、程度だけど思ったより色々とハードルがあったので今後のためのメモ

### GCPで挫折

初期登録時に無料枠があったので、最初にGCPを試した。
CPUインスタンスでサンプルが動いたのでGPUインスタンスを立ち上げようとしたら
`Quota 'GPUS_ALL_REGIONS' exceeded. Limit: 0.0 globally` 
とお断りされた

[ここ](https://stackoverflow.com/questions/53415180/gcp-error-quota-gpus-all-regions-exceeded-limit-0-0-globally)を見るとリソースの割り当てを要求しないといけないらしい
ということで、GPU1個要求したら

```
Unfortunately, we are unable to grant you additional quota at this time. If 
this is a new project please wait 48h until you resubmit the request or 
until your Billing account has additional history.
```
と言われて即お断りされた

48時間待ってもう一度リクエストしても無料枠の一部を別のインスタンスで消費してからリクエストしてもやっぱりお断りされたので諦めた。
実際に課金が発生しないとダメなんだろうか

### EC2を使ってみる

GCPが無理だったのでEC2を試す。こっちはサービスへの課金履歴があるので同じ問題があっても大丈夫のはず
同じようにCPUインスタンスでサンプルスクリプトの動作確認した後、GPUインスタンスを試した
インスタンスの種類は色々あるが、Tesla T4が接続されたg4dn.xlargeを選択。こちらも同じように制限に引っかかったので上限割り当て変更をリクエスト（GPUインスタンスで使うvCPUの個数を緩和する、という内容のリクエストだった）

課金履歴があるからなのか、こちらのリクエストはすぐ通った


#### 前準備

CUDA Toolkitやらtensorflowやらでそこそこディスクを消費するので、ストレージの容量はデフォルトの8GBだと足りない。16GBあればギリギリなんとかなりそうだけど、20GBにした

125GBのエフェメラルディスクがついてるという説明があったが、使い方がわからなかったので[AWSのインスタンスストアボリューム(エフェメラルストレージ）](https://blog.e2info.co.jp/2019/02/20/%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%82%B9%E3%83%88%E3%82%A2%E3%83%9C%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%A0%E3%82%A8%E3%83%95%E3%82%A7%E3%83%A1%E3%83%A9%E3%83%AB%E3%82%B9%E3%83%88/)を参考に設定

* インスタンス停止時に揮発するが、ソースコードや学習データはこっちにおいて都度ダウンロードで問題なさそう

```
$ sudo mkfs.ext4 /dev/nvme1n1
$ sudo mkdir /workspace
$ sudo mount /dev/nvme1n1 /workspace
```

念の為GPUの存在を確認

```
$ lspci
...
00:1e.0 3D controller: NVIDIA Corporation TU104GL [Tesla T4] (rev a1)
...
```


#### CUDAのインストール

CUDA Toolkitを[公式の手順](https://developer.nvidia.com/cuda-10.2-download-archive?target_os=Linux&target_arch=x86_64&target_distro=CentOS&target_version=7&target_type=rpmlocal)に沿ってインストール

ここでGPUの確認をするとエラー

```
$ nvidia-smi
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.
```

ドライバが必要らしいのでこちらも[公式](https://www.nvidia.com/Download/index.aspx)からダウンロード
GPUのシリーズとToolkitのバージョンを選択するのでインスタンスのGPU、CUDA Toolkitのバージョンと一致させる
CUDA Toolkitを2020/07/07時点での最新版11.0-rcにすると、ここで対応するバージョンが見つからなかったのでToolkitを10.2で導入した

```
sudo ./NVIDIA-Linux-x86_64-418.152.00.run
```

ここでDKMS経由でインストールしないと[再起動時にドライバがロストする](https://forums.developer.nvidia.com/t/nvidia-driver-not-work-after-reboot-on-ubuntu/70831)という問題が起こるらしい
インスタンスを止めるとドライバ再インストールはまずいのでDKMSを使うように選択する
すると今度は次のエラーに遭遇

```
Your kernel headers for kernel XXX cannot be found at /lib/modules/XXX/build or /lib/modules/XXX/source.
```

該当のリンクをたどると確かにリンク切れを起こしている
kernel-develはCUDA Toolkitの依存ライブラリとしてインストールされているのだが、最新版がインストールされてしまうため、必要なカーネルソースと一致しないという状況なのでバージョンが一致するカーネルソースを取得する

```
sudo yum install "kernel-devel-uname-r == $(uname -r)"
```


#### Python/Tensorflow環境

ここはGPUでもCPUでもあまり変わらない。パッケージでtensorflow-gpuがあるのでこちらをpipで入れる

```
$ sudo yum install python3-pip
$ sudo pip3 install --upgrade pip
$ pip3 install tensorflow-gpu
$ python3
>> import tensorflow as tf
>> hello = tf.constant("Hello World!!")
```
いくつかのWarningが発生

```
2020-07-07 08:30:28.910462: W tensorflow/stream_executor/platform/default/dso_loader.cc:55] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory
2020-07-07 08:30:28.916972: W tensorflow/stream_executor/platform/default/dso_loader.cc:55] Could not load dynamic library 'libcudnn.so.7'; dlerror: libcudnn.so.7: cannot open shared object file: No such file or directory
2020-07-07 08:30:28.916984: W tensorflow/core/common_runtime/gpu/gpu_device.cc:1598] Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU. Follow the guide at https://www.tensorflow.org/install/gpu for how to download and setup the required libraries for your platform.
```

一部のライブラリのリンクに失敗している

最初のWarningは10.1を要求しているのに実際には10.2をインストールしていてそこでバージョン不整合を起こしていたので、
CUDA Toolkit 10.1をインストールし直して解決した

2つ目のWarningは公式でlibcudnnの[各バージョン向けのアーカイブ](https://developer.nvidia.com/rdp/cudnn-archive)が配布されているのでこれを導入する
ここでダウンロードしようとしたところでNVIDIAへの開発者登録が要求される、fxxk！
心を落ち着けてユーザ登録を済ませてダウンロード・インストール

```
$ sudo rpm -i libcudnn7-7.6.4.38-1.cuda10.1.x86_64.rpm
$ sudo yum install libcudnn7
$ ls -al /usr/lib64/libcudnn*
lrwxrwxrwx 1 root root        17  7月  7 09:14 /usr/lib64/libcudnn.so.7 -> libcudnn.so.7.6.4
-rwxrwxr-x 1 root root 430101144  9月 21  2019 /usr/lib64/libcudnn.so.7.6.4
```

Toolkitが導入するライブラリパスは`/usr/local/cuda/lib64` だけど、
単独で導入したlibcudnnのインストール先は`/usr/lib64`になっていた

これで、Hello Worldを実行するとWARNINGが解消されてGPUが使える状態に。
サンプルスクリプトを実行してnvidia-smiで確認すると

```
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0     31327      C   python3                                    14405MiB |
+-----------------------------------------------------------------------------+
```

と確かにGPUを使った処理が動いてる！！！
体感的な処理速度もかなり改善していたので無事動作確認することができた


### 最終的な環境

* ホスト: Amazon EC2（g4dn.xlarge）
* OS: Amazon Linux2
* GPUドライバ: CUDA Toolkit 10.1
* スクリプト: Python3.7.6 + tensorflow-gpu 2.2.0


### 感想

cudaとtensorflowのバージョン組み合わせは非常にトラップ満載
動く環境でどこかをバージョンアップするとリカバリが大変な状況に陥る予感がすごいので、
動く組み合わせごとにコンテナ化するなり、インスタンスを分けるなりして環境を切り分けた方が良さそう