GPU搭載WindowsのWSL2でNGCカタログのコンテナイメージを使う

# はじめに

自宅PCをリニューアルしたときから、画策していたこと。。。

それは、搭載した**GPU（NVIDIA GeForce GTX 1660 SUPER）**を使った分析環境を仕立てること！

過去の記事([※1](https://qiita.com/Blaster36/items/bcbd1a2bd82bdfcc555e)）では、VirtualBox／VagrantでUbuntu／Dockerを準備し、JupyterHub／JupyterLabを起動した。
が、その構成だと、GPUが使えないという状況にあった。。。せっかく搭載されているのに活かせず。。。

Windows Insider Previewに参加しながら、WSLの動向に気にしていたのだが、状況は変わってきた様子！！

というわけで、**「JupyterHubを利用した分析環境を、WSLに移行＆NGCカタログでGPU使用に構成変更かけてゆく流れ」**を紹介してゆく。

## 本稿で紹介すること

- Dockerの環境設定
- JupyterHub／JupyterLabの環境設定
- Dockerコンテナイメージのビルド based on NGC Catalog
- JupyterHub／JupyterLabの起動
- GPUデバイスの認識確認（TensorFlowおよびPyTorch）

[jupyterhub | Read the Docs](https://readthedocs.org/projects/jupyterhub/)
[JupyterHub &#8212; JupyterHub 1.0.0 documentation](https://jupyterhub.readthedocs.io/en/1.0.0/)
[GitHub - jupyter/docker-stacks: Ready-to-run Docker images containing Jupyter applications](https://github.com/jupyter/docker-stacks)

## 本稿で紹介しないこと

- Windows Insider Previewの参加（およびWindiws Update）　※Microsoft Windows [Version 10.0.22000.120]を使用
- GPUドライバのインストール（For NVIDIA GeForce GTX 1660 SUPER） ※NVIDIAドライバ: 471.41
- WSL2のインストール
- Ubuntuのインストール（From Microsoft Store）　※Ubuntu 18.04.5 LTSを使用
- Dockerのインストール　※Docker Community Edition 20.10.8を使用
- NVIDIA Container Toolkitのイントール　※nvidia-docker2 2.6.0を使用
- NGC Catalogの全般（Requirements等々）
- Pythonライブラリ（MeCab、TensorFlow、PyTorch）の全般

[待ってました CUDA on WSL 2](https://qiita.com/ksasaki/items/ee864abd74f95fea1efa)
[WSL2を用いたdockerでGPU認識（2020年8月）](https://qiita.com/siroiruka/items/ad8128586a78a49537b4)
[TensorFlow | NVIDIA NGC](https://ngc.nvidia.com/catalog/containers/nvidia:tensorflow)
[TensorFlow Release Notes :: NVIDIA Deep Learning Frameworks Documentation](https://docs.nvidia.com/deeplearning/frameworks/tensorflow-release-notes/)
[PyTorch | NVIDIA NGC](https://ngc.nvidia.com/catalog/containers/nvidia:pytorch)
[PyTorch Release Notes :: NVIDIA Deep Learning Frameworks Documentation](https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes/)

# 環境構築
大きく、4ステップです。これら、WSL2／Ubuntu上で実行します。
1. Dockerの環境設定
2. JupyterHub／JupyterLabの環境設定
3. Dockerコンテナイメージのビルド based on NGC Catalog
4. JupyterHub／JupyterLabの起動

## 事前準備
以下のリンクに記載された手順を見本とし、WSL2のUbuntu18上でNVIDIA Container Toolkitのインストールまで進めます。
[待ってました CUDA on WSL 2](https://qiita.com/ksasaki/items/ee864abd74f95fea1efa)
[WSL2を用いたdockerでGPU認識（2020年8月）](https://qiita.com/siroiruka/items/ad8128586a78a49537b4)

:::note info
以下、筆者の環境です。
:::

```
C:\>ver

Microsoft Windows [Version 10.0.22000.120]
```

```
C:\>nvidia-smi
Tue Aug 10 09:25:51 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 471.41       Driver Version: 471.41       CUDA Version: 11.4     |
|-------------------------------+----------------------+----------------------+
| GPU  Name            TCC/WDDM | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ... WDDM  | 00000000:01:00.0  On |                  N/A |
| 42%   38C    P8    14W / 125W |    619MiB /  6144MiB |      1%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
```

```shell:
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.5 LTS"
```

```shell:
$ docker version
Client: Docker Engine - Community
 Version:           20.10.8
 API version:       1.41
 Go version:        go1.16.6
 Git commit:        3967b7d
 Built:             Fri Jul 30 19:54:08 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.8
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.6
  Git commit:       75249d8
  Built:            Fri Jul 30 19:52:16 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.9
  GitCommit:        e25210fe30a0a703442421b0f60afac609f950a3
 nvidia:
  Version:          1.0.1
  GitCommit:        v1.0.1-0-g4144b63
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

```shell:
$ sudo dpkg -l | grep -i nvidia
ii  libnvidia-container-tools      1.4.0-1                             amd64        NVIDIA container runtime library (command-line tools)
ii  libnvidia-container1:amd64     1.4.0-1                             amd64        NVIDIA container runtime library
ii  nvidia-container-runtime       3.5.0-1                             amd64        NVIDIA container runtime
ii  nvidia-container-toolkit       1.5.1-1                             amd64        NVIDIA container runtime hook
ii  nvidia-docker2                 2.6.0-1                             all          nvidia-docker CLI wrapper
```

NGC Catalog（[※2](https://ngc.nvidia.com/catalog/containers/nvidia:tensorflow)、[※3](https://ngc.nvidia.com/catalog/containers/nvidia:pytorch)）より、TensorFlowおよびPyTorchのコンテナイメージを取得しておきます。
**Python 3.6**を使いたいため、過去タグ「20.11-*」を取得しています。

```shell:
$ docker images
REPOSITORY                         TAG                   IMAGE ID       CREATED         SIZE
jupyterhub/jupyterhub              1.0-custom            972a2af201e9   2 hours ago     418MB
jupyter/minimal-notebook           2343e33dec46-custom   4d9dcbd96625   16 hours ago    3.47GB
nvcr.io/nvidia/tensorflow          20.11-tf2-py3         98a7952f7f9c   9 months ago    11.6GB
nvcr.io/nvidia/pytorch             20.11-py3             ae35b2b3cad1   9 months ago    13.2GB
jupyterhub/jupyterhub              1.0                   c399e04fda3c   20 months ago   283MB
jupyter/minimal-notebook           2343e33dec46          c3bbd3471e39   2 years ago     2.72GB
hrektts/fusiondirectory-openldap   1.1.9-1.2-1           7f2e4370509d   4 years ago     226MB
hrektts/fusiondirectory            0.2.0                 b56f1086a08d   4 years ago     345MB
```

Dockerネットワーク「jupyterhub」を作成しておきます。形態は**Bridge**です。

```shell:
$ docker network ls
NETWORK ID     NAME         DRIVER    SCOPE
d880009721fa   bridge       bridge    local
dc9d83b0f49c   host         host      local
d26548c59f23   jupyterhub   bridge    local
18e4f9062739   none         null      local
```

## 1. Dockerの環境設定

以下のリンクに記載された手順を見本とし、DockerのDefault RuntimeをGPUが使えるRuntimeへ変更します。
[Upgrading to the NVIDIA Container Runtime for Docker :: DGX Systems Documentation](https://docs.nvidia.com/dgx/nvidia-container-runtime-upgrade/index.html#using-nv-container-runtime)

以下、環境設定の例です。★マークの付いた行を追記・保管後、Dockerサービスを再起動します。

```shell:
$ cat /etc/docker/daemon.json
{
    "default-runtime": "nvidia",　★本行を追記
    "runtimes": {
        "nvidia": {
            "path": "nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}
```

## 2. JupyterHub／JupyterLabの環境設定

以下のリンクに記載された項目を参考に、JupyterHub／JupyterLabの振る舞いを決めて、**環境設定ファイル（jupyterhub_config.py）**に記述します。
[jupyterhub | Read the Docs](https://readthedocs.org/projects/jupyterhub/)
[JupyterHub &#8212; JupyterHub 1.0.0 documentation](https://jupyterhub.readthedocs.io/en/1.0.0/)
[GitHub - jupyter/docker-stacks: Ready-to-run Docker images containing Jupyter applications](https://github.com/jupyter/docker-stacks)

以下、過去の記事([※1](https://qiita.com/Blaster36/items/bcbd1a2bd82bdfcc555e)）からの変更点です。

- ①LDAPサーバのアドレス変更
- ②NGC Catalogのカスタムイメージを選択肢に登録

:::note warn
①LDAPサーバのアドレス≒WSL2／Ubuntuのeth0ですが、
WSL2／Ubuntuを起動するたびに変わっているようで、その点注意。
:::

以下、環境設定ファイル（jupyterhub_config.py）です。

```python:jupyterhub_config.py
c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator'
c.LDAPAuthenticator.valid_username_regex = r'^[a-z0-9][.a-z0-9_-]*$'
c.LDAPAuthenticator.use_ssl = False
c.LDAPAuthenticator.server_address = '${WSL2／Ubuntuのeth0}'　★変更点①
c.LDAPAuthenticator.server_port = 389
c.LDAPAuthenticator.bind_dn_template = 'uid={username},ou=people,ou=jhub,dc=hoge,dc=com'
c.LDAPAuthenticator.lookup_dn = False

c.JupyterHub.hub_connect_ip = 'jupyterhub'
c.JupyterHub.hub_ip = '0.0.0.0'
c.Authenticator.admin_users = {'admin', 'jupyter'}

from dockerspawner import DockerSpawner

class DemoFormSpawner(DockerSpawner):
  def _options_form_default(self):
    default_stack = 'jupyter/minimal-notebook'
    return """
    <label for="stack">Select your desired stack</label>
    <select name="stack" size="1">
      <option value="jupyter/minimal-notebook:2343e33dec46">jupyter/minimal-notebook</option>
      <option value="jupyter/minimal-notebook:2343e33dec46-custom">jupyter/minimal-notebook(w/MeCab)</option>
      <option value="nvcr.io/nvidia/tensorflow:20.11-tf2-py3-custom">nvcr.io/nvidia/tensorflow</option>　★変更点②
      <option value="nvcr.io/nvidia/pytorch:20.11-py3-custom">nvcr.io/nvidia/pytorch</option>　★変更点②
    </select>
    """.format(stack=default_stack)

  def options_from_form(self, formdata):
    options = {}
    options['stack'] = formdata['stack']
    container_image = ''.join(formdata['stack'])
    print('SPAWN: ' + container_image + 'IMAGE')
    self.image = container_image
    return options

c.JupyterHub.spawner_class = DemoFormSpawner

notebook_dir = '/home/jovyan'
c.DemoFormSpawner.notebook_dir = notebook_dir
c.DemoFormSpawner.volumes = {'jupyterhub-user-{username}':notebook_dir}
c.DemoFormSpawner.remove = True
c.DemoFormSpawner.default_url = '/lab'
c.DemoFormSpawner.start_timeout = 300
c.DemoFormSpawner.http_timeout = 120
c.DemoFormSpawner.network_name = 'jupyterhub'
```

## 3. Dockerコンテナイメージのビルド based on NGC Catalog

以下、コンテナイメージのビルド用環境設定ファイル（TensorFlow版）です。
PyTorch版は、先頭行のFROMの指定を変更するのみです。

```shell:Dockerfile.tensorflow.lab
FROM nvcr.io/nvidia/tensorflow:20.11-tf2-py3

RUN python3 -m pip install jupyterhub==1.0.0

RUN useradd -m jovyan
ENV HOME=/home/jovyan
WORKDIR $HOME
USER jovyan

CMD ["jupyterhub-singleuser"]
```

以下、ビルドコマンドです。

```shell:
$ docker image build -t nvcr.io/nvidia/tensorflow:20.11-tf2-py3-custom -f Dockerfile.tensorflow.lab .
```

## 4. JupyterHub／JupyterLabの起動

以下、環境設定（環境変数の定義）を含む起動コマンドです。
過去の記事([※1](https://qiita.com/Blaster36/items/bcbd1a2bd82bdfcc555e)）からの変更なしです。

```shell:
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8000:8000 -p 8001:8001 -p 8081:8081 \
  -d --name jupyterhub --net jupyterhub jupyterhub/jupyterhub:1.0-custom
```

Webブラウザで http://127.0.0.1:8000/hub/login にアクセスし、JupyterHubのログイン画面が表示できればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/e23e707e-abfa-3486-8e4a-0126f3e572a1.png)

# では早速、動作確認

TensorFlowおよびPyTorchで、GPUデバイスが認識できているか否か、を確認します。
結果、**GPU（NVIDIA GeForce GTX 1660 SUPER）**がきちんとPythonプログラムから認識できています！！！

これで、Google Colaboratoryに頼らずとも、手元の環境でPythonプログラムを実行できるようになります。
・・・というほど、普段は負荷／データ量においてGoogle Colaboratoryでの限界を感じてはいませんが。。。

## TensorFlowでのGPUデバイス確認画面

以下、確認用のCode例です。

```python:
import tensorflow as tf

gpu_devices = tf.config.list_physical_devices('GPU')
if gpu_devices:
  details = tf.config.experimental.get_device_details(gpu_devices[0])
  print(details)
# {'compute_capability': (7, 5), 'device_name': 'NVIDIA GeForce GTX 1660 SUPER'}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/cbd3430d-f3ef-d061-224e-3045beca3409.png)

ちなみに、このときコンテナ一覧を確認すると、NGC Catalogをカスタムしたイメージからコンテナが起動されていることがわかります。

```shell:
$ docker ps -a
CONTAINER ID   IMAGE                                            COMMAND                  CREATED              STATUS              PORTS                                                                                                      NAMES
0967a1df5d63   nvcr.io/nvidia/tensorflow:20.11-tf2-py3-custom   "/usr/local/bin/nvid…"   About a minute ago   Up About a minute   6006/tcp, 6064/tcp, 8888/tcp
```

## PyTorchでのGPUデバイス確認画面

以下、確認用のCode例です。

```python:
import torch

print(torch.cuda.get_device_name())
# NVIDIA GeForce GTX 1660 SUPER
print(torch.cuda.get_device_capability())
# (7, 5)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/685193/966f056e-8d5e-1220-2944-7057903a73f0.png)

こちらも同じく、コンテナ一覧を確認すると、NGC Catalogをカスタムしたイメージからコンテナが起動されていることがわかります。

```shell:
$ docker ps -a
CONTAINER ID   IMAGE                                     COMMAND                  CREATED        STATUS       PORTS                                                                                                      NAMES
6fb7d292bbf8   nvcr.io/nvidia/pytorch:20.11-py3-custom   "/usr/local/bin/nvid…"   3 hours ago    Up 3 hours   6006/tcp, 8888/tcp
```

# まとめ
自宅PCをリニューアルしたときから描いていた構成を実現できました。これで、GPUを使ったPythonプログラムを動かせます。

とはいえ、VirtualBox／Vagrantを使うケースも存在するので、適材適所で使ってゆこうと考えています。
（ちょっと前は、WSLとVirtualBoxが上手く共存できない、という状況だったと思うのですが。。。進歩のスピードが素晴らしい。）