AWSのGPUインスタンス構築　値段を抑えて最短で構築する

# はじめに
本記事はAWSクラウド（以降、AWS）でGPUインスタンスの値段を抑えてかつ、最短で構築する方法について記載しています。

例えば、AWSを利用してGPUインスタンスを構築する場合、安易に有料のAMIを利用してGPUインスタンスを構築するのはナンセンスです。

AWSのサービス内容を正しく理解した上で導入することにより、コストを抑えることができます。また、Terraformを活用することで構築時間を短縮してGPUインスタンス環境を用意することができます。

GPUを利用する場合は、前提としてNVIDIAドライバ、CUDA、cuDNN等の知識が必要です。これらについては以前書いた[UbuntuでGPUマシーンを構築する](https://qiita.com/Brutus/items/2db051ec2bcfa726cc2f)を参照。

## GPUの導入における検討事項
オンプレミスやクラウドに関わらず、GPUを導入する場合は、色々と考えないといけないことがあります。

AWSにおいてEC2のGPUインスタンスを使用して、ディープラーニングや学習済みモデルを動かす場合は、以下について何を使用するか考える必要があります。これらについて事前にきちんと計画することで、無駄なコストを抑えたり作業の手戻りを減らすことができます。

- [x] EC2インスタンス（P系・G系等）
- [x] AMI（OSバージョン　例：Ubuntu18.04、20.04）
- [x] NVIDIAドライバのバージョン（4XX〜）
- [x] CUDAのバージョン（8〜11）
- [x] cuDNNのバージョン（7.4、7.6、8）
- [x] TensorFlowのバージョン（1系・2系）
- [x] Pythonのバージョン（3.6、3.7、3.8）

**GPUを動かすための各要素は他と結びつきが強く依存する構造になっているため、互換性を担保することが必要です。**

また、Dockerを使用してコンテナからGPUを使用する場合は、Dockerのバージョンについても考慮が必要です。

例えば、学習済みモデルをDockerで動かそうとした際に、古いTensorFlowのバージョンで開発していた場合は、移行が困難になる場合があります。そのため、必要に応じてTensorFlowのバージョンアップ対応が必要です。

導入時のポイントとしては、既に、学習済みモデルを動かす場合などは、プログラムで使用されているTensorFlowののフレームワークのバージョンを軸に、EC2インスタンスのタイプや、AMIを検討するのがいいと思います。

### GPUインスタンス
AWSでGPUが使用可能なインスタンスは以下の様な種類があります。
また、他にも機械学習推論アプリケーションをサポートする新しい**Inf1**や、カスタムハードウェアアクセラレーションを実現できる**F1**と行った機械学習向けのタイプ(※)があります。

- P4/P3/P2
- G4dn
- G4ad
- G3

(※)Inf1やF1については、ハードウェアアクセラレーター (コプロセッサ) を使用していますがGPUとは別チップになります。詳細については、[「Amazon EC2 インスタンスタイプ」](https://aws.amazon.com/jp/ec2/instance-types/)を参照。

初めてAWSでGPUインスタンスを導入する場合、どれにすればいいか悩むと思いますが、大別するとP系やG系などの汎用インスタンスが導入しやすいと思います。

特徴として、P系の最新世代である**P4**や**P3**は、コストがかなり高くなります。
お勧めはGPUインスタンスで一番コスパが良いG系の**g4dn**だと考えます。

オンデマンドの時間単価は高速コンピューティングのインスタンスタイプの中では一番安い**0.71 USD**です。また、GPUも**NVIDIA T4 Tensor Core**になるため、元々オンプレで動かしていたものをクラウドに移行する場合は、導入しやすいと思います。

以下は、AWSの料金計算ツールである[SIMPLE MONTHLY CALCULATOR](https://calculator.s3.amazonaws.com/index.html)で、g4dn.xlargeと、p3.2xlargeをそれぞれ1ヶ月使用し続けた場合の概算見積もりになります。料金はEC2インスタンスのみです。

| インスタンスタイプ | 毎月の請求額 |
|:-|:-:|
|  g4dn.xlarge  |  ¥56,847|
|  p3.2xlarge |  ¥335,798 |

※本記事執筆時点(2021.05.15)のレートで計算

本記事では**g4dn.xlarge**を指定して構築します。

### AMI
AWSではディープラーニング用途に特化したAMIがあります。

ディープラーニング用のAMIは最初からNVIDIAドライバ、CUDA、cuDNN等がセットアップされています。またAMIによってKerasなどののフレームワークも事前に用意されているため、導入にかかるコストは不要ですが、中には有料のAMIもあります。

**AWS Deep Learning AMI (Ubuntu 18.04)**は、NVIDIAドライバ、CUDA、cuDNN等は最初からセットアップされています。また、無料で使用することができますが、本記事では**Ubuntu Server 20.04 LTS (HVM)**を選択し、自前でVIDIAドライバ、CUDA、cuDNN等をセットアップします。

例えば、ディープラーニング用途に特化したAMIを利用して学習済みモデルを動かそうとした場合、CUDA等のバージョンが合わないことがあります。そのため、自前で構築する場合は、通常のUbuntu Server 20.04 LTS (HVM)や、Ubuntu Server 18.04 LTS (HVM)のAMIを利用するのがいいと思います。

## AWS（EC2）構築
本記事ではEC2インスタンスをTerraformで構築し、EC2インスタンス構築後SSHでログインし、調査しながらGPU環境を構築しています。

EC2インスタンスをTerraformで構築する場合、環境構築の時短以外にもメリットがあります。

例えば、GPU環境を構築する場合に上手くいかなくて、NVIDIAドライバやCUDAを入れ直したいケースがあると思います。その様な場合にTerraformを利用すればやり直しが簡単にすぐできます。

### Terraform
TerraformでGPUインスタンスを構築する場合は、インスタンスタイプを任意のGPUインスタンスを指定するだけで基本的に通常のEC2の書き方と変わりません。本記事ではAMIにUbuntu18.04、インスタンスタイプにg4dn.xlargeを指定しています。

Terraformは以前書いた、[AWSのEC2とRDSをTerraformで構築する　Terraform３分クッキング](https://qiita.com/Brutus/items/cd5aab062ea6cebe436c)を参照。

```tf
resource "aws_instance" "gpu_server" {
    ami = "ami-0fe22bffdec36361c"
    instance_type = "g4dn.xlarge"
    key_name   = "xxx-sshkey"
    vpc_security_group_ids = [
      "${aws_security_group.hoge-xxx-sg.id}"
    ]
    subnet_id = "${aws_subnet.hoge-xxx.id}"
    associate_public_ip_address = "true"
    ebs_block_device {
      device_name    = "/dev/sda1"
      volume_type = "gp2"
      volume_size = 50
      }
    user_data          = "${file("./userdata/cloud-init.tpl")}"
    tags  = {
        Name = "gpu_server"
    }
}
```

GPUのEC2インスタンスを初めて利用する場合は、リソース制限があるため事前にAWSサポートなどから申請する必要があります。

**注意事項として、申請する際に申請する数はEC2インスタンスの台数ではなく、利用するインスタンスタイプのvCPU数になります。**

以下の出力はリソース制限の申請をしていない状態で、`terraform apply`を実行した際の出力例です。

```console
Error: Error launching source instance: PendingVerification: Your request for accessing resources in this region is being validated, and you will not be able to launch additional resources in this region until the validation is complete. We will notify you by email once your request has been validated. While normally resolved within minutes, please allow up to 4 hours for this process to complete. If the issue still persists, please let us know by writing to aws-verification@amazon.com for further assistance.
	status code: 400, request id: ××××××××-××××-××××-××××-××××××××××××
```

status codeが400となっているのでBad Requestが発生しています。

```console
Error: Error launching source instance: VcpuLimitExceeded: You have requested more vCPU capacity than your current vCPU limit of 0 allows for the instance bucket that the specified instance type belongs to. Please visit http://aws.amazon.com/contact-us/ec2-request to request an adjustment to this limit.
	status code: 400, request id: ××××××××-××××-××××-××××-××××××××××××
```

上記のメッセージは、「現在のvCPU制限である0が、指定されたインスタンスタイプが属するインスタンスバケットで許可されているよりも多くのvCPU容量を要求しました。この制限の調整をリクエストするには、http：//aws.amazon.com/contact-us/ec2-requestにアクセスしてください。」と出力されています。

現在（※）カスタマーサービス日本語窓口が混み合っていて、問い合わせに時間がかかっています。以前は申請したらすぐ折り返しがきましたが、今回は申請から反映まで1〜2時間要しました。

（※）本記事執筆時点

無事に申請が終わると、EC2ダッシューボードより、現在の制限の数が変わっているのが確認できます。

AWSコンソールのEC2サービスの左ペインから**制限**を選択し、対象のリソースを検索します。今回、**g4dn.xlarge**を申請したので「All G instances のオンデマンドを実行中」の「現在の制限」が4vCPUに変わっています。

![スクリーンショット 2021-05-16 22.20.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/b914399b-9207-e65b-39de-e90a422b8c44.png)

再度、`terraform apply`を実行すると無事にリソースが作成されました。

```console
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
```

## GPU環境構築
TerraformでGPUインスタンス構築後、SSHでログインします。

はじめにubuntu-drivers-commonパッケージをインストールします。

`# apt install ubuntu-drivers-common`

### NNvidiaドライバ
以下のコマンドを実行し、GPUが認識されていることを確認します。
実行結果より、**NVIDIA Tesla**のGPUが認識されているのが確認できます。
補足としてOSのバージョンの違いにより出力のされ方が変わります。

Ubuntu18.04の場合、`1eb8`と出力されていますが、この文字列はDevice PCI IDになり、**Tesla T4**を指します。文字列の詳細については[Appendix A. Supported NVIDIA GPU Products](https://download.nvidia.com/XFree86/Linux-x86_64/410.104/README/supportedchips.html)より確認できます。

`# lspci | grep -i nvidia`

```console
# Ubuntu18.04
00:1e.0 3D controller: NVIDIA Corporation Device 1eb8 (rev a1)

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

以下のコマンドを実行し、NVIDIAグラフィックカードのモデルと推奨ドライバーを検出します。
実行結果より、**nvidia-driver-460**が推奨されました。

`# ubuntu-drivers devices`

```console
== /sys/devices/pci0000:00/0000:00:1e.0 ==
modalias : pci:v000010DEd00001EB8sv000010DEsd000012A2bc03sc02i00
vendor   : NVIDIA Corporation
driver   : nvidia-driver-450-server - distro non-free
driver   : nvidia-driver-418-server - distro non-free
driver   : nvidia-driver-450 - distro non-free
driver   : nvidia-driver-460-server - distro non-free
driver   : nvidia-driver-460 - distro non-free recommended
driver   : xserver-xorg-video-nouveau - distro free builtin
````

以下のコマンドを実行しNVIDIAドライバを検索します。

`# apt-cache search nvidia-driver-460`

```console
nvidia-driver-460 - NVIDIA driver metapackage
nvidia-headless-460 - NVIDIA headless metapackage
nvidia-headless-no-dkms-460 - NVIDIA headless metapackage - no DKMS
xserver-xorg-video-nvidia-460 - NVIDIA binary Xorg driver
nvidia-driver-460-server - NVIDIA Server Driver metapackage
nvidia-headless-460-server - NVIDIA headless metapackage
nvidia-headless-no-dkms-460-server - NVIDIA headless metapackage - no DKMS
xserver-xorg-video-nvidia-460-server - NVIDIA binary Xorg driver
````

以下のコマンドを実行し、NVIDIAドライバのインストールします。

`# apt-get install nvidia-driver-460`

以下のコマンドを実行し、NVIDIAのパッケージを確認します。

`# dpkg -l | grep nvidia`

インストール後、OSを再起動します。

`# systemctl reboot`

OS再起動後、以下のコマンドを実行し、NVIDIAのドライバがロードされていることを確認します。

`# dmesg | grep -i nvidia`

```console
[    3.467021] nvidia: loading out-of-tree module taints kernel.
[    3.467032] nvidia: module license 'NVIDIA' taints kernel.
[    3.485884] nvidia: module verification failed: signature and/or required key missing - tainting kernel
[    3.494062] nvidia-nvlink: Nvlink Core is being initialized, major device number 240
[    3.547956] NVRM: loading NVIDIA UNIX x86_64 Kernel Module  460.73.01  Thu Apr  1 21:40:36 UTC 2021
[    3.557074] nvidia-modeset: Loading NVIDIA Kernel Mode Setting Driver for UNIX platforms  460.73.01  Thu Apr  1 21:32:31 UTC 2021
[    3.559328] [drm] [nvidia-drm] [GPU ID 0x0000001e] Loading driver
[    3.559329] [drm] Initialized nvidia-drm 0.0.0 20160202 for 0000:00:1e.0 on minor 0
[    3.575127] nvidia-uvm: Loaded the UVM driver, major device number 238.
[    4.088606] audit: type=1400 audit(1620885900.832:2): apparmor="STATUS" operation="profile_load" profile="unconfined" name="nvidia_modprobe" pid=384 comm="apparmor_parser"
[    4.088610] audit: type=1400 audit(1620885900.832:3): apparmor="STATUS" operation="profile_load" profile="unconfined" name="nvidia_modprobe//kmod" pid=384 comm="apparmor_parser"
```

以下のコマンドを実行し、NVIDIAのドライバが確認できます。

`# nvidia-smi`

```console
Sun May 16 00:53:49 2021       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.73.01    Driver Version: 460.73.01    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 T4            Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   64C    P0    30W /  70W |      0MiB / 15109MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

### CUDA
基本的には[CUDA Toolkit Documentation](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html)を参照し、インストールしたNVIDIAのドライバに該当する**CUDA Toolkit**のバージョンを確認しますが、本記事では**cuda-toolkit-10-0**をインストールしています。

- CUDAのダウンロード 
`# wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin`

- ファイルの移動
`# mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600`

- キーのインポート 
`# apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub`

- リポジトリの追加 
`# add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/ /"`

- パッケージのアップデート
`# apt-get update`

- CUDAのインストール
`# apt-get install cuda-toolkit-10-0`

- パッケージの確認
`# dpkg -l | grep cuda`

`nvcc`コマンドは`/usr/local/cuda-10.0`配下に存在するため環境変数`PATH`にパスを通します。

`# find / -name nvcc`

```console
/usr/local/cuda-10.0/bin/nvcc
```

`# vi ~/.bashrc`

```text
export PATH=$PATH:/usr/local/cuda-10.0/bin/
```

- .bashrcファイルの反映
`# source ~/.bashrc`

- CUDAのバージョン確認
`# nvcc -V`

```console
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2018 NVIDIA Corporation
Built on Sat_Aug_25_21:08:01_CDT_2018
Cuda compilation tools, release 10.0, V10.0.130
```

### cuDNN
任意のcuDNNをダウンロードする場合は、NVIDIAのアカウントを作成し、[cuDNN Archive](https://developer.nvidia.com/rdp/cudnn-archive)よりダウンロードします。ダウンロード後、GPUマシーンにファイル転送します。ファイル転送後、インストトールします。

cuDNNは、libcudnn7_7.6.0.64-1+cuda10.0_amd64.debからインストールします。
先に、libcudnn7-dev_7.6.0.64-1+cuda10.0_amd64.debからインストールすると、依存関係により失敗します。

- cuDNNのインストール
`# dpkg -i libcudnn7_7.6.0.64-1+cuda10.0_amd64.deb`　
`# dpkg -i libcudnn7-dev_7.6.0.64-1+cuda10.0_amd64.deb `
`# dpkg -i libcudnn7-doc_7.6.0.64-1+cuda10.0_amd64.deb `

- パッケージの確認 
`# dpkg -l | grep cudnn`

例として以下のメッセージは、Tensorflowのエラーメッセージです。
cuDNNのバージョンが合わない場合、以下の様なメッセージが出力されます。

```console
2021-05-16 01:14:36.075919: E tensorflow/stream_executor/cuda/cuda_dnn.cc:319] Loaded runtime CuDNN library: 7.4.2 but source was compiled with: 7.6.0.  CuDNN library major and minor version needs to match or have higher minor version in case of CuDNN 7.0 or later version. If using a binary install, upgrade your CuDNN library.  If building from sources, make sure the library loaded at runtime is compatible with the version specified during compile configuration.
2021-05-16 01:14:36.076938: E tensorflow/stream_executor/cuda/cuda_dnn.cc:319] Loaded runtime CuDNN library: 7.4.2 but source was compiled with: 7.6.0.  CuDNN library major and minor version needs to match or have higher minor version in case of CuDNN 7.0 or later version. If using a binary install, upgrade your CuDNN library.  If building from sources, make sure the library loaded at runtime is compatible with the version specified during compile configuration.
```

メッセージの意味は、ロードされたランタイムCuDNNライブラリ：7.4.2ですが、ソースは7.6.0でコンパイルされました。 CuDNN 7.0以降のバージョンの場合、CuDNNライブラリのメジャーバージョンとマイナーバージョンは、一致するか、より高いマイナーバージョンである必要があります。バイナリインストールを使用している場合は、CuDNNライブラリをアップグレードが必要です。

### 動作確認
GPUが正常に動作しているかは、TensorFlowを利用して確認できます。
インタプリンタでpythonを起動して、以下のコードを実行します。

```python
import tensorflow as tf
tf.test.is_gpu_available()
```

最後に`True`と表示されれば、GPUは認識できています。

```console
True
```

また、`nvidia-smi`コマンドを実行し、実際にGPUが動作しているか確認できます。

## Docker 
Dockerを使用してGPUを使用する場合は、一手間がかかります。

最近のDockerでGPUを動かす方法としては、NVIDIA Container Toolkitをインストールして、ランタイムを指定するやり方に変わってきています。

NVIDIA Container Toolkitの詳細については触れませんが、Dockerを使用する際のいくつかのナレッジについて記載します。

### TensorFlow のエラーメッセージ（dynamic libraryが見つからない）
以下はDockeでコンテナを起動して、TensorFlow のエラーメッセージです。
LDDのエラーが出ています。

```console
2021-05-13 16:42:57.522076: W tensorflow/stream_executor/platform/default/dso_loader.cc:55] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /usr/local/cuda/extras/CUPTI/lib64:/usr/local/nvidia/lib:/usr/local/nvidia/lib64
```

ビルドする`Dockerfile`に、対応するLDDが存在する`LD_LIBRARY_PATH`を設定することで回避できます。

```Dockerfile
ENV LD_LIBRARY_PATH $LD_LIBRARY_PATH:/usr/local/cuda-10.0/compat/
```

問題なければ、以下の様なメッセージが出力されます。

```console
2021-05-14 06:43:08.754116: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.1
```

### TensorFlow のエラーメッセージ（GPUデバイスが認識されない）
以下もDockeでコンテナを起動して、TensorFlow のエラーメッセージです。
コンテナからGPUが認識されていません。

```console
2021-05-13 16:42:57.522126: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:163] no NVIDIA GPU device is present: /dev/nvidia0 does not exist
```

docker-composeの場合は`docker-compose.yml`に、`devices`を指定してコンテナが使用できるデバイスの予約を構成します。

```dockerfile
devices:
  - "/dev/nvidia0:/dev/nvidia0"
```

# おわりに
GPU環境を新たに構築する場合、調査や検証が必要になってくると思います。
Terraformを利用することでE2インスタンスを迅速に構築できるため、**trial and error**がやりやすくなります。

本記事ではオンプレミスの環境で動いていた学習済みモデルをAWSクラウドに移行するにあたり、環境差異の弊害により何度か`terraform apply`と、`terraform destroy`を繰り返しました。

環境が汚れてもクリーンな環境でやり直しがすぐにできるため、改めてTerraformは便利だなと思いました。

## 参考
各ソフトのダウンロード先や、どのバージョンが対応しているかについては以下リンクのURLから確認できます。

### NVIDIA
- [Install NVIDIA drivers on Linux instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/install-nvidia-driver.html)
- [NVIDIA Driver Downloads](https://www.nvidia.com/Download/Find.aspx)

### CUDA
- [CUDA Toolkit 11.3 Downloads](https://developer.nvidia.com/cuda-downloads)

### cuDNN
- [cuDNN Archive](https://developer.nvidia.com/rdp/cudnn-archive)

### Docker
- [Compose における GPU アクセスの有効化](https://matsuand.github.io/docs.docker.jp.onthefly/compose/gpu-support/)
- [TensorFlow Docker の要件](https://www.tensorflow.org/install/docker?hl=ja#tensorflow_docker_requirements)
- [Installation Guide] (https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html)
- [nvidia-container-runtime](https://github.com/NVIDIA/nvidia-container-runtime)

### TensorFlow 
- [ソースからのビルド - テスト済みのビルド構成](https://www.tensorflow.org/install/source?hl=ja#gpu)