ESXi6.7でGPUパススルーしてCUDA10とChainer4.4を使ってみる

# はじめに
ひょんな事からGTX1070Tiのグラフィックカードを入手しました。様々な環境でGPUを使ってみたいため、ESXiでパススルーさせて仮想OS上で扱えるようにしてみます。

動作確認としてChainerをインストールし、簡単ながら、仮想環境のGPU性能と物理環境のGPU性能も比較してみます。

# 環境
今回の検証環境は以下の通りです。

* ハードウェア

| ハードウェア | バージョン等 | 備考 |
| --- | --- | --- |
| CPU  | Intel Core i7-3770 3.40 GHz | |
| M/B | ASUS P8B75-M | |
| MEM | DDR3-1600 8GB x 4(32GB) | |
| GPU | GEFORCE GTX1070Ti | |
| NIC | Intel Gigabit CT Desktop Adapter EXPI9301CT | オンボードNIC(realtek)はESXiが認識せず、設定が手間なので手っ取り早くIntel NICを導入 |

* ソフトウェア

| ソフトウェア | バージョン等 | 備考 |
| --- | --- | --- |
| [VMware vSphere Hypervisor(ESXi)](https://my.vmware.com/jp/web/vmware/evalcenter?p=free-esxi6) | 6.7 | |
| [Ubuntu](https://www.ubuntulinux.jp/) | 18.04 | 仮想OSとして使用 |
| [Anaconda3](https://www.anaconda.com/)  | 5.2 Python3.6 ver | |
| Python | 3.6.5 | Anacondaにて |
| [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) | 10.0.130 |
| [CuDNN](https://developer.nvidia.com/cudnn) | 7.3.0.29 for CUDA10 |
| [CuPy](https://cupy.chainer.org/) | 4.4.1 |
| [Chainer](https://chainer.org/) | 4.4.0 |

# 環境構築の流れ

以下の流れで構築します。

1. NICの設置
* UEFI(BIOS)の設定
* ESXiのインストール、ネットワーク設定
* GPUパススルーの設定
* Ubuntuのインストール
* UbuntuでGPUを使用するための設定
* 各種ソフトウェアのインストール

## 1. NICの設置
ESXiは対応しているNICが限られており、Realtek社などのオンボードNICは対応していないことが多いです。
IntelのオンボードNICなら対応しているかもしれませんが、今回使用するオンボードNICはRealtek社製であり非対応でした。

NICのドライバを入手してESXiのイメージを作り直す方法もありますが手間なので、今回はIntel NICを導入しました。

## 2. UEFI(BIOS)の設定

GPUパススルーするにはUEFI(BIOS)の以下の設定変更が必要です。
具体的な設定項目は使用するM/Bによって異なるため、詳細は省きます。

* iGPUの無効化 / PCIeグラフィックカードの有効化
* オンボードオーディオの無効化

今回、手間取った点がオンボードオーディオの無効化でした。これも無効にしないと正しくGPUを使用できませんでした。

当然ながら、ディスプレイケーブルはG/B側に接続します。

## 3. ESXiのインストール、ネットワーク設定
これは通常通りインストールすればOKです。私はブータブルUSBにインストールしています。

インストール完了後、必要に応じてネットワーク設定を行って下さい。

## 4. GPUパススルーの設定
いよいよGPUパススルーの設定です。ブラウザでESXiにアクセスします。

左ペインの「ホスト」-「管理」を選択し、「ハードウェア」タブの「PCIデバイス」を選択します。

デバイスの一覧が表示されるため、グラフィックカードを探して選択し、「パススルーの切り替え」をクリックします。

![pic1_esxi_gpu_passthrough.png](https://qiita-image-store.s3.amazonaws.com/0/187299/f835e5dd-bf48-c621-4f5b-e2e39bed6c08.png)

切り替え後に再起動します。

私の環境の場合、再起動中に問題が発生しました。
「dma_mapper_iommu loaded successfully」という表示のまま固まってしまいます。
結果的には、このままでもESXiに接続でき仮想マシンもインストールできるのですが解決方法は見つかっていません。他のM/Bならば発生しないかもしれません。

##5. Ubuntuのインストール
これは普通にインストールしてOKです。今回はCPU6コア、メモリ24GBを割り当てています。

仮想マシンの構成パラメータとして、「hypervisor.cpuid.v0=FALSE」を追加しておきます。

なお、この段階では仮想マシンにPCIデバイスのG/Bの割り当ては行っていません。

![pic2-1_gpu_param_setting.png](https://qiita-image-store.s3.amazonaws.com/0/187299/957c3621-51a5-24a1-c3e8-5be44e757021.png)

![pic2-2_gpu_param_setting.png](https://qiita-image-store.s3.amazonaws.com/0/187299/a3863b56-801c-af76-0d62-7bac7a39512d.png)

##6. UbuntuでGPUを使用するための設定

ここからは仮想マシンのUbuntu上での作業になります。

おなじみのapt-get updateなど実行します。適宜再起動しつつ、以下を実施します。
ここではopenssh-serverもインストールし、SSHで接続して作業できるようにしています。

```sh
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install openssh-server
$ sudo apt-get install build-essential
```

GPUを使用するには、デフォルトで使用されているnouveauグラフィックスドライバを無効化しなければなりません。カーネルモジュールとしてnouveauをロードしないように設定します。

"/etc/modprobe.d/blacklist-nouveau.conf"ファイルを作成し、update-initramfsで再構築します。

```sh
$ sudo vi /etc/modprobe.d/blacklist-nouveau.conf
  # 以下を記述して保存する。
  blacklist nouveau
  options nouveau modeset=0

$ sudo update-initramfs -u
```  

ここで一度Ubuntuを終了させて、ESXiで仮想マシンの設定を行います。仮想マシンにPCIデバイスのG/Bを追加します。その後、Ubuntuを起動します。

![pic3_vm_pci-device.png](https://qiita-image-store.s3.amazonaws.com/0/187299/0655128c-18f8-4e91-e068-0138567484ea.png)

Ubuntu起動後にnouveauが無効になっており、G/Bがデバイスとして認識されていることを確認します。

```sh
$ lsmod | grep nouveau
# 何も表示されなければOK

$ lspci  | grep -i nvidia
03:00.0 VGA compatible controller: NVIDIA Corporation GP104 (rev a1)
```

#7. 各種ソフトウェアのインストール
ここからAnaconda3、CUDA Toolkit、CuDNN、CuPy、Chainerをインストールします。

* Anaconda3のインストール
通常通りインストールします。

```sh
$ wget https://repo.anaconda.com/archive/Anaconda3-5.2.0-Linux-x86_64.sh
$ sudo bash Anaconda3-5.2.0-Linux-x86_64.sh
$ python -V
Python 3.6.5 :: Anaconda, Inc.
```

* CUDA Toolkitのインストール
これも特に問題なくインストール出来ると思います。CUDAのインストール時にGPUのデバイスドライバもインストールしてくれます。

```sh
$ wget https://developer.nvidia.com/compute/cuda/10.0/Prod/local_installers/cuda_10.0.130_410.48_linux
$ sudo bash cuda_10.0.130_410.48_linux
```

環境変数として以下を~/.bashrcに追加しておきます。

```sh
export PATH=$PATH:/usr/local/cuda-10.0/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda-10.0/lib64
```

再起動後、デバイスドライバが登録されているか確認します。

```sh
$ nvidia-smi
Wed Sep 26 13:16:34 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 410.48                 Driver Version: 410.48                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 107...  Off  | 00000000:03:00.0 Off |                  N/A |
|  0%   53C    P8    13W / 180W |      0MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

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

* CuDNNのインストール

これも問題なくインストールできると思います。

```sh
$ tar xvzf cudnn-10.0-linux-x64-v7.3.0.29.tgz
$ sudo cp cuda/include/cudnn.h /usr/local/cuda-10.0/include
$ sudo cp cuda/lib64/libcudnn* /usr/local/cuda-10.0/lib64
$ sudo chmod a+r /usr/local/cuda-10.0/include/cudnn.h /usr/local/cuda-10.0/lib64/libcudnn*
```

* CuPy, Chainerのインストール

これもpipのおなじみの手順でインストールできると思います。Chainerのサンプルを使用するためダウンロードして解凍します。

```sh
$ python -m pip install -U pip
$ pip install cupy
$ pip install chainer
$ wget https://github.com/chainer/chainer/archive/v4.4.0.tar.gz
$ tar xvzf v4.4.0.tar.gz
```

# GPUでMNISTを実行してみる
Chainerのサンプルのmnistを実行してみます。

```
$ python train_mnist.py -g 0 --noplot
GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20

epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.192528    0.095931              0.941617       0.9697                    3.5658
2           0.0734889   0.0736662             0.976632       0.9775                    6.15579
3           0.0492194   0.0798578             0.984015       0.9751                    8.7438
4           0.0358381   0.0711196             0.988299       0.9808                    11.3176
5           0.0291727   0.0790036             0.990415       0.9788                    13.8838
6           0.0249285   0.0648517             0.992448       0.9828                    16.4479
7           0.0177988   0.0665626             0.994365       0.9812                    19.0179
8           0.0204571   0.0933524             0.993382       0.9786                    21.6026
9           0.0137231   0.0830202             0.995382       0.9826                    24.2013
10          0.0179817   0.079697              0.994182       0.9797                    26.7915
11          0.0129556   0.0820209             0.995815       0.9821                    29.3783
12          0.0125504   0.0999209             0.996032       0.9796                    31.9766
13          0.012244    0.0870966             0.995949       0.9825                    34.5637
14          0.00928367  0.0855221             0.997249       0.982                     37.1542
15          0.0101715   0.11876               0.996765       0.9779                    39.7451
16          0.0112296   0.108288              0.996732       0.9809                    42.3315
17          0.013817    0.0989172             0.996016       0.9817                    44.9493
18          0.00567857  0.0943258             0.998149       0.9818                    47.5357
19          0.00869481  0.120587              0.997432       0.9804                    50.1249
20          0.0106723   0.103008              0.997016       0.983                     52.7213
```

で、仮想環境ではなく、同一のハードウェア構成の物理環境にUbuntuをインストールして同じようにmnistを実行してみると以下のようになりました。

```sh
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           0.190415    0.0903942             0.943167       0.9721                    2.67092
2           0.0746116   0.0807735             0.976516       0.9726                    5.08526
3           0.0492081   0.0701688             0.984416       0.9778                    7.49376
4           0.0348724   0.0922888             0.988465       0.9734                    9.91205
5           0.0306512   0.0677831             0.989832       0.9814                    12.3288
6           0.0238598   0.0793712             0.992032       0.9781                    14.748
7           0.0199106   0.0958048             0.993465       0.9771                    17.1625
8           0.0194361   0.104865              0.993632       0.9772                    19.5808
9           0.0169871   0.103223              0.994765       0.9781                    21.9916
10          0.0141968   0.0858889             0.995565       0.978                     24.4067
11          0.0161239   0.102017              0.994415       0.9784                    26.8181
12          0.0116298   0.0891281             0.996165       0.9806                    29.2339
13          0.0124855   0.0925222             0.995999       0.9813                    31.6487
14          0.00826959  0.0935222             0.997332       0.9825                    34.0658
15          0.016028    0.0884019             0.995482       0.9828                    36.4816
16          0.00984132  0.0873142             0.996866       0.9837                    38.8963
17          0.00607726  0.0908004             0.998116       0.9841                    41.3141
18          0.0122555   0.0953374             0.996732       0.9832                    43.7299
19          0.00827334  0.0978206             0.997582       0.9824                    46.148
20          0.0105671   0.0836676             0.996965       0.9834                    48.568
```

| 環境 | 経過時間 |
| --- | ---|
| 仮想環境 | 52.7213秒 |
| 物理環境 | 48.568秒 |

GPUパススルーした仮想環境よりも物理環境の方がさすがに速いです。
それでもGPUを使って様々な環境で実験したい場合には、ESXiによる仮想化は有用かと思います。