WSL2 + Dockerのメモ

# 想定環境
- WSL2 on Windows11
- with NVIDIA GPU

# Install
## WSL2 (Ubuntu) のインストール
- 参照: https://docs.microsoft.com/ja-jp/windows/wsl/install

```bash:パワーシェル(管理者権限)
wsl --install -d Ubuntu-20.04
```

- イメージ置き場
    - `C:\Users\iwatake\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState`
- WindowsからWSLファイルへのアクセス
    - `\\wsl.localhost\Ubuntu-20.04`
- WSLからWindowsファイルへのアクセス
    - `/mnt/c/`
- WSLのGUIアプリを実行する場合、Windows側にX表示用アプリ (VcXsrvなど) は不要。だけど、Docker上のGUIアプリを実行する場合は必要っぽい

## VSCode
- Windows上のVSCodeに`ms-vscode-remote.vscode-remote-extensionpack` をインストールしておく
- WSL上で、`sudo apt install wget ca-certificates` しておく

### WindowsのVSCodeからWSL上のDockerコンテナ内のファイルにアクセスする
- VSCodeをWindowsで開く
- Remote Explorer -> WSL Targets -> Ubuntu-20.04(WSL) -> Connect to WSL
- Remote Explorer -> Containers -> Target container -> Attach to container

## NVIDIA CUDA in WSL のインストール (optional)
- NVIDIA Drivers for CUDA on WSLをインストール
    - https://developer.nvidia.com/cuda/wsl/download
        - 510.06_gameready_win11_win10-dch_64bit_international.exe
- 参考
    - https://docs.microsoft.com/ja-jp/windows/ai/directml/gpu-cuda-in-wsl

## Dockerのインストール
- 参考
    - https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html
    - https://qiita.com/ksasaki/items/ee864abd74f95fea1efa#wsl-2-%E4%B8%8A%E3%81%AE-linux-%E3%81%B8%E3%81%AE-nvidia-container-toolkit-%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%88%E3%83%BC%E3%83%AB


```bash:WSL
# Docker
curl https://get.docker.com | sh
sudo usermod -aG docker $USER

# NVIDIA Container Toolkit (optional)
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-docker2
sudo service docker restart
sudo service docker start
```

# Docker よく使うコマンド
## 管理コマンド
```bash:基本
sudo service docker start    # WSLの場合、毎回startが必要
# docker image ls
docker images
# docker container ls -a
docker ps -a
docker rm  MY_CONTAINER_NAME
docker rmi IMAGE_NAME
```


```bash:保存(コンテナからイメージ作成)
docker commit MY_CONTAINER_NAME my_new_image_from_container
docker save my_new_image_from_container -o my_new_image_from_container.tar
```

## イメージの取得
```bash:WSL
docker pull ubuntu:20.04
```

## イメージのビルド
```docker:Dockerfile
FROM ubuntu:20.04

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
  build-essential \
  cmake \
  curl \
  git \
  libopencv-dev \
  nano
```

```bash:WSL
docker build ./ -t cpp_opencv_image
# docker run --rm -it cpp_opencv_image
```

## コンテナの生成と起動
### 同じコンテナを使いたい場合
```bash:WSL
# docker run -itd --name=MY_CONTAINER_NAME ubuntu:20.04
docker create -it --name=MY_CONTAINER_NAME ubuntu:20.04
docker start MY_CONTAINER_NAME
docker exec -it MY_CONTAINER_NAME bash
docker stop MY_CONTAINER_NAME
```

- メモ
    - createでの`-it` 指定はコンテナの標準入出力、ターミナルの設定。`docker attach MY_CONTAINER_NAME` 用。また、これがないとstartしてもすぐに終了してしまう。
    - execでの`-it` は新規に作成するbashプロセス用の設定

### 1回だけ動かす(コンテナは毎回削除)
```bash:WSL
docker run --rm -it  ubuntu:20.04
docker run --rm ubuntu:20.04 /bin/echo "Hello World"
```

### よく使うオプション
```bash:WSL
docker create  -v /mnt/c/iwatake/devel:/root/devel -v /etc/localtime:/etc/localtime:ro -e DISPLAY="192.168.1.2:0"  -w /root/ -p 8888:8888 -it --name=MY_CONTAINER_NAME ubuntu:20.04
docker run --rm -v /mnt/c/iwatake/devel:/root/devel -v /etc/localtime:/etc/localtime:ro -e DISPLAY="192.168.1.2:0"  -w /root/ -p 8888:8888 -it  ubuntu:20.04
```

# Docker 例
## GPUを見れるようにだけしたい
```bash:WSL
docker run --name=ubuntu_gpu_0 -it --gpus=all ubuntu:20.04
# docker run --name=ubuntu_gpu_0 -it --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=0 ubuntu:20.04
```

## TensorFlow + Jupyter
```bash:WSL
docker run -it --rm -v /mnt/c/iwatake/devel:/home/jovyan/devel -e DISPLAY="192.168.1.2:0" -p 8888:8888 jupyter/tensorflow-notebook
```

## NGC: TensorFlow + Jupyter
```bash:WSL
docker run --gpus all -it --rm -v /mnt/c/iwatake/devel:/workspace/devel -e DISPLAY="192.168.1.2:0" -p 8888:8888 nvcr.io/nvidia/tensorflow:21.11-tf2-py3 jupyter-lab
```

http://localhost:8888/lab

```py:train_mnist.ipynb
# https://www.tensorflow.org/guide/gpu?hl=ja
import tensorflow as tf

print("Num GPUs Available: ", len(tf.config.experimental.list_physical_devices('GPU')))

mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# with tf.device('/CPU:0'):
with tf.device('/GPU:0'):
    model = tf.keras.models.Sequential([
      tf.keras.layers.Flatten(input_shape=(28, 28)),
      tf.keras.layers.Dense(128, activation='relu'),
      tf.keras.layers.Dropout(0.2),
      tf.keras.layers.Dense(10),
      tf.keras.layers.Softmax()
    ])
    loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
    model.compile(optimizer='adam',
                  loss=loss_fn,
                  metrics=['accuracy'])
    model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test,  y_test, verbose=2)
```

## NGC: その他
```bash:WSL
docker run --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark  
docker run --gpus all -it --rm -v /mnt/c/iwatake/devel/:/workspace/devel -e DISPLAY="192.168.1.2:0" nvcr.io/nvidia/tensorrt:21.11-py3
docker run --gpus all -it --rm -v /mnt/c/iwatake/devel/:/workspace/devel -e DISPLAY="192.168.1.2:0" nvcr.io/nvidia/cuda:11.4.2-devel-ubuntu20.04
```

- メモ
    - TensorRTはONNXモデル変換がうまくいかない (変換中に、` GPU error during getBestTactic: xxx : invalid argument` エラーが出てフリーズする)。けど、サンプルのmnixt.onnxは変換可能なので、モデル依存か?



# Docker その他
## Docker内のワークスペースをVSCodeで開く
- Windows上でDocker Desktopを入れていたら、Remote Explorer -> Containersで開けるはず
- WSL内にdocker-ceをインストールしている場合、WSL上で、`code .` を開いてから、Remote Explorer -> Containersで開けるはず
    - 開きたいコンテナを選んで、Attach Container。すると新Windowsが開くので、OpenFolder

## GUI出力について
- WSL2のGUIは何もしないでも出力される (Windows11の場合)
- WSL2 + DockerのGUIはX11フォワーディングが必要
    - DISPLAY環境変数を自分のIPアドレスに設定する
    - Xサーバソフトが必要。vcxsrvか、MobaXterm
- 描画速度はどれも大差なかった (1280x720のcv::Matをcv::imshow, waitkeyした場合)
    - VS2019(Windows11): 2.5 ms
    - WSL2: 1.5ms
    - WSL2 + Docker: 1.6m
    - ※Windows10の時はもっと遅かった気がする

# WSL上で、TensorRT環境を整える
- CUDAはNGC環境で動いた
- だけど、TensorRTはNGC環境だと動かないので、しょうがないのでWSL上で直接構築する
- WSL上でもうまく動かなかった。。。

## WSL上で、CUDAのインストール
- https://docs.nvidia.com/cuda/wsl-user-guide/index.html#ch03a-setting-up-cuda

```bash:WSL
wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin
sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.0/local_installers/cuda-repo-wsl-ubuntu-11-4-local_11.4.0-1_amd64.deb
sudo dpkg -i cuda-repo-wsl-ubuntu-11-4-local_11.4.0-1_amd64.deb
sudo apt-key add /var/cuda-repo-wsl-ubuntu-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda

sudo ldconfig
```

- 下記エラー発生時
    - `/sbin/ldconfig.real: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link`
    - 参考: https://github.com/microsoft/WSL/issues/5548#issuecomment-912495487
    - パワーシェルを管理者として開き、

```bash:パワーシェルとWSL
wsl -e /bin/bash
cd /mnt/c/Windows/System32/lxss/lib
ln -s libcuda.so.1.1 libcuda.so.1
ln -s libcuda.so.1.1 libcuda.so
```

- 下記を毎回実行するか、`~/.bashrc` に追加する

```bash:WSL
export PATH="/usr/local/cuda/bin:$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
```


## WSL上で、cuDNNのインストール
- https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html#install-linux
    - https://developer.nvidia.com/rdp/cudnn-download
        - ↑のバージョンの場合、 https://developer.nvidia.com/compute/machine-learning/cudnn/secure/8.2.4/11.4_20210831/cudnn-11.4-linux-x64-v8.2.4.15.tgz
    
```bash:WSL
tar -xvf cudnn-11.4-linux-x64-v8.2.4.15.tgz
sudo cp cuda/include/cudnn*.h /usr/local/cuda/include 
sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda/lib64 
sudo chmod a+r /usr/local/cuda/include/cudnn*.h /usr/local/cuda/lib64/libcudnn*
```




## WSL2上で、TensorRTのインストール
- https://docs.nvidia.com/deeplearning/tensorrt/install-guide/index.html
    - https://developer.nvidia.com/nvidia-tensorrt-download
    - TensorRT-8.2.1.8.Linux.x86_64-gnu.cuda-11.4.cudnn8.2.tar.gz

```bash:WSL
version="8.2.1.8"
arch=$(uname -m)
cuda="cuda-11.4"
cudnn="cudnn8.2"
tar xzvf TensorRT-${version}.Linux.${arch}-gnu.${cuda}.${cudnn}.tar.gz
```

- 下記を毎回実行するか、`~/.bashrc` に追加する

```bash:WSL
version="8.2.1.8"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/TensorRT-${version}/lib
export TensorRT_ROOT=~/TensorRT-${version}
```

# WSL2上で、USB接続のWebCameraを使う (usbipd使用バージョン)
- 参照
    - https://github.com/rpasek/usbip-wsl2-instructions
    - https://devblogs.microsoft.com/commandline/connecting-usb-devices-to-wsl/
    - https://zenn.dev/pinto0309/articles/c3014043502245

## 一度だけやればよいこと
- usbipdをWindows上でインストール
    - https://github.com/dorssel/usbipd-win/releases/download/v1.2.0/usbipd-win_1.2.0.msi

- カーネルとカーネルモジュールのビルド

```bash:WSL
sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev autoconf libudev-dev libtool
uname -r
# 5.10.16.3-microsoft-standard-WSL2

sudo git clone https://github.com/microsoft/WSL2-Linux-Kernel.git /usr/src/5.10.16.3-microsoft-standard
cd /usr/src/5.10.16.3-microsoft-standard

sudo git checkout linux-msft-wsl-5.10.16.3

sudo cp /proc/config.gz config.gz
sudo gunzip config.gz
sudo mv config .config

sudo make menuconfig

### ↓↓↓ 変更内容
Device Drivers->USB support[*]
Device Drivers->USB support->Support for Host-side USB[M]
Device Drivers->USB support->Enable USB persist by default[*]
Device Drivers->USB support->USB Modem (CDC ACM) support[M]
Device Drivers->USB support->USB Mass Storage support[M]
Device Drivers->USB support->USB/IP support[M]
Device Drivers->USB support->VHCI hcd[M]
Device Drivers->USB support->VHCI hcd->Number of ports per USB/IP virtual host controller(8)
Device Drivers->USB support->Number of USB/IP virtual host controllers(1)
Device Drivers->USB support->USB Serial Converter support[M]
Device Drivers->USB support->USB Serial Converter support->USB FTDI Single Port Serial Driver[M]
Device Drivers->USB support->USB Physical Layer drivers->NOP USB Transceiver Driver[M]
Device Drivers->Network device support->USB Network Adapters[M]
Device Drivers->Network device support->USB Network Adapters->[Deselect everything you don't care about]
Device Drivers->Network device support->USB Network Adapters->Multi-purpose USB Networking Framework[M]
Device Drivers->Network device support->USB Network Adapters->CDC Ethernet support (smart devices such as cable modems)[M]
Device Drivers->Network device support->USB Network Adapters->Multi-purpose USB Networking Framework->Host for RNDIS and ActiveSync devices[M]

Device Drivers > Multimedia support[*]
Device Drivers > Multimedia support > Media drivers > Media USB Adapters[*]
Device Drivers > Multimedia support > Media drivers > Media USB Adapters >  USB Video Class (UVC) [M]
Device Drivers > Multimedia support > Media drivers > Media USB Adapters >  USB Video Class (UVC) > UVC input events device support [*]
### ↑↑↑ 変更内容

sudo make -j 12 && sudo make modules_install -j 12 && sudo make install -j 12
sudo cp /usr/src/5.10.16.3-microsoft-standard/vmlinux /mnt/c/Users/iwatake/.
```

- ビルドしたカーネルを使用するように設定する

```txt:C/Users/iwatake/.wslconfig
[WSL2]
kernel=C:\\Users\\iwatake\\vmlinux
```

- 一度`wsl --shutdown` する


- WSL上でUSB/IP用のユーザースペースツールのインストール (この手順は不要かも)

```bash:WSL
sudo apt install linux-tools-5.4.0-77-generic hwdata
sudo visudo

### ↓↓↓ 変更内容
# Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
Defaults        secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
### ↑↑↑ 変更内容
```

- 一度`wsl --shutdown` する

## 毎回必要なこと
- 必要なモジュールをロードする。/dev/video* を使えるようにする

```
sudo modprobe usbcore
sudo modprobe usb-common
sudo modprobe hid-generic
sudo modprobe hid
sudo modprobe usbnet
sudo modprobe cdc_ether
sudo modprobe rndis_host
sudo modprobe usbserial
sudo modprobe usb-storage
sudo modprobe cdc-acm
sudo modprobe ftdi_sio
sudo modprobe usbip-core
sudo modprobe vhci-hcd

sudo modprobe uvcvideo
# sudo modprobe videobuf2-v4l2
# sudo modprobe videobuf2-memops
# sudo modprobe videobuf2-common
# sudo modprobe videobuf2-vmalloc

sudo chmod 777 /dev/video*
```


```bash:PowerShell(管理者)
usbipd wsl list
#PS C:\Users\iwatake> usbipd wsl list
# BUSID  DEVICE                                                        STATE
# 1-8    USB 入力デバイス, WinUsb デバイス                                       Not attached
# 1-12   USB 入力デバイス                                                    Not attached
# 1-13   USB オーディオ デバイス, USB ビデオ デバイス                                  Not attached
# 2-2    USB 入力デバイス                                                    Not attached

usbipd wsl attach --busid 1-13
usbipd wsl detach --busid 1-13

```


## WSL2 + Docker上で、USB接続のカメラを使う
- WSL2上でUSBカメラが使用できる状態で、`--device=/dev/video0:/dev/video0` オプションをつけてコンテナを作る
    - `docker create -v /mnt/c/iwatake/devel:/root/devel -e DISPLAY="192.168.1.2:0"  --device=/dev/video0:/dev/video0 -it --name=cpp_opencv cpp_opencv_image`