MacBookPro + eGPU + PyTorchでディープラーニングしてみる

# はじめに
普段はUbuntuでGPUを使っていますが、MacでもGPUで計算したくなりました。eGPUの存在を知ったので、セットアップしてみた。

# 注意
- PCの画像出力をGPU経由で行うようにできていませんが気にしていません。
- 以下の手順ではホットスタートできるようにはなりませんでした。
- GPU1枚で遊ぶので、ncclは入れていません。

# 環境

* MacBook Pro 2017
* High Sierra 10.13.2 （10.13.6よりあとでは動かないらしいので要ダウングレード）
* GTX1080 ti （ZTGTX1080Ti-11GD5MIN）
* Razor Core X
* Nvidia Web Driver (378.10.10.10.25.102)
* CUDA 10.0 (V10.0.130)
* cuDNN 7.3
* anaconda3-5.0.1 (pyenv)
* PyTorch 1.0
* Apple LLVM version 9.0.0 (clang-900.0.37)

# 後日メモ
### CUDAのバージョンの確認
```
cat /usr/local/cuda/version.txt
```


# eGPUのセットアップ
## SIPをオフにする
最近のMacOSには、System Integrity Protection(SIP)なるセキュリティー保持のための機能が追加されましたが、eGPUのセットアップの段階では妨げになるのでOFFにします。
### リカバリーモードで起動
電源ボタンを押した後、りんごマークとともにロードしてるっぽいブート画面に入る前から、Commandキー（⌘）とRキーを押下しておきます。
ユーティリティー > ターミナル
を選択した後、

```
$ csrutil disable
```

を入力します。
場合によっては、言語の選択画面が出てきますが、日本語を選ばなくても特に問題ないです。
### 再起動する
ターミナル画面のままで、

```
$ reboot
```
を入力すればmacOSが再起動します。

## macOS-eGPU.shを実行する
macOSはオフィシャルではGTX系のGPUをサポートしていません。困ったものですが、GTX系のGPUを使ったeGPUを使いたい人のために、セットアップスクリプトがいくらか公開されています。

* [macOS-eGPU.sh](https://egpu.io/forums/mac-setup/script-fr34ks-macos-egpu-sh-one-script-all-solutions-fully-automated/#post-35722)
* [うまくいかない](https://egpu.io/forums/mac-setup/automate-egpu-sh-is-reborn-with-amd-polaris-fiji-support-for-macos/)

「This script is not applicable to High Sierra.」を見落して、うまくいかないほうで一度スクリプトを回しきりましたが、案の定失敗しました。知らないふりをしてその上からmacOS-eGPU.shを実行してみたら、GPUが認識されるようになっていました。

```
$ bash <(curl -s https://raw.githubusercontent.com/learex/macOS-eGPU/master/macOS-eGPU.sh)
```
これだけでできるすごいスクリプト。[ここ](https://github.com/learex/macOS-eGPU#step-by-step-guide)にも書いてありますが、必ずバックアップを取りましょう（自戒）。それぞれの作業環境によって、うまくいかないどころかいろいろ吹き飛ぶ可能性は否定出来ないですからね。

## GPUが認識されたことを確認する

Apple メニュー () > システム情報
から、
ハードウェア > グラフィックス/ディスプレイ
を選択し、ビデオカードのリストに自分のGPUが表示されているかを見ます。
<img width="863" alt="システム情報.png" src="https://qiita-image-store.s3.amazonaws.com/0/207596/23f26504-a83e-da07-7e50-5f33d7f56973.png">
ちなみに、「システム情報」は、Appleメニューを開いた状態でoptionボタンを押すと表示されます。

# PyTorchのセットアップ

## Nvidia Driver Managerをインストールする
必要なのかわかりませんが、一応入れました。
この[スクリプト](https://github.com/vulgo/webdriver.sh)を使いました。すごくあっさり終わります。

## CUDAをインストールする
GPUとの情報のやり取りにはCUDA(Compute Unified Device Architecture)を使います。ここではv10.0を使います。dmgファイルを[公式](https://developer.nvidia.com/cuda-downloads)からダウンロードしてインストールする方法もありますが、何故かうまく行きませんでした。オフィシャルの[ドキュメント](https://docs.nvidia.com/cuda/cuda-installation-guide-mac-os-x/index.html)上にはありませんでしたが、homebrewでCUDAをインストールすることにします。

> 以下の３つは試して失敗した例です。失敗とは、PyTorchのビルドが途中で止まるケースのことです。
>
* CUDA9.2(dmg local) + cuDNN7.4.1
* CUDA9.0(dmg local) + cuDNN7.4.1
* CUDA10.0(dmg local) + cuDNN7.3.0

まずはcaskをインストールします（私はすでに入っていたのでやってません）

```
xcode-select --install

brew uninstall --force brew-cask; brew update
brew tap caskroom/cask
```

次にCUDAをインストールします（こっちはやりました）

```
brew upgrade
brew install coreutils
brew install swig
brew install bazel

# brew cask install cuda の代わりに下記
brew tap caskroom/drivers
brew cask install nvidia-cuda
```
上記caskインストール〜CUDAインストールは[こちら](https://qiita.com/yamadar/items/c2a3997d0e8eeb80563e)を参考にしました。ありがとうございました。

#### うまく行っていることの確認
この段階でCuPyはインストールできました。上記リンクから試してみると安心できます。
もしかすると、[Xcode Command Line Toolsのダウングレード](https://qiita.com/yamadar/items/c2a3997d0e8eeb80563e#xcode-command-line-tools-%E3%81%AE%E3%83%80%E3%82%A6%E3%83%B3%E3%82%B0%E3%83%AC%E3%83%BC%E3%83%89)が必要かもしれません。

## cuDNNをインストールする
cudnn.h、libcudnn_static.a等各ファイルを特定の場所にコピーすることでインストールします。オフィシャルの[ドキュメント](https://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html#install-mac)は、CUDA9.0+cuDNN7系で表記してありますが、参考になります。
まずは、インストールしたCUDA10.0に対応したcuDNNを、[ここ](https://developer.nvidia.com/rdp/cudnn-download)からダウンロードします。アカウントがなければ作成しましょう。以下は、「cudnn-10.0-osx-x64-v7.3.0.29.tgz」をダウンロードして実行しました。

```
$ tar xzvf cudnn-10.0-osx-x64-v7.3.0.29.tgz
```
解凍できたら、

```
$ sudo cp cuda/include/cudnn.h /usr/local/cuda/include
$ sudo cp cuda/lib/libcudnn* /usr/local/cuda/lib
$ sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib/libcudnn*
```
これで適切なディレクトリにコピーできました。

## PyTorchをビルドする
PyTorchは、macOS向けのGPUを使えるwheelを提供していません。仕方ないのでソースからビルドします。手順は[ここ](https://github.com/pytorch/pytorch#from-source)にあります。pipでも通るかもしれませんが、オフィシャルがcondaを推奨しているのでcondaでがんばります。私は、pyenvでanacondaを準備しました。

```
$ git clone --recursive https://github.com/pytorch/pytorch
```
でレポジトリをクローンした後、

```
$ export CMAKE_PREFIX_PATH=`which anaconda`
$ conda install numpy pyyaml mkl mkl-include setuptools cmake cffi typing
```
で必要なライブラリをインストールし、

```
$ MACOSX_DEPLOYMENT_TARGET=10.13 CC=clang CXX=clang++ python setup.py install
```
で実行します。
ちなみに、anaconda root directoryは

```
$ which anaconda
```
で確認できます。あとは数時間待機します。
ビルド終了直後にREPLから実行しようとしたところ、何故かCUDAが有効じゃない旨のエラーを吐きましたが、ターミナルを落としてもう一度立ち上げ直して実行したところうまくできていました。ちゃんと動くかの確認は

```
>>> import torch
>>> a = torch.tensor(1)
>>> a.to("cuda")
tensor(1, device='cuda:0')
```
で十分でしょうか。

# その他
## 問題点がいろいろあります。

* thunderbolt3ケーブルを抜くとOSが落ちる。
* （私の環境では）GPU経由で画面を出力できない。
* 有効に使うためには、ブート画面表示時にthunderboltで繋がなければいけない。
* brewでインストールできるCUDAのバージョンはその時々で変わる。
* nvidia-smi的なものがない。

## 一応MNIST回しました
この[サンプルコード](https://github.com/pytorch/examples/blob/master/mnist/main.py)を使いました。

- GPU : 75.07196378707886秒
- CPU : 392.6573271751404秒

期待したほど早くなかったです。Thunderbolt3の通信速度が40Gbpsなのに対して、PCIe x16が128Gbpsであるのがボトルネックかと思うので、トレーニングデータもGPUに全部のせてGPUだけで計算を行えばだいぶ早くなるのか・・・？