GCPで格安のpreemptibleなGPU環境(chainer,tensorflow+python3)を30分で立ち上げる

# なんでそんなことを考えたか
　技術力さえあれば、安い価格でGPUが使えるからです。K80での時間単価は１/３です。例えば24時間の使用を考えると、4,950円が1,650円です。これは安い。

|Normal|preemptible|
|:---|:--|
|$0.45 USD per GPU|$0.135 USD per GPU|

　[kaggle](https://www.kaggle.com/competitions)や[SIGNATE](https://signate.jp/competitions)で画像系の分析をするときに、GPUが使えるかどうかで分析にかけられる時間は大きく変わってしまいます。特に学習には時間がかかるうえに、0.01ポイントの精度を上げるために数千、数百万epochを繰り返すことになります。あなたのPCのCPUではたった1 epochすら回せないでしょう。

　最近ではGoogleが[Colaboratory](https://colab.research.google.com/)を提供してくれるようになりました。これは専有できるGPU(Tesla K80)が1つ利用できる無償サービスです。ちょっとした分析には非常に便利です。ただし12時間で強制的にHDDもろとも初期化されてしまいます。それは「インタラクティブな使用を想定しており、GPU を使ったバックグラウンドでの時間のかかる計算はダメ」というサービスの利用目的があるからです。

　他にGCPで提供されている[Cloud DataLab](https://cloud.google.com/datalab/)を使う手もあります。専有できるGPU,HDDがあるため、使いたいだけ利用することができます。その分課金されます。通常のGPU課金なので高いです。（Cloud DataLab自体は無料ですが、使用するコンピュートリソースに応じて課金されます）

　そこで、GCPには[preemptible](https://cloud.google.com/compute/docs/instances/preemptible)なVM環境を構築する方法を紹介します。このインスタンスは、通常のインスタンスよりはるかに低価格で作成、実行できるインスタンスです。これを使わない手はありません。Googleによって自動的にシャットダウンされてしまう可能性がありますが、シャットダウンされてもHDDは初期化されず残っていますので、途中からでも再開させればいいだけです。


## 事前準備（初回のみ必要になる作業です）
### GCP割当申請をする
最初は、各リージョン、各GPUごとに割当数は1となっています。2枚以上のGPUを使いたい場合は、次の記事を参考に割当申請をしておくとよいでしょう。手っ取り早く1枚のGPUでやりたい。という方は読み飛ばしてください。
[TensorFlowのGPU環境をGCPでさくっと作る方法](https://qiita.com/kazunori279/items/b17d16a2bfbec5e72984#gpu%E3%81%AE%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6%E7%94%B3%E8%AB%8B)

### Cloud shell環境を使う
　執筆時点において、preemptibleなGPUをWEBのコンソール画面から立ち上げることはできません。そのためgcloudコマンドを使って構築します。そこで、便利なCloud Shellを使いましょう。あなた専用の簡易サーバがGCP上にあるようなものです。ターミナルアプリ等を自分のPCにインストールする必要はなくブラウザのみで完結できます。ただtmuxのversionが2.3でちょっと古いですが。

　GCP Consoleの画面上部の右側に「Cloud Shellをアクティブにする」というボタンがあるので、それを押下しましょう。



### NVIDIA CLOUDアカウントを作成しておく
* [NVIDIA CLOUD](https://ngc.nvidia.com/)に行き、アカウントを作成後、ログインをする。
* 次にAPI Key を発行します。
 * 画面右上の「Get API Key」を押下する。
 * その後「Generate API Key」を押下すると API Keyが表示されますので、メモしておいてください。



## では、さっそく始めましょう
### いきなりGPUインスタンスを作成し起動する
```
# 最低限設定するもの
GCP_PROJECT=put_your_project_id
INSTANCE_NAME=put_create_instance_name
SERVICE_ACCOUNT=put_your_service_account # eg) xxxx-compute@developer.gserviceaccount.com

# 必要に応じて設定するもの
GPU_TYPE=nvidia-tesla-v100,count=1
MACHINE_TYPE=n1-standard-2
BOOT_DISK_SIZE=128GB
ZONE=us-central1-a
DISK_NAME=$INSTANCE_NAME

# インスタンス作成
gcloud compute --project=$GCP_PROJECT instances create $INSTANCE_NAME --preemptible \
    --boot-disk-size=$BOOT_DISK_SIZE --boot-disk-type=pd-ssd \
    --boot-disk-device-name=$DISK_NAME \
    --machine-type=$MACHINE_TYPE --zone=$ZONE \
    --accelerator type=$GPU_TYPE \
    --image-family ubuntu-1804-lts --image-project ubuntu-os-cloud \
    --no-restart-on-failure --maintenance-policy=TERMINATE \
    --service-account=$SERVICE_ACCOUNT \
    --scopes=https://www.googleapis.com/auth/cloud-platform 
```

```
# 作成したGPUインスタンスに接続
gcloud compute ssh --zone $ZONE $INSTANCE_NAME
```

```
# 続くコマンドを起動したVM上でrootで実行
sudo su -

# TIMEZONEをJSTに
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# CUDAをインストール
curl -O http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
dpkg -i ./cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
apt update && apt install -y cuda
```
5分ぐらいかかる。

### dockerのインストール
```
# docker-ceのインストール
apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
apt-key fingerprint 0EBFCD88
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt update && apt install -y docker-ce=5:18.09.0~3-0~ubuntu-bionic
```
```
# nvidia-dockerのインストール
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
apt update && apt install -y nvidia-docker2
pkill -SIGHUP dockerd
docker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi
```

### NVIDIA CLOUDから VMイメージを取得しVMを実行する

NVIDIA CLOUDには、chainerを使いたい人向けにchainer(nvcr.io/partners/chainer:4.0.0b1)が用意されているので、それを使えばいいのであるがpython2である。私はpython3+chainerを使いたいので```tensorflow:18.12-py3```を使ってdockerを立ち上げてchainerをインストールするという手順をとっている。tensorflow使い、python2+chainer使いの人は、以降に続くcudaのインストールが不要なので、15分ぐらいで完了する。

```
# NVIDIA CLOUDにログイン
docker login nvcr.io
Username: $oauthtoken
Password: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```
* 「Username：」は、そのまま「$oauthtoken」と入力します。
* 「Password：」は、NVIDIA CLOUDで発行されたAPI KEYを入力します。（事前準備でメモしたもの）

```
# NVIDIA CLOUDから VMイメージを取得しVMを実行する
docker pull nvcr.io/nvidia/tensorflow:18.12-py3
mkdir -p /container_data1
nvidia-docker run -d -t \
    --shm-size=1g \
    --ulimit memlock=-1 \
    --ulimit stack=67108864 \
    -v /container_data1:/workspace/data1 \
    -v /etc/localtime:/etc/localtime:ro \
    -p 80:8888 nvcr.io/nvidia/tensorflow:18.12-py3
```

```
# dockerのプロセス状況を確認し、containerの中に入る
docker ps
docker exec -it <container-id> /bin/bash
```

### ひとまず必要そうなライブラリをインストール
```
# 以下のコマンドはcontainer上で実行する
apt update && apt install -y libsm6 libxext6 libxrender1 libfontconfig1 python3-opengl xvfb ffmpeg
pip install numpy cupy chainer chainercv chainerrl pandas seaborn scikit-image jupyter
```
10分ぐらいかかる。python3-opengl,xvfb,ffmpegはchainerrl向け。

### 動作確認

```
# GPUの認識確認
nvidia-smi
# GPUの動作確認
python -c 'import chainer;print(chainer.cuda.available);print(chainer.cuda.cudnn_enabled);chainer.print_runtime_info()'
```


これで完了です。どうでしょう30分程度でGPUが使える環境までできましたね。


# jupyterを使えるようにしよう
本節では、構築できたGPU環境をもっと使いやすくするために、jupyterが動く環境まで作っていきましょう。jupyter自体は前節でインストール済みです。

### jupyter notebookのパスワードを設定する。
```
(in-docker)# python -c 'from notebook.auth import passwd;print(passwd())'
Password: xxx
Verify password: xxx
sha1:xxxxxxxx:xxxxxxxxxxxxx
```
入力したパスワードに合わせたハッシュ値(sha1)が表示されますので、それをメモしておきましょう。


### jupyterの起動を確認しておく
```
(in-docker)# jupyter notebook \
    --allow-root \
    --ip=0.0.0.0 \
    --port=8888 \
    --notebook-dir=/workspace \
    --NotebookApp.password='sha1:xxxxxxxx:xxxxxxxxxxxxx'
```
上記コマンドを実行するとログが表示されます。エラーが出てなければ問題ありません。Ctrl+Cで終了しましょう。

### jupyterの起動をentrypointのスクリプトの最後の方に加える。

```
(in-docker)# vi /usr/local/bin/nvidia_entrypoint.sh
```

53行目付近の「exec "/bin/bash"」を以下の通り変更します。

```
if [[ $# -eq 0 ]]; then
  xvfb-run --server-args="-screen 0 1024x768x24" jupyter notebook --allow-root --ip=0.0.0.0 --port=8888 --notebook-dir=/workspace --NotebookApp.password='sha1:xxxxxxxx:xxxxxxxxxxxxx'
  exec "/bin/bash"
else
  exec "$@"
fi
```

※```exec xvfb-run --server-args="-screen 0 1024x768x24"```を付与しているのはchainerRLのため。

###jupyterの起動確認しイメージをcommitする。(entry_pointを変更しjupyterを起動させるようにするため)

一度dockerを抜けてから、修正したイメージを作るためにcommitする。

```
(in-docker)# exit
(in-vm)# docker commit <container-id> my/gpu_chainer:1.0
```

### commitさせたjupyterでdockerを起動させる。
```
(in-vm)# nvidia-docker run -d -t \
  --shm-size=1g \
  --ulimit memlock=-1 \
  --ulimit stack=67108864 \
  -v /etc/localtime:/etc/localtime:ro \
  -v /container_data1:/workspace/data1 \
  -p 80:8888 my/gpu_chainer:1.0
```

外部からjupyterを使うことが多いと思うので、GCP ConsoleのFWの設定でhttpを許可しておきましょう。

# まとめ
　これで、あなた専用のGPU環境が作成できました。preemptibleなので、最大24時間という制約や、GCPによっていきなりVMが落とされることもありますが、HDDは残っております。学習する際にはepoch別にモデルを保存しておく等しておきましょう。GCPに限らず長期的にみればCloudを利用するよりも、GPUを購入した方が安いという判断もあるでしょう。

　みなさまの状況にあわせた選択をしていただければ幸いです。

　preemptibleなインスタンスなので、この構築作業中に落とされることもあります。おかしいな。と思うことがあれば、以下のコマンドを実行して、インスタンスが起動していることを確認すると良いと思います。

```
# インスタンス一覧の取得
gcloud compute instances list
# インスタンスの起動
gcloud compute instances start <instance-name>
```