WSL2でdocker と CUDAを綺麗に使うためには

ようやくゲームしながらUbuntuコンテナで機械学習もできる最高や！、と考え
WSL2にGPU入れようとしたら詰まって色々調べたのでまとめた。
大まかに[CUDA公式のリファレンス](https://docs.nvidia.com/cuda/wsl-user-guide/index.html)に沿って既存の環境から構築しようとしてダメだった部分を載せていきます。
`docker desktop`との共存は現時点(2020/07/16)では諦めた方が早いです。

# 前提となる環境
[6月18日のアナウンス](https://blogs.windows.com/windowsexperience/2020/06/17/announcing-windows-10-insider-preview-build-20150/)以降のwindows insider programでWSL2からGPUを叩けるようになった。

実際に使用するためには下記の環境が必要となる。

- windows 10 : ビルド番号 20150.1000以降
- WSL2: 4.19.121以降
- cudaドライバ: 455.41_gameready_win10系列
- docker(WSL上): 19.03以降

このうち、May 2020 Update以前のinsider programでWSL2+dockerが使える状態からGPU
を有効にしようとすると色々つまづいたので対処した内容も含めて導入部を書き綴っていくことにする。

# windows側
インストーラーでガシガシインストールしていく。

## windows10 20150以降のインストール

現状だと windows10 の 20150以降のバージョンは insider program をファスト（最近だとdevチャネル）に合わせてインストールする。
導入自体は直接躓くことは無かったことと分量が多くなるため、割愛する。

#### 参考元:
[Windows10 Insider Program で最新バージョンにアップデートする方法](https://www.pasoble.jp/windows/10/insider-program-update.html)

## windowsにCUDAドライバをインストールする
WSL2でCUDAドライバを利用するには`NVIDIA Drivers for CUDA on WSL`を使用する必要がある。
[NVIDIA Drivers for CUDA on WSL](https://developer.nvidia.com/cuda/wsl/download)のページからexeファイルをダウンロードし実行する。
NVIDIA Developer Program に登録しておく必要があるので Nvidiaアカウントを作成・ログインしておく。
`455.41_gameready_win10~`が落ちてくるので必然的にCUDA11が入る。プレビュー版であるため[ドライバダウンロード](https://www.nvidia.co.jp/Download/index.aspx?lang=jp)ページで検索できるドライバよりも大きいバージョンのものが落ちてくる。
インストール完了後におそらくPCの再起動を促されるので、そのまま再起動しよう。

windows側にドライバがインストールできれば、**WSL2側にCUDAドライバを入れる必要はない**

## WSL2 Linuxカーネルの更新
WSL2のカーネルもinsiderで最新なもの(4.19.121以降)じゃないと対応してない。
少し前まで手動で[「WSL 2 Linux カーネルの更新」](https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-kernel#download-the-linux-kernel-update-package)から最新カーネルをインストールする必要があったのだが
最近だと、WSL2のカーネルのアップデートが「更新プログラムのチェック」での実行対象になったため、
`「設定」->「更新とセキュリティ」->「詳細オプション」->「Windows の更新時に他の Microsoft 製品の更新プログラムを受け取る」`を**オン**することで最新のカーネルをインストールできるようになった。

![どこに存在するかが非常にわかりづらい](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614012/8301eba6-b0c9-b885-fccb-be7657511aaa.jpeg)


「更新プログラムのチェック」からのインストール完了した後は
プロンプト上で`wsl --update`を実行すれば最新カーネルをインストールできる。

のだが、自分の環境だと更新直後にGSoDに直行し起動後にwslコマンドが実行できない状態になった。。
再度、[「WSL 2 Linux カーネルの更新」](https://docs.microsoft.com/ja-jp/windows/wsl/wsl2-kernel#download-the-linux-kernel-update-package)から[インストーラ](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi)を実行し直すことで事なきを得ました。。

#### 参考元:
[WSL2が突然起動しなくなったときに参考にしてください](https://qiita.com/jun_summer/items/7950f93d65a0757ea7df)

# WSL2側
`Ubuntu18.04`を起動しCUIで操作していく。

## WSL2上にdockerをインストール
今回の最大ハマりポイント。
今回のようにWSL２上でGPUを使いたい場合はWSL2上に一から`docker 19.03`以降を導入する必要がある。
ここで注意する必要がある点として`docker desktop on windows`のbackend **を利用をしない** ということである。

### ハマった要因
自分が元々使っていた環境の場合だと、docker desktopと連携してWSL上でもdocker操作できるようにしていた。
[公式の手順](https://docs.docker.com/docker-for-windows/wsl/)にもある`Use WSL 2 based engine`の項目をチェックして
`docker desktop`のsettingの項目から`Resouce -> WSL integrate`の項目を有効にして使用する方法だ。

しかしながら、`docker desktop`との連携を切っておかないと衝突して、`docker desktop`が起動失敗->GSoD->windows強制再起動コンボが頻発する羽目になった。`Insider Preview Build 20150`初期マイナーバージョンだとGSoD後の自動再起動すらしない状態に。。


### Docker Desktop WSL 2 backendの無効化
`docker desktop`の状態をWSL側と共有する必要が無い場合は、上記の公式の手順で有効にした項目を全て無効にし、WSL上でdockerに関するエイリアスなどを貼っているならこれも無効にする必要がある。
もしくは`docker desktop`の`switch to Windows Containeres...`をクリックしてWSL側との連携を切ることもできる。

### インストール
`docker desktop`との連携が切れていることを確認できれば最新のdockerをWSL2にインストールする。
基本的には[docker公式](https://docs.docker.com/engine/install/ubuntu/)の手順通り。

``` shell:docker_install
sudo apt-get install -y \
	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 apt-key fingerprint 0EBFCD88

sudo add-apt-repository \
	"deb [arch=amd64] https://download.docker.com/linux/ubuntu\
	$(lsb_release -cs) \
	stable"

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

sudo usermod -aG docker $USER
sudo mkdir /sys/fs/cgroup/systemd
sudo mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd
```

dockerをrestartさせても設定が反映されないことがある。
その場合はCUIを一度再起動してやる。

### 動作確認
無事インストールできたか下記コマンドで確認する。

```
sudo docker run hello-world
```

`powershell`を立ち上げて`desktop docker`側に`hello-world`イメージが存在していない事を確認できれば尚良し

## wsl上のUbuntuに cuda toolkit を導入

win上にインストールしたCUDAを仮想的に使用するため、WSL2にはCUDAドライバをインストールする必要はないものの
WSL2上でもCUDAを使うために`cuda-toolkit`を導入する必要がある。

折角windows側に最新のCUDA11が入っているのでこれに合わせる事とする。
そのためにはWSL2側で`cuda-toolkit`の最新版をインストールすればよい。

対話的な処理でもいいなら[runファイル](https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1804&target_type=runfilelocal)を実行し、`cuda-toolkit`の項目だけ選択してインストールするのが多分一番簡単。
ただ、個人的になるべくスクリプトファイルを叩いた後は操作したくないためaptでインストールできるようにしていく。

[nvidiaのCUDAダウンロードサイト](https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1804&target_type=deblocalhttps://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&target_distro=Ubuntu&target_version=1804&target_type=deblocal)のスクリプト利用してインストールする。
肝は`cuda`や`cuda-driver`じゃなくて **`cuda-toolkit-11-0`** を指定すること

``` shell:install_cuda-toolkit
sudo apt-get --purge remove nvidia-*
sudo apt-get --purge remove cuda-*

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin
sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600
sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
sudo sh -c 'echo "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64 /" > /etc/apt/sources.list.d/cuda.list'
sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install -y cuda-toolkit-11-0
```

あとは使用しているシェルにPATHを追記して適用すればOK

``` shell:add_PATH
echo "set -x PATH /usr/local/cuda-11.0/bin \$PATH" >> .config/fish/config.fish
echo "set -x LD_LIBRARY_PATH /usr/local/cuda-11.0/lib64 \$LD_LIBRARY_PATH" >> .config/fish/config.fish
source .config/fish/config.fish
```

### 動作確認
インストールが完了すれば`/usr/local/cuda/`にSample含め展開される。
下記のサンプルを実行してやれば、GPUデバイスが認識できるかどうかが確認できる。

```shell:BlackScholes
cd /usr/local/cuda/samples/4_Finance/BlackScholes
sudo make
./BlackScholes
```

## nvidia-container-toolkit のインストール
nvidia dockerを動かすためにnvidia-container-toolkitを導入していく。

'Docker Desktop WSL 2 backend'経由だと**GPUサポートされていないため**、WSL2のlinux上で`nvidia-container-toolkit`か`nvidia-docker2`を使用して使う必要がある。
将来的にnvidia-docker2が廃止される予定みたいなので今回は`nvidia-container-toolkit`をインストールする。

[Nvidia-containerのQuickstart](https://github.com/NVIDIA/nvidia-docker/tree/master#ubuntu-160418042004-debian-jessiestretchbuster)を元にしながら入れていく。

``` shell:install_nvidia-container-toolkit
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
curl -s -L https://nvidia.github.io/libnvidia-container/experimental/$distribution/libnvidia-container-experimental.list | sudo tee /etc/apt/sources.list.d/libnvidia-container-experimental.list

sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo service docker restart
```

WSL2ではそのままだとsystemdが使えないためsystemctlが使えない。Quickstartの最後の行はserviceコマンドに置き換えた。

#### 参考元:
[nvidiaのフォーラム](https://forums.developer.nvidia.com/t/hiccups-setting-up-wsl2-cuda/128641/2)
[【WSL2】systemctlが動かない問題をきちんと解決する](https://qrunch.net/@shikiyura/entries/LYgUFJMzcrSpdCJT?ref=qrunch)

### 動作確認
これで漸く、nvidia-dockerがWSL2でも実行できるようになったはずだ。
下記コマンドを実行して確認する

``` shell:exec_nvidia-docker
docker run --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark

...

> Windowed mode
> Simulation data stored in video memory
> Single precision floating point simulation                                                                            > 1 Devices used for simulation
MapSMtoCores for SM 7.5 is undefined.  Default to use 64 Cores/SM
GPU Device 0: "GeForce RTX 2060 SUPER" with compute capability 7.5
> Compute 7.5 CUDA device: [GeForce RTX 2060 SUPER]
34816 bodies, total time for 10 iterations: 56.069 ms
= 216.188 billion interactions per second
= 4323.764 single-precision GFLOP/s at 20 flops per interaction
```

GPUが認識できたようだ。これでやっと機械学習ができる環境が整った訳である。

（`Compute 7.5 CUDA device`ってなんだ？と調べたところハードウェア側のバージョンを表示しているようだ。GeForce RTX 2060 SUPERはTuringアーキテクチャを採用しているので7.5となる。ソフトウェアのCUDAのバージョンかと思い焦ったのは内緒）
https://docs.nvidia.com/deploy/cuda-compatibility/index.html#support-title


## docker実行時にエラーが出た場合
もし、上記コマンドを実行した際にエラーが出てdockerイメージを実行できない場合は

1. `docker desktop`にイメージが作られていないか確認
    * 作られていれば連携している状態にあるので解除
2. WSL2からGPUが認識できているか確認
    * cudaサンプルコードを再実行してみる
    * 認識できなければ再インストール
    * 認識できている場合は`docker desktop`を使おうとしてないか確認
3. docker のオプションとして`--gpu`が利用可能か確認する
    * dockerバージョンを確認する(19.03以前にはこのコマンドは存在しない)
    * `/usr/bin/nvidia-container-toolkit`が存在する確認

以上を確認する。

### docker・nvidia-dockerのバージョンが古い

```shell:docker_run_log
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused
"process_linux.go:402: container init caused \"process_linux.go:385: running prestart hook 1 caused
...
...
```

エラーをざっくり解釈すると、指定したコマンドが間違っていたりした時に出るエラーのようで、
`--gpus`オプションが使えない、dockerバージョン(19.03以前)だったり`nvidia-docker`を使って実行しようとした時に出る模様。
各々のバージョンをアップグレードしよう。
[Upgrading with nvidia-docker2 (Deprecated)](https://github.com/NVIDIA/nvidia-docker#upgrading-with-nvidia-docker2-deprecated)

### GPUが認識できていない場合

```shell:WSL2
docker: Error response from daemon: linux runtime spec devices: could not select device driver "" with capabilities: [[gpu]].
```

docker実行時のコマンド自体は合っているが、GPUが見つからず落ちる場合。
サンプルを実行し直して認識できていなければ再インストールしよう。

ちなみに、nvidia-dockerがサポートされていないwindowsで直接実行しても似たようなエラーが得られる。

```shell:windows
docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]]
```

`docker desktop`をbackendとして使用している場合もこのエラーが出るため中々原因に気付けなかった。。
`docker desktop`との連携が有効になっていないか確認しよう。

#### 参考元:
[NVIDIA Docker って今どうなってるの？ (19.11版)](https://qiita.com/ksasaki/items/b20a785e1a0f610efa08)
[nvidia-dockerのwiki](https://github.com/NVIDIA/nvidia-docker/wiki/Frequently-Asked-Questions#is-microsoft-windows-supported)

___
今回の内容をgitでまとめました -> https://github.com/naka345/wsl2_gpu
やることが...やることが多い...！！