Kaggle公式DockerイメージをGCPにデプロイしてGPU計算環境を準備する

# ゴール

ローカルマシンからGCP上のVMインスタンスにsshして

```
$ gcloud compute ssh kaggle-gpu-compute -- -L 8080:localhost:8080
```

ブラウザからJupyterLabに接続できること

<img width="676" alt="スクリーンショット 2020-02-09 19.04.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215784/cb45180c-d001-07ca-84c9-7011b9f19d77.png">

更に、データセットをKaggleのノートブックと同じPATHで読み込めること。

<img width="955" alt="スクリーンショット 2020-02-09 19.48.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215784/2ecf7316-67d4-4882-2856-fa428cf9270f.png">


# 前提

- GPUの割り当て申請が完了していること
- gcloudのインストール、認証が完了していること
- gcloudのデフォルトゾーンが設定されていること（筆者は以下の通りに設定しています）

```
$ gcloud config set compute/zone asia-northeast1-a
```

# 手順

## VMインスタンスの作成、ログイン

以下のコマンドで一発で作成します。
OSはUbuntu 18.04、GPUはNVIDIA Tesla T4です。料金の節約のためpreemptibleで作成しています。
インスタンス名、machine-type等は適当なのでお好みに合わせて変更してして下さい。ストレージは、KaggleのDockerイメージが30GB近くあるので、コンペのデータをダウンロードすることなどを考えると100GB程度はあったほうが安心です。

```
$ gcloud compute instances create kaggle-gpu-compute \
    --image-family=ubuntu-1804-lts \
    --image-project=gce-uefi-images \
    --boot-disk-size="100" \
    --machine-type=n1-standard-2 \
    --accelerator type=nvidia-tesla-t4,count=1 \
    --maintenance-policy="TERMINATE" \
    --no-restart-on-failure \
    --preemptible
```

作成できたら、以下のコマンドでsshします。

```
$ gcloud compute ssh kaggle-gpu-compute
```

## 必要なパッケージのインストール

まっさらなUbuntuインスタンスなので、必要なパッケージをゴリゴリ入れていく必要があります。
まずGPUに対応したDockerコンテナを動かすために、以下が必要となります。

- Docker
- NVIDIA Container Toolkit

更に、Kaggleのデータセットをダウンロード、解凍するために以下が必要になります。

- Kaggle API
- unzip

これらをまとめてインストールするスクリプトを以下に用意しました。Ubuntu 18.04ならコピペで動くはずです。

```set_up.sh
sudo apt -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io
sudo curl -L https://github.com/docker/compose/releases/download//1.25.1-rc1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo add-apt-repository -y ppa:graphics-drivers/ppa
sudo apt update
sudo apt -y install ubuntu-drivers-common
sudo ubuntu-drivers autoinstall
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$(. /etc/os-release;echo $ID$VERSION_ID)/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt update
sudo apt -y install nvidia-container-toolkit
sudo reboot
```

これをインスタンス上に保存して実行します。

```
$ sh set_up.sh
```

最後まで完了するとrebootされて接続が切れるので、再起動が完了する頃合いをみてもう1度sshしてください。

## Kaggleの公式イメージをbuild

GPU版のDockerイメージはまだ公開されていないそうなので、手動でビルドする必要があります。

https://github.com/Kaggle/docker-python
> GPU: private for now, we will make it public soon.

といっても公式ドキュメントの手順通りに、リポジトリをcloneしてビルドするだけです（一時間半くらいかかりました）

```
$ git clone https://github.com/Kaggle/docker-python.git
$ cd docker-python
$ sudo ./build --gpu
```

完了すると以下の通りに`kaggle/python-gpu-build`というイメージが出来上がっています。

```
$ sudo docker images
REPOSITORY                                   TAG
kaggle/python-gpu-build                      latest
```

## kaggle/python-gpu-buildをベースにしたオリジナルのイメージを作成する

`kaggle/python-gpu-build`をそのまま使用するのではなく、これをベースに新たなイメージを作成します。
まず、後々コンテナにマウントするためのディレクトリを作っておきます。

```
$ mkdir -p ~/kaggle/input/
```

ここで作成したinputディレクトリはKaggle APIを使ってダウンロードしたデータの置き場所です。
コンテナの外からここにデータを置いて、コンテナ内からアクセスできるようにします。

Dockerfileを書いて`~/kaggle/`配下に置きます。

```Dockerfile
FROM kaggle/python-gpu-build
ENV NVIDIA_VISIBLE_DEVICES all
ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64
EXPOSE 8080
RUN mkdir -p /kaggle/working
WORKDIR /kaggle/working
CMD jupyter-lab --no-browser --port=8080 --ip=0.0.0.0 --allow-root --NotebookApp.token=''
```

設定する環境変数ですが、`NVIDIA_VISIBLE_DEVICES`はコンテナ内からどのGPUを認識できるかを指定する変数です。
`LD_LIBRARY_PATH`は以下の記事で解説されてる通り、CUDAのライブラリのPATHを通すために指定しています。
https://qiita.com/chrstphr_ml/items/c23b7ad2423c2c7b94f9#%E7%AB%8B%E3%81%A1%E4%B8%8A%E3%81%92
WORKDIRに`/kaggle/working`を指定することによって、Kaggleのノートブックと同じディレクトリ構造で動作するようにしています。
CMDにはJuputerLabを起動するコマンドを指定しています。`--NotebookApp.token=''`をつけることにより、トークンなしで接続できます。

イメージをbuildしてコンテナを起動します。

```
$ cd ~/kaggle/
$ sudo docker build -t kaggle/jupyter .
$ sudo docker run -v `pwd`:/kaggle -p 8080:8080 -d --name kaggle --restart=always --gpus all kaggle/jupyter
```

`--gpus all`はNVIDIA Container Toolkitをインストールしたことにより使用可能なオプションです。GPUを使用するためには必須です。
`--restart=always`を付けることにより、インスタンスが立ち上がると自動でコンテナが起動するようになります。

## Kaggle APIが使えるようにする

kaggle.jsonをインスタンス`~/.kaggle/`に置く必要があるので適当なテキストエディタを使って配置します。

```
$ mkdir ~/.kaggle/
$ vim ~/.kaggle/kaggle.json # put your kaggle.json
$ chmod 600 ~/.kaggle/kaggle.json
$ kaggle --version
Kaggle API 1.5.6
```

試しにタイタニックコンペのデータセットをダウンロードしてzipを展開しておきましょう（タイタニックコンペに参加している前提）。

```
$ cd ~/kaggle/input/
$ kaggle competitions download -c titanic
$ unzip titanic.zip -d titanic
```

## JupyterLabに接続する

sshポートフォワーディングを利用して、接続します。

```
$ gcloud compute ssh kaggle-gpu-compute -- -L 8080:localhost:8080
```

これでsshで接続している間は http://localhost:8080/ からJupyterLabに接続できるはずです。

# 動作確認

適当なノートブックを作成して、以下のコードを実行してください。

```python

# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load in 

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the "../input/" directory.
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os
for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))

# Any results you write to the current directory are saved as output.
```

先程ダウンロードしたデータセットが見れるはずです。

<img width="955" alt="スクリーンショット 2020-02-09 19.48.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215784/2ecf7316-67d4-4882-2856-fa428cf9270f.png">

また以下のコードを実行して`True`が返ってくればGPUも問題なく認識しています。

```python
from tensorflow.python.client import device_lib
device_lib.list_local_devices()
```

nvidia-smiを実行してGPUの使用状況を確認することもできます。

```
!nvidia-smi
Sun Feb  9 11:07:41 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.48.02    Driver Version: 440.48.02    CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla T4            Off  | 00000000:00:04.0 Off |                    0 |
| N/A   45C    P0    25W /  70W |    222MiB / 15109MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
```