Azure で NVIDIA GPU を使うには

[Docker Advent Calendar 2016](http://qiita.com/advent-calendar/2016/docker) の 11 日目です。昨日は @nekop さんの「[Dockerfileの無駄なレイヤリングを排除してビルドを高速化する](http://nekop.hatenablog.com/entry/2016/12/09/112301)」でした。

# GPU ですよ

最近仕事で GPU が欠かせないので、備忘を兼ねて、[Azure](https://azure.microsoft.com/ja-jp/) の GPU が搭載された新しい [N シリーズ](https://blogs.technet.microsoft.com/jpitpro/2016/11/22/azure-n-series-general-availability-on-december-1/) で NVIDIA GPU を使う + Tips を残します。

# Azure で VM 起動

## Linux Data Science Virtual Machine

最近 [機械学習向けのマシンイメージ](https://docs.microsoft.com/ja-jp/azure/machine-learning/machine-learning-data-science-linux-dsvm-intro) が出たとのことでこれを使おうとしたのですが、残念、GPU 未対応のようです。がっつり学習に利用するというより、試行錯誤するフェーズ向けな気もしますし、私の目論見が不純でした、すみません・・。


## 制限

で、現状は、公式の [Set up GPU drivers for N-series VMs](https://github.com/Microsoft/azure-docs/blob/master/articles/virtual-machines/virtual-machines-linux-n-series-driver-setup.md) にあるように
`Linux の GPU サポートは現在、Ubuntu Server 16.04 LTS で稼働する Azure NC シリーズの仮想サーバのみです`
とのことなので、該当のサーバイメージを探します。探したら、

## CLI で起動

South Central US で N シリーズの VM を 1 台起動します。
以下、CLI は [v1](https://docs.microsoft.com/ja-jp/azure/xplat-cli-install) を使います。

マシン起動時の大事な引数は以下のとおり。

| キー | 値 |
|:--|:--|
| --os-type | Linux |
| --location | southcentralus |
| --vm-size | Standard_NC6 |
| --image-urn | Canonical:UbuntuServer:16.04-LTS:16.04.201612050 |


鍵はあらかじめ用意しつつ、残りの引数はよしなに変えて実行します。

```sh:create-vm
$ azure vm create -g gpus -n demo --os-type Linux \
    --location southcentralus --vm-size Standard_NC6 \
    --image-urn Canonical:UbuntuServer:16.04-LTS:16.04.201612050 \
    --admin-username azure --ssh-publickey-file key.pub \
    --vnet-name demo-vnet --vnet-subnet-name demo-subnet \
    --nic-name demo-nic --public-ip-name demo-pip \
    --storage-account-name demogpu20161212
```

仮想ネットワークの CIDR ブロックとパブリック DNS 名を対話的に決めしばらくすると、サーバが起動します。

#  GPU 諸々セットアップ

## 1. SSH ログイン

```sh:ssh
$ ssh -i key azure@<domain-name>.southcentralus.cloudapp.azure.com
```

ドメイン名には、先ほど VM 起動時に対話的に決めた名前が入ります。

## 2. GPU チェック

CUDA が使える GPU であることをチェックしてみます。

```sh:check-cuda-capability
azure@demo:~$ lspci | grep -i NVIDIA

a848:00:00.0 3D controller: NVIDIA Corporation GK210GL [Tesla K80] (rev a1)
```

## 3. NVIDIA ドライバインストール

```sh:install-nvidia-driver
sudo apt-get update
sudo apt install -y gcc make
wget -O /tmp/NVIDIA-Linux-x86_64-375.20.run http://us.download.nvidia.com/XFree86/Linux-x86_64/375.20/NVIDIA-Linux-x86_64-375.20.run
chmod +x /tmp/NVIDIA-Linux-x86_64-375.20.run
sudo sh /tmp/NVIDIA-Linux-x86_64-375.20.run
```

ダイアログに従って、ライセンスに `Agree` した後は OK で進めていきます。
インストールが終わったら、デバイスの状態を `nvidia-smi` で確かめてみます。

```sh:query-the-gpu-device-state
azure@demo:~$ nvidia-smi

Mon Dec 12 08:36:20 2016
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.20                 Driver Version: 375.20                    |
|-------------------------------+----------------------+----------------------+
| 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 K80           Off  | A848:00:00.0     Off |                    0 |
| N/A   43C    P0    74W / 149W |      0MiB / 11471MiB |     96%      Default |
+-------------------------------+----------------------+----------------------+

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

見えましたね。

# Docker 諸々セットアップ

ホストには最低限 `GPU ドライバ` のみをインストールし、その上のアプリケーションはなんでも動かしやすいよう、Docker ベースの環境にします。

## 1. Docker インストール

```sh:install-docker
$ curl -sSL https://get.docker.com/ | sh
$ sudo service docker start
$ sudo usermod -aG docker azure
```

azure ユーザでも sudo なしで docker コマンドが使えるようにいったん SSH から抜け入り直します。

## 2. nvidia-docker インストール

```sh:install-nvidia-docker
$ wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb
$ sudo dpkg -i /tmp/nvidia-docker*.deb && rm /tmp/nvidia-docker*.deb
```

## 3. 起動チェック

（初回ダウンロード時は少し時間がかかりますが）
先ほどの `nvidia-smi` を Docker 経由で実行してみます。

```sh:nvidia-smi-with-docker
azure@demo:~$ nvidia-docker run --rm nvidia/cuda nvidia-smi

Using default tag: latest
latest: Pulling from nvidia/cuda
04cf3f0e25b6: Pull complete
..
Digest: sha256:e8ad1b9d0c3840ba07da8b622a0ede291c0c24d4025bda0a8f488b00d34c0eee
Status: Downloaded newer image for nvidia/cuda:latest
Mon Dec 12 08:50:47 2016
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.20                 Driver Version: 375.20                    |
|-------------------------------+----------------------+----------------------+
| 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 K80           Off  | A848:00:00.0     Off |                    0 |
| N/A   38C    P8    33W / 149W |    121MiB / 11471MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

いいですね。コンテナの中からもホストの GPU が見えました！


# Docker の起動フラグ

nvidia-docker を使うととても楽ができるものの、最後に、それがなくとも Docker から GPU を使うための方法をまとめ直します。

* --privileged
* --device

## --privileged

特権モードとなるため、ホストの **すべてのデバイス** がそのまま見えます。
必要以上に権限を渡すことになるため、後述の `--device` の方が望ましい。

```sh:privileged
azure@demo:~$ docker run -it --rm --privileged nvidia/cuda sh
root# ls -la /dev | grep nvidia

crw-rw-rw-  1 root root    245,   0 Dec 12 10:05 nvidia-uvm
crw-rw-rw-  1 root root    245,   1 Dec 12 10:05 nvidia-uvm-tools
crw-rw-rw-  1 root root    195,   0 Dec 12 10:05 nvidia0
crw-rw-rw-  1 root root    195, 255 Dec 12 10:05 nvidiactl
```

## --device

特定のデバイスのみにアクセスを許可するには `--device` を使います。`:rwm` オプションによって読み込み専用などの読み書き制御も付与できます。

nvidia-docker が内部的に使っているのはこれ。実際に、nvidia-docker-plugin に HTTP リクエストで問い合わせれば docker に渡しているオプションを確認することもできます。

```sh:nvidia-docker-plugin
azure@demo:~$ curl 127.0.0.1:3476/v1.0/docker/cli

--volume-driver=nvidia-docker --volume=nvidia_driver_375.20:/usr/local/nvidia:ro --device=/dev/nvidiactl --device=/dev/nvidia-uvm --device=/dev/nvidia-uvm-tools --device=/dev/nvidia0
```

つまり nvidia-docker で docker をラップすると、内部的に以下の引数を `docker run` に加えていることがわかります。

|オプション | 値 |
|:--|:--|
| --volume-driver | nvidia-docker |
| --volume | nvidia_driver_375.20:/usr/local/nvidia:ro |
| --device | /dev/nvidiactl |
| --device | /dev/nvidia-uvm |
| --device | /dev/nvidia-uvm-tools |
| --device | /dev/nvidia0 |

--device オプションを使ってみると

```sh:device
azure@demo:~$ docker run -it --rm --device=/dev/nvidia0 nvidia/cuda sh
root# ls -la /dev | grep nvidia

crw-rw-rw-  1 root root 195,   0 Dec 12 10:16 nvidia0
```

期待通り、渡した GPU だけが見えました。

おわり