Tensorflow 1.0でGPU利用 with AWS

そろそろ春の足音がしだして、相変わらずの何もできてないっぷりに愕然とする今日このごろです。

さすがに何もしていないのはまずいので、以前触った [TensorFlow](https://www.tensorflow.org/) を再び触り始めています。
相変わらずのGoogleクオリティで、APIが色々と変わっているような気がしますが、いろいろ忘れているので調度良いかなと。

何か一つ作ってみようと、作ってみている最中です（いずれ記事に・・・）が、DeepLearningとかコモディティ化してきていると言われているとはいえ、理解しながら作ろうとすると、もれなく数式とコンニチワするので、文字通りに頭の痛い日々です。でも楽しい。

## Amazon LinuxでGPU利用する ##
今作ってみているものは、学習をCPUだけでやると時間がかかってしょうがない＋学習用データが大きすぎて洒落にならないため、GPUで実施したくなります。
GPUは一応部屋のPCについているとはいえ、CUDAのインストールトラウマもあって、流石に普段使いのPCにインストールするのが面倒です。

ちなみに現時点で、CPUだけで学習すると1ステップ辺り、1データで2分とかかかります。20000ステップとか考えたくもないですね。電気代的な意味で。

AzureでもGCPでもいいけれども、使い慣れてるAWSのGPUインスタンスを使ってみます（使ったことがなかった）。
（というか、個人とかの場合クラウドを使わないと現実的な時間で終わんないような）

まともに利用するとかなりの金額になりそうだったので、SpotFleetで用意しました。Spot Fleetになってから初めて利用しますが、ちゃんとドキュメントを読まないと、capacityの意味が？ってなりますね。でも6割り引きくらいで利用できるのはあまりに魅力的・・・。

さて、CUDAはつい最近知識をアップデートしましたが、かなりインストールが楽になっていてびっくりです。
そのなかでも楽をするために、Ubuntu16.04のAMIを利用することにしました。CUDA/cuDNNのライブラリをnvidiaのページからダウンロードします。cuDNNはアカウントが必要なので、作る必要があります。

CUDAライブラリは、runfileにしておくと楽です、が、今回はdebにしてしまったので、次のようになります。

```bash
$ sudo dpkg -i <ダウンロードしたパッケージ名>
$ sudo apt-get update
$ sudo apt-get install cuda
```

cudnnライブラリは、cudaをインストールした先に展開します。debでインストールした場合は `/usr/local/lib/cuda` に入るので、その下になるように入れましょう。

```bash
$ tar xf cudnn-*.tgz
$ sudo cp cuda/include/* /usr/local/cuda/include
$ sudo cp cuda/lib64/* /usr/local/cuda/lib64
```

    Tensorflowが1.0になりましたが、GPU版の要求CUDAが8.0（最新）になり、同時に要求されるnvidia-driverのバージョンが375系になるという、非常に悲しい出来事がありました。過去を振り返らないnvidiaとGoogleらしい決断とでもいうのでしょうか。
    そして悲劇として、Tokyoリージョンで利用できるg2インスタンスだと、K520というGPUを積んでいるのですが、これがで、367.\*系のドライバでしか動かないのです・・・。
    そのため、tensorflowのバージョンを下げるか、USリージョンでp2インスタンスを利用する必要があります。
    今回は涙をのんでUSリージョンにP2を立てました。Tokyoにも来てくれることを願います。


そして、``.bash_profile`` で ``LD_LIBRARY_PATH`` を設定します。これを忘れるとライブラリの読み込みでエラーになって( ﾟдﾟ)ﾊｯ!てなります。

```bash
if [[ -z $LD_LIBRARY_PATH ]]; then
    export LD_LIBRARY_PATH=/usr/local/cuda/lib64
else
    export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
fi
```

ちゃんと動作しているかどうかは、非常に簡単なTensorflowプログラムを動かしてみるのが一番手っ取り早いです。

```python
import tensorflow as tf
sess = tf.session()
hello = sess.run(tf.constant('Hello, world!'))
print(hello)
```

うまく動作すると、次のようなログが出ます。

```
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcublas.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcudnn.so.5 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcufft.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcurand.so.8.0 locally
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE3 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.
I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:910] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
I tensorflow/core/common_runtime/gpu/gpu_device.cc:885] Found device 0 with properties:
name: Tesla K80
major: 3 minor: 7 memoryClockRate (GHz) 0.8235
pciBusID 0000:00:1e.0
Total memory: 11.17GiB
Free memory: 11.11GiB
I tensorflow/core/common_runtime/gpu/gpu_device.cc:906] DMA: 0
I tensorflow/core/common_runtime/gpu/gpu_device.cc:916] 0:   Y
I tensorflow/core/common_runtime/gpu/gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla K80, pci bus id: 0000:00:1e.0
```

メモリ11GiBて・・・。さすがサーバー用のGPUは驚異的です。

## Tensorflowのプロファイルを取りたい ##

GPUを利用していても、実際に早くなったのかどうか、は計測してみないとわからないものです。まぁ大体は速くなるんですけど、想定より遅いとかもありますし。

この場合、gperfとかそういったもので取得することも出来るようですが、軟弱な我々としては、やはりChromeとかFirefoxで慣れたタイムライン表示とかがいいです。可視化最高。TensorflowはGoogle主導で開発しているためかどうかはわからないけれど、これを行うための機能がすでにあります。

既存のTensorFlowプログラムに、以下のようなコードを追記します。

```python
from tensorflow.python.client import timeline

with tf.Session() as sess:
    run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
    run_metadata = tf.RunMetadata()

    sess.run(
        training_op,
        feed_dict=feed,
        run_metadata=run_metadata,
        options=run_options)

    # write train
    tl = timeline.Timeline(run_metadata.step_stats)
    ctf = tl.generate_chrome_trace_format()
    with open('timeline.json', 'w') as f:
        f.write(ctf)

```

こうやると、timeline.jsonというのがローカルに出来ます。これを、Chromeのアドレスバーで `chrome://tracing` といれて出るページで読み込ませてやると、見慣れた？タイムライン表示が行えます。内容的にはTensorflowのoperation単位になってるようです。

ただし、事前に ``LD_LIBRARY_PATH`` に ``/usr/local/cuda-8.0/extras/CUPTI/lib64`` を追加しておく必要があります。この中にあるlibcuptiが読み込めないとエラーになるためです。

## GPUの活用は正義（多分） ##
最初はg2インスタンスでやろうと思っていたのですが、まさかの使えない問題でp2インスタンスを利用することになるとは思いませんでした・・・。USリージョンだとSSHがまさしく「あー海越えてるなー」って速度になるんでストレスフルですね！

とりあえず、学習とかモデルとか、その辺りを勉強しながら進めていく、というのはなんだか久しぶりで、かなり新鮮です。データセットを集める手法の確立とかも大事ですが、そこはまぁ頑張ればいいので・・・。

新しいものをすぐ使いたくなるような人（主に自分）の助けになれば幸いです。