NVIDIA 社提供の AMI で DeepLearning

# 更新履歴

- [2017/03/08] AWS 公式 [Deep Learning AMI v2 リリース](https://aws.amazon.com/about-aws/whats-new/2017/03/deep-learning-ami-release-v2-0-now-available-for-amazon-linux/)！  
**CUDA 8.0** になり、TensorFlow v1.0.0, MXNet v0.9.3 など

# Deep Learning に便利な AMI

## NVIDIA 提供

* [
NVIDIA CUDA Toolkit 7.5 on Amazon Linux](https://aws.amazon.com/marketplace/pp/B01LZMLK1K)
* [NVIDIA DIGITS 4 on Ubuntu 14.04](https://aws.amazon.com/marketplace/pp/B01LZN28VD)

2016/09 末に公開された模様。

自分で CUDA をインストールしたことがある方ならわかると思いますが、NVIDIA の GPU ドライバから始まり、CUDA をインストールするのはなかなかの手間でした。（まあ、一度 AMI を作ればってのはありますが）
この AMI を使えば、ものすごく簡単にサーバが使い始められます！

## AWS 提供

* [Deep Learning AMI Amazon Linux Version](https://aws.amazon.com/marketplace/pp/B01M0AXXQB)
* [Deep Learning AMI Ubuntu Version](https://aws.amazon.com/marketplace/pp/B06VSPXKDX)

MXNet、Caffe、Tensorflow、Theano そして Torch など人気のフレームワークが事前にインストール・設定・テストされた AMI。~~ただし、こちらには CUDA がインストールされておらず、東京リージョンには配布されていません・・。~~（追記：最新版は CUDA 入っていますし、東京でも使えます！ありがたい！！）

（お試し用テストサンプルが入っていたりするようです）

```
[ec2-user@ip-10-0-0-10 ~]$ ls -la /home/ec2-user/src/bin
合計 40
drwxrwxr-x  2 ec2-user ec2-user 4096  9月 20 00:48 .
drwxrwxr-x 14 ec2-user ec2-user 4096  9月 28 20:21 ..
-rwxr-xr-x  1 ec2-user ec2-user  350  9月 20 00:12 testAll
-rwxr-xr-x  1 ec2-user ec2-user 1207  9月 20 00:12 testCaffe
-rwxr-xr-x  1 ec2-user ec2-user 1141  9月 20 00:12 testMXNet
-rwxr-xr-x  1 ec2-user ec2-user   70  9月 20 00:13 testTensorFlow
-rwxr-xr-x  1 ec2-user ec2-user   66  9月 20 00:13 testTheano
-rwxr-xr-x  1 ec2-user ec2-user 1058  9月 20 00:13 testTheanoOrTensorFlow
-rwxr-xr-x  1 ec2-user ec2-user  861  9月 20 00:13 testTorch
-rwxr-xr-x  1 ec2-user ec2-user  210  9月 20 00:13 testUtil
```

# 起動してみる

NVIDIA CUDA Toolkit 7.5 on Amazon Linux を使い、中身を確認してみます。

## 起動手順

### 1. マーケットプレイスでライセンスに承諾

適切なリージョンを選択し `continue` をクリックします。

<img width="500" alt="1_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/66457/1494ffb0-672d-fadb-2e90-477d3d4d4886.png" width="500">

スポットインスタンスを使いたいので、マニュアルでの起動を選択。画面の左下にある AMI の ID を控えつつ、（今回の AMI はいまのところ無料ですが）起動時間あたりの利用料を確認し、ソフトウェアの規約に従うことを承認します。

<img width="972" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/66457/65d930d4-f6f4-be54-6b84-916e6bd73c9a.png" width="500">

### 2. EC2 インスタンスの起動

スポット料金を設定しつつ g2.2xlarge インスタンスを起動します。

<img width="500" alt="1.png" src="https://qiita-image-store.s3.amazonaws.com/0/66457/c2625e95-2be5-0d3e-d0c5-1a547e582d3d.png" width="500">

<img width="500" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/66457/8505320e-91c5-186c-9171-9a21b6d689bc.png" width="500">

スポットの金額が市場を下回るとインスタンスが起動します。

### 3. EC2 に SSH

起動したらサーバに入ってみます。
`ssh -i ec2.pem ec2-user@x.x.x.x`

インストールされているドライババージョンを確認してみます。

```sh:バージョンの確認
[ec2-user@ip-10-0-0-10 ~]$ nvcc -V

-bash: nvcc: コマンドが見つかりません
```

なるほど。

```sh:バージョンの確認
[ec2-user@ip-10-0-0-10 ~]$ /usr/local/cuda/bin/nvcc -V

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2015 NVIDIA Corporation
Built on Tue_Aug_11_14:27:32_CDT_2015
Cuda compilation tools, release 7.5, V7.5.17
```

はい、しっかり 7.5 ですね。AMI 素晴らしい！！

## NVIDIA-docker も入れとこう

### 1. docker を入れて

[公式ドキュメント](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html#install_docker) には従わず[^9]、以下のコマンドで Docker をインストールします。

```sh:
sudo yum install -y docker
sudo service docker start
sudo usermod -aG docker ec2-user
```

以後 ec2-user でも docker コマンドが使えるように、いったん SSH から抜けて入りなおします。

### 2. NVIDIA-docker も入れる

残念ながら `1.0.0` 時点では、配布されている rpm の依存パッケージ名が AmazonLinux だとアレなもので、[バイナリを持ってきて置く](https://github.com/NVIDIA/nvidia-docker/wiki#other-distributions)ことにします。

```sh:nvidia-dockerインストール
wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.0/nvidia-docker_1.0.0_amd64.tar.xz
sudo tar --strip-components=1 -C /usr/bin -xvf /tmp/nvidia-docker*.tar.xz && rm /tmp/nvidia-docker*.tar.xz
```

### 3. nvidia-docker-plugin の起動

```sh:nvidia-docker-pluginの起動
sudo mkdir -p /run/docker/plugins/
sudo -b nohup nvidia-docker-plugin > /tmp/nvidia-docker.log
```

もし後からサーバを再起動する / AMI をとるつもりであれば、以下のようにスクリプトを用意しておくとよいです。

```sh:nvidia-docker-pluginのinitスクリプト設置
sudo sh -c "cat << EOF > /etc/init.d/nvdocker-plugin
#!/bin/sh
# chkconfig: 345 98 20
# description: NVIDIA-docker plugin
nvidia-docker-plugin > /tmp/nvidia-docker.log
EOF"
sudo chmod +x /etc/init.d/nvdocker-plugin
sudo chkconfig --add nvdocker-plugin
```

plugin が起動したら、REST API の疎通を確認します。

```sh:API起動確認
[ec2-user@ip-10-0-0-10 ~]$ curl 127.0.0.1:3476/v1.0/docker/cli

--volume-driver=nvidia-docker --volume=nvidia_driver_352.99:/usr/local/nvidia:ro --device=/dev/nvidiactl --device=/dev/nvidia-uvm --device=/dev/nvidia0
```

### 4. 起動確認

インストールした NVIDIA-docker で nvidia-smi コマンドを実行します。

```sh:Dockerを使ったnvidia-smi
[ec2-user@ip-10-0-0-10 ~]$ nvidia-docker run --rm nvidia/cuda:7.5 nvidia-smi

Tue Oct 11 00:38:26 2016
+------------------------------------------------------+
| NVIDIA-SMI 352.99     Driver Version: 352.99         |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GRID K520           On   | 0000:00:03.0     Off |                  N/A |
| N/A   25C    P8    17W / 125W |     11MiB /  4095MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

いい感じ[^2]ですね。

# 検証

## 推論サーバの起動

GPU を推論に使い、HTTP プロトコルを話す API サーバを使ってベンチマークをとってみます。

### 1. GRE インストール

NVIDIA 社がデモ用に作った、Caffe での推論を REST API 化した [GPU REST Engine](https://github.com/NVIDIA/gpu-rest-engine)[^3] というアプリケーションを使います[^4]。

以下のコマンドで推論サーバを起動します。

* コミットを b7fac33 に戻し、**CUDA7.5 系にする必要あり**
* `docker build` には g2.2xlarge で 30 分程度かかります

```sh:推論サーバの起動
cd $HOME
git clone https://github.com/NVIDIA/gpu-rest-engine
cd gpu-rest-engine
git checkout b7fac33
docker build -t inference -f Dockerfile.inference_server .
nvidia-docker run -d --name api -p 8000:8000 inference
```

### 2. 起動確認

git リポジトリに同梱されているウサギの画像を推論サーバに問い合わせてみます。

```
[ec2-user@ip-10-0-0-10 ~]$ IMAGE="./images/1.jpg"
[ec2-user@ip-10-0-0-10 ~]$ curl -XPOST --data-binary @${IMAGE} 127.0.0.1:8000/api/classify

[{"confidence":0.9998,"label":"n02328150 Angora, Angora rabbit"},{"confidence":0.0001,"label":"n02325366 wood rabbit, cottontail, cottontail rabbit"},{"confidence":0.0001,"label":"n02326432 hare"},{"confidence":0.0000,"label":"n02085936 Maltese dog, Maltese terrier, Maltese"},{"confidence":0.0000,"label":"n02342885 hamster"}]
```

99.98% の確率でアンゴラウサギらしいです。動いてますね。
この状態で SecurityGroup さえ解放されていれば、外部からの推論も行えます。楽しい。


## 簡易ベンチマーク

### 検証用クライアントのビルド

[rakyll/boom](https://github.com/rakyll/boom) を内包したツールをビルドします。

```sh:検証用クライアントの生成
docker build -t client -f Dockerfile.inference_client .
```

### ベンチマークの実施[^5]

```sh:g2.2xlarge
[ec2-user@ip-10-0-0-10 ~]$ docker run --rm -e CONCURRENCY=1 -e REQUESTS=20000 --net=host client

Summary:
  Total:       	258.6445 secs
  Slowest:     	0.0167 secs
  Fastest:     	0.0127 secs
  Average:     	0.0129 secs
  Requests/sec:	77.3262
  Total data:  	6880000 bytes
  Size/request:	344 bytes
[...]
```

GRE の README.md には「スタンドアローンな Caffe ならだいたい秒間 500 画像処理できる」とありますが、まあ、はい。
一方でこの GRE、マルチ GPU に対応してちゃんとリクエスト捌いてくれるということなので・・

### インスタンスタイプ変更

Amazon EC2 では最近、P2 系がリリースされました。
[New P2 Instance Type for Amazon EC2 – Up to 16 GPUs | AWS Blog](https://aws.amazon.com/jp/blogs/aws/new-p2-instance-type-for-amazon-ec2-up-to-16-gpus/)

ということで、せっかくなので p2.16xlarge でもベンチマークをとり比較しようと思います[^6]。GPU そのものも K80 だし、何から何まで高スペックになるので期待が高まりますね！

### p2.16xlarge で再取得

10月12日現在、スポットインスタンスが意味不明な金額に高騰しているので素直にオンデマンドで起動します。上述の手順同様に[^7] GRE までのインストールを済ませ[^8]、nvidia-smi を流してみます。

```sh:GPU数
[ec2-user@ip-10-0-0-10 ~]$ nvidia-docker run --rm nvidia/cuda:7.5 nvidia-smi | grep K80 | wc -l
16
```

これを参考に `CONCURRENCY` の値をよしなに変更しつつ、ベンチマーク取得コマンドを打ってみます。

```sh:p2.16xlarge
[ec2-user@ip-10-0-0-10 ~]$ docker run --rm -e CONCURRENCY=16 -e REQUESTS=20000 --net=host client

Summary:
  Total:        14.1379 secs
  Slowest:      3.9332 secs
  Fastest:      0.0055 secs
  Average:      0.0110 secs
  Requests/sec: 1414.6341
  Total data:   6880000 bytes
  Size/request: 344 bytes
[...]
```

おーなるほど。

更に、p2 系では [GPU 最適化](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using_cluster_computing.html#optimize_gpu) が行えるので、その上でもう一度やってみます。

```sh:GPU最適化
nvidia-docker run --rm --privileged nvidia/cuda:7.5 nvidia-smi -pm 1
nvidia-docker run --rm --privileged nvidia/cuda:7.5 nvidia-smi --auto-boost-default=0
nvidia-docker run --rm --privileged nvidia/cuda:7.5 nvidia-smi -ac 2505,875
```

```sh:p2.16xlarge（GPU最適化後）
[ec2-user@ip-10-0-0-10 ~]$ docker run --rm -e CONCURRENCY=16 -e REQUESTS=20000 --net=host client

Summary:
  Total:        7.9519 secs
  Slowest:      0.0200 secs
  Fastest:      0.0055 secs
  Average:      0.0063 secs
  Requests/sec: 2515.1144
  Total data:   6880000 bytes
  Size/request: 344 bytes
[...]
```

そもそも p2 系はスペック的に学習に最適なサーバだと思いますが、推論でもこれだけ違いがでてくるとわくわくしますね！！

### 利用する GPU デバイス数を変えてみる

NVIDIA-docker はやれる子なので、**docker コンテナに引き渡しアプリで利用するデバイスを自由に指定**できます。（当然ながら docker コンテナからは渡された GPU しか見えません）

現在動いているサービスを一度止め

```sh:サービスの停止
docker stop api
docker rm api
```

5 GPU に制限して起動し直します。

```sh:GPU制限版推論サーバの起動
NV_GPU=0,1,2,3,4 nvidia-docker run -d --name api -p 8000:8000 inference
```

```sh:p2.16xlarge（GPU制限後）
[ec2-user@ip-10-0-0-10 ~]$ docker run --rm -e CONCURRENCY=5 -e REQUESTS=20000 --net=host client

Summary:
  Total:        25.8224 secs
  Slowest:      0.5705 secs
  Fastest:      0.0055 secs
  Average:      0.0064 secs
  Requests/sec: 774.5201
  Total data:   6880000 bytes
  Size/request: 344 bytes
[...]
```

それっぽい値ですね！
おわり。

　

[^2]: `docker: Error response from daemon` が返ってきたら再度実行してみてください。2度目なら通ると思います
[^3]: Go の http サーバ経由で Caffe での推論を行う薄いラッパーです
[^4]: 参考: [GPU REST Engine のセットアップと動作確認](http://qiita.com/daikumatan/items/2758ebd12209622772ab)
[^5]: 画像の推論はせず、[CUDA の kernel call のみを計測するアプリケーション](https://github.com/NVIDIA/gpu-rest-engine#benchmarking-overhead-of-cuda-kernel-calls) も同梱されています。
[^6]: 上限緩和しないと使えないのですが、そもそも申請フォーム上 p2 系が選択できないため、コメント欄で「p2 使わせて！」と懇願してしばらく待ちましょう
[^7]: g2 などでの AMI 化がうまくいっていないと p2 起動後に絶望するので、間違いなく動作する、NVIDIA 公式 AMI からスクリプトをあてていく手順をお勧めします。慣れていなければ。
[^8]: 推論サーバは GPU 分の初期化処理（モデルのロードなど）が走るため、起動までしばらく時間がかかります。`docker logs -f api` などで "Starting server listening on :8000" が表示されるまで気長に待ちます。
[^9]: yum update でカーネルが更新されてしまうと、再起動時に必要なモジュールが読み込まれなくなるため。 `sudo sh -c 'echo "exclude=kernel*" >> /etc/yum.conf'` などで対処しておいてもいいかもしれない