サーバーで DeepLearning (+GUI) なDocker環境構築【祝 900DL！】

# はじめに

- **最新の仕様は [こちら](https://github.com/naruya/dl_remote) から確認できます！**:bow:
  - **(2024/12/29時点も) 頻繁に⇧は更新してますが、本記事の更新は稀です･･･**
- 動かない・使い方わからない等あれば ~~お気軽にご連絡ください~~ 連絡くださってもよいですが返信できるかわかりません :bow:
- VNC等を使わない場合は、本 docker よりも NVIDIA が出してる docker image の方がより最適化されていそうでおすすめです。
    - [nvcr.io/nvidia/pytorch:23.09-py3](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/pytorch
) など！
    - tensorflow版、jax版などもあるっぽい?
    


# DL研究のこんな問題を解決します！

- **ディープラーニングの環境構築がつらい**
    - GPUが認識されない... (`nvidia-smi` , `nvcc -V` が効かないとかも...)
    - ライブラリ公式のdockerが使い勝手悪い！でもカスタマイズはつらい...
- **サーバー上でのレンダリングで沼ってる**
    - 親の顔より見た `opengl` / `glfw` のエラー
    - GUIが見れないから、毎回gifで保存して`scp`...

**あてはまる方はぜひ & 普段使い用としてもおすすめです！**

# 本 Docker Image の概要

1. 最新の **PyTorch**
1. ちょっとだけリッチなCUI & GUI (VNC)
1. ローカルでもサーバーでもヘッドレス(ディスプレイなし)でもok
1. **安心安全！**
  ・ 高頻度でメンテしてます (自分でも使ってるので)
  ・ たくさんダウンロードしてもらってます！(?) Thanks！
1. Auto Build された image を、`docker pull` でお使いいただけます！

<iframe width="640" height="400" src="https://www.youtube.com/embed/lgePnugIP1U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

- よくわかる動画 (⇧大学のヘッドレスサーバーにsshして実行しています)

# 更新

- **2022-11-23**
  - **600ダウンロード**！ (たぶんauto build分も含められてるけど)
- **2022-08-19**
  - cuda:11.0 -> cuda:11.3、pytorch を最新版に
  - **tensorflowは非対応にしました...。**
    - (pytorchの方が圧倒的に使われてると思うので)
  - **pyenvは非対応にしました...。**
    - (python2とはさよならして、[venv](https://docs.python.org/ja/3/library/venv.html) で事足りるようになったので)
- **2021-08-04**
  - pytorch と tensorflowを最新版に、tensorboardのバグを修正
- **2021-04-28**
  - **祝！300ダウンロード！**
- **2020-12-22**
  - cuda:9.2 -> cuda:11.0、pytorch と tensorflowを最新版に
- **2020-12-22**
  - **祝！200ダウンロード！**
- **2019-12-31**
  - **祝！100ダウンロード！**

# 動作確認済みのGUIを使うライブラリ

- ページ下の方の[ギャラリー](#動作確認済み一覧)も参考にしてみてください。
    - OpenAI Gym
    - PyBullet
    - MuJoCo131, Mujoco150, MuJoCo200
    - CleanRL (⇦ New! 2022/11/27)
    - など

# 仕様
- Deep周り
  - CUDA, PyTorch, Tensorboard
- GUI周り
  - Xvfb (仮想ディスプレイ), x11vnc (GUI転送)
- Python周り
  - venv, Jupyter Lab
- Shell周り
  - zsh

## Dockerfile (2022/11/23時点)

- Dockerfile自体は [Github](https://github.com/naruya/dl_remote) にアップロードしてます
- (Auto BuildされたDocker Imageは [Dockerhub](https://hub.docker.com/r/naruya/dl_remote) にあります)

```dockerfile
FROM nvidia/cuda:11.3.1-devel-ubuntu20.04
ENV DEBIAN_FRONTEND=noninteractive

# zsh
RUN apt-get update && apt-get install -y wget git zsh
SHELL ["/bin/zsh", "-c"]
RUN wget http://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh
RUN sed -i "s/# zstyle ':omz:update' mode disabled/zstyle ':omz:update' mode disabled/g" ~/.zshrc

# python
RUN apt-get update && apt-get install -y python3.9 python3.9-dev python3.9-venv python3-pip
RUN ln -s /usr/bin/python3.9 /usr/bin/python

# vnc
RUN apt-get update && apt-get install -y xvfb x11vnc icewm
RUN echo 'alias vnc="export DISPLAY=:0; Xvfb :0 -screen 0 1400x900x24 &; x11vnc -display :0 -forever -noxdamage > /dev/null 2>&1 &; icewm-session &"' >> /root/.zshrc

# torch
RUN python -m venv /root/venv/torch
RUN source ~/venv/torch/bin/activate && \
    pip install -U pip && \
    pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113

# utils
RUN apt-get update && apt-get install -y vim ffmpeg
RUN source ~/venv/torch/bin/activate && \
    pip install jupyterlab tensorboard ipywidgets && \
    echo 'alias jl="jupyter lab --ip 0.0.0.0 --port 8888 --NotebookApp.token='' --allow-root &"' >> /root/.zshrc && \
    echo 'alias tb="tensorboard --host 0.0.0.0 --port 6006 --logdir runs &"' >> /root/.zshrc

RUN apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /root
CMD ["zsh"]
```

- いつぞやと比べてかなりシンプルなところに落ち着きました
  - ~~主にpython2とtensorflowとさようならしたおかげで~~

# 使い方
- ローカルマシンでも使えますが(その場合は②から)、ここではサーバーで使う例を書きます

① ローカルPCからサーバーにssh等で入る

```shell
$ ssh foo@bar -L 5900:localhost:5900 -L 6006:localhost:6006 -L 8888:localhost:8888
```

② サーバー上でdocker imageを起動する

```shell
$ docker pull naruya/dl_remote:latest
$ docker run --gpus all -it \
    -p 5900:5900 -p 6006:6006 -p 8888:8888 --name dl_remote naruya/dl_remote
```

③ python環境に入る/切り替え

```shell
% source ~/venv/torch/bin/activate  # 入る
# 以下も参考に
# % deactivate  # 出る
# % python -m venv ~/venv/foo # 新しい(独立した)python環境を作る
# % source ~/venv/foo/bin/activate  # 入る
```

④ コンテナの中でVNCを起動する

```shell
% export DISPLAY=:0
% vnc  # GUIを起動して転送します (以下が実行されます)
# % export DISPLAY=:0
# % Xvfb :0 -screen 0 1400x900x24 &
# % x11vnc -display :0 -forever -noxdamage > /dev/null 2>&1 &
# % icewm-session &
```

⑤ ローカルPCでVNCビューアを開く

- ~~[Chromeの拡張機能のVNCビューア](https://chrome.google.com/webstore/detail/vnc%C2%AE-viewer-for-google-ch/iabmpiboiopbgfabjmgeedhcmjenhbla?utm_source=chrome-app-launcher-info-dialog)がおすすめです。(以下はこれを使う前提)~~
  - ⇧は使えなくなってました。Winの人は [⇧の後継?のRealVNC](https://www.realvnc.com/en/connect/download/viewer/) がおすすめです。
    - (RealVNCはユーザー登録を促されますがskipできます。無料です)
- アドレスには `localhost:5900` と入力して `Connect` 。（下図）
- ③で設定したVNCのパスワード（pass）を入力して入ります。

⑥ GUIを使うソフトウェアを起動すると、転送されたディスプレイ上で見ることができます！(参考:⑦)

⑦ [Jupyter Lab](https://jupyter.org/) (デフォルトで対応してます！) などでコーディングする⇩

例) [OpenAI Gym](https://www.gymlibrary.dev/) (pythonで動く物理シミュレータ) をサーバーで動かす

```shell
% pip install gym
% jl  # 以下のコマンドが実行されます
# % DISPLAY=:0  jupyter lab --ip 0.0.0.0 --port 8888 --allow-root &
```

- でjupyter labを開き、以下のようなコードでcartpoleのシミュレータを起動します。
- サンプルコード⇩

```python
# https://gym.openai.com/ から拝借
import gym
env = gym.make("CartPole-v1")
observation = env.reset()
for _ in range(1000):
  env.render()
  action = env.action_space.sample() # your agent here (this takes random actions)
  observation, reward, done, info = env.step(action)

  if done:
    observation = env.reset()
env.close()
```

</div></details>

- 無事にサーバーで動いているGUIが見れました。

![Screenshot from 2019-10-15 21-02-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188173/286d513b-fd8a-d56a-ff2a-2fd139fdcd67.png)
![Screenshot from 2019-10-15 21-02-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188173/2f31d786-99e5-862c-2e4b-87642586a501.png)

- tensorboardにも対応しています

```shell
% tb  # 以下のコマンドが実行されます
# % tensorboard --host 0.0.0.0 --port 6006 --logdir runs &
```

- 以上！

# 使い方２ (⇦ New! )

- こちらは自分で `docker build` する人用です

```shell
$ vim Dockerfile  # 書く
$ docker build --no-cache . -t my_dl_remote
$ docker run --gpus all -it \
    -p 5900:5900 -p 6006:6006 --p 8888:8888 --name dl_remote my_dl_remote
```


# 動作確認済み一覧

## MuJoCo

- [mujoco-py](https://github.com/openai/mujoco-py/blob/master/Dockerfile)を参考にパッケージ追加したら動きます (コマンド2つと `~/.mujoco` を用意するくらいです、コメント欄を参考にしてみてください)

<details><summary>サンプルコードはこちら</summary><div>

```python
import gym
env = gym.make("Humanoid-v2")
o = env.reset()
for _ in range(1000):
    env.render()
    a = env.action_space.sample()
    o, r, done, info = env.step(a)
    if done:
        o = env.reset()
env.close()
```
</div></details>


![Screenshot from 2019-10-17 16-18-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188173/fd5cd1b5-ccb9-7ed2-4f9b-08d832e4820b.png)

## Meta-World

- https://meta-world.github.io/

- こんな感じ
![Screenshot 2019-12-30 at 23.13.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188173/94a28b0c-f3a2-0122-fb0d-878beb40e30f.png)

- mujocoのバージョンが合ってるかだけ注意すればよかったと思います
    - (若干`setup.py`をいじる必要がありました)

## DeepMind Control Suite (dm_control)

- https://deepmind.com/research/publications/dm-control-Software-and-Tasks-for-Continuous-Control
- ソフトウェアレンダリング(OSmesa)でやりました。
- おまけで、dm_controlをちょっとイジって(↓)ソースコードからinstallしてます。

<details><summary>サンプルコードはこちら</summary><div>

```shell
diff --git a/dm_control/suite/cartpole.xml b/dm_control/suite/cartpole.xml
index e01869d..800499e 100644
--- a/dm_control/suite/cartpole.xml
+++ b/dm_control/suite/cartpole.xml
@@ -16,7 +16,11 @@
 
   <worldbody>
     <light name="light" pos="0 0 6"/>
-    <camera name="fixed" pos="0 -4 1" zaxis="0 -1 0"/>
+    <camera name="fixed-2" pos="-3.5   -2 1" euler="90 -60 0"/>
+    <camera name="fixed-1" pos="  -2 -3.5 1" euler="90 -30 0"/>
+    <camera name="fixed00" pos="   0   -4 1" euler="90   0 0"/>
+    <camera name="fixed+1" pos="   2 -3.5 1" euler="90  30 0"/>
+    <camera name="fixed+2" pos=" 3.5   -2 1" euler="90  60 0"/>
     <camera name="lookatcart" mode="targetbody" target="cart" pos="0 -2 2"/>
     <geom name="floor" pos="0 0 -.05" size="4 4 .2" type="plane" material="grid"/>
     <geom name="rail1" type="capsule" pos="0  .07 1" zaxis="1 0 0" size="0.02 2" material="decoration" />
(END)
```

```shell
sudo apt-get install libgl1-mesa-glx libosmesa6
export MUJOCO_GL=osmesa
echo $MUJOCO_GL # => osmesa
```

```python
from dm_control import suite
import numpy as np
import cv2

max_frame = 100
width = 128
height = 128
video = np.zeros((max_frame, height, width*5, 3), dtype=np.uint8)

env = suite.load(domain_name="cartpole", task_name="swingup")
action_spec = env.action_spec()
time_step = env.reset()

# 先にシミュレーションとレンダリングをしておきます(少し時間がかかります)
for i in range(max_frame):
    action = np.random.uniform(action_spec.minimum,
                               action_spec.maximum,
                               size=action_spec.shape)
    time_step = env.step(action)
    video[i] = np.hstack([env.physics.render(height, width, camera_id=0), 
                          env.physics.render(height, width, camera_id=1), 
                          env.physics.render(height, width, camera_id=2),
                          env.physics.render(height, width, camera_id=3),
                          env.physics.render(height, width, camera_id=4)])

cv2.namedWindow("frame", cv2.WINDOW_NORMAL)

for _ in range(999):
    for i in range(max_frame):
        cv2.imshow("frame", video[i])
        cv2.waitKey(5)

cv2.destroyAllWindows()
```

</div></details>

![Screenshot from 2019-11-02 20-40-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188173/07c74bcf-5959-bbd2-5e83-35b4b438c8e4.png)

# ToDo (?)
- GPU レンダリング化 (恐らくこれは闇)
  - [nvidia-egl-desktop](https://github.com/ehfd/docker-nvidia-egl-desktop)を試す？？
  - このあたりが良さそう
    - https://tech.aptpod.co.jp/entry/2023/12/18/100000
    - https://github.com/Tiryoh/docker-ros-desktop-vnc/issues/12
    - https://github.com/ryought/glx-docker-headless-gpu
- singularity image化
  - [ABCIでmujoco210を動かす
](https://qiita.com/namahoge/items/d527413fa3804e077b25)

<!--
- 仮想ディスプレイをGPU accelerate可能にする
    - `nvidia-xconfig` で `/etc/X11/xorg.conf` 作って、 `Xvfb` の代わりに `Xorg` でGPU使った仮想ディスプレイを建てる必要がありそうです、ただし `nvidia-xconfig` は `nvidia/cuda` 系のdockerリポジトリからcloneして作ったコンテナには入ってないので、たぶんかなりだるいです。
    - ちなみに `x11vnc` を使わずに環境変数 `DISPLAY` をローカルPCとコンテナの中で共有するような方法もありますが、刺さってるGPUとかが違うと動かないです、たぶん。
-->

# おわりに
- 最後まで見てくださってありがとうございます:bow:
- コメント、プルリク等 welcomeです！

（[twitter](https://twitter.com/stapriegg)）