PyTorch+CUDA+Poetry+Pyenvのコンテナ環境をVSCodeから作成する方法

# はじめに
```docker```を使った```cuda```+```Pytorch```+```Poetry```+```Pyenv```の環境構築を行いました。
また今回は```VSCode```上でコーディングしたかったのと、構築した環境の配布も考えていたので```VSCode```の拡張機能である```Devcontainer```を使いました。

```cuda```+```Pytorch```だけであればnvidia公式から```nvidia/cuda```のimageを持って来るだけなので簡単なのですが、そこに```poetry```, ```devcontainer```の要素が入ってきたため結構複雑なことになりました。
本記事の手法が**最適解ではないこと**は明白ですが、備忘録兼これより良い方法が知りたいのでまとめておきます。

## 筆者環境
- WSL2 on windows
- cuda 11.6
- docker 
- vscode

## 設定ファイルと解説
```dockerfile:dockerfile
# cudaと利用するpytorchに応じて適切なものを選択
FROM nvidia/cuda:11.6.0-cudnn8-devel-ubuntu20.04
# 環境変数を設定
ENV TZ=Asia/Tokyo
ENV DEBIAN_FRONTEND=noninteractive
# 必要そうなライブラリをインストール
RUN apt-get update && \
    apt-get install -y software-properties-common tzdata
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update -y \
    && apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
    libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev \
    liblzma-dev python-openssl git vim less
# pyenvをインストール
##環境変数の設定
ENV HOME /root
ENV PYENV_ROOT $HOME/.pyenv
ENV PATH $PYENV_ROOT/bin:$PATH
## pyenvでインストールするPythonのバージョンを指定
ARG PYTHON_VERSION="3.9.16"　

RUN git clone https://github.com/pyenv/pyenv.git ~/.pyenv
RUN echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc && \
    echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc && \
    echo 'eval "$(pyenv init --path)"' >> ~/.bashrc
RUN eval "$(pyenv init --path)"
## 指定したPythonをインストールしグローバルで認識するように設定
RUN pyenv install $PYTHON_VERSION && \
    pyenv global $PYTHON_VERSION
```
環境構築の要となる```dockerfile```です。
cudaを使えるようにするため```nvidia:cuda```のイメージを利用します。
ここはそれぞれの環境に応じて適切なものを選んでください。
imageのバージョンがいろいろありますが今回はdevelにしました。
開発用であればdevelでよいと思います。

Pythonのバージョンがデフォルトだと```3.8.10```だったので、それを変更するために```Pyenv```を導入しています。
```Pyenv```を使うことで簡単にバージョンの切り替えができるようになります。
今回は```3.9.16```を使うことにしました。

本来であれば```dockerfile```に```poetry```のインストールと```poetry```によるライブラリのインストールを書けばよいのですが、なぜかうまくいかなかったので別の方法をとることにしました。
```devcontainer.json```には```postCreateCommand```というコマンドがあります。これはコンテナ作成後に一回だけ指定したコマンドを実行するというものです。
他にも```postStartCommand```や```postAttachCommand```があります。これらのコマンドの説明は[こちら](https://vlike-vlife.netlify.app/posts/vscode_remote_container_command)がわかりやすかったです。
これを用いてあとから```poetry```のインストールを行いました。
用いた```postcmd.sh```と```devcontainer.json```の中身を下に示しています。

```sh:postcmd.sh
#!/bin/bash
#poetryのインストール
pip install --upgrade pip
pip install poetry
#poetryによるライブラリのインストール
poetry config virtualenvs.create false
poetry install --no-root
```


```json:devcontainer.json
{
    "name": "Python39docker2",
    "dockerFile": "../dockerfile",
    "postCreateCommand": "./postcmd.sh",
    "extensions": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "ms-python.black-formatter",
        "njpwerner.autodocstring",
        "mosapride.zenkaku",
        "oderwat.indent-rainbow"
    ],
    "runArgs": [
        "--gpus",
        "all",
        "--shm-size",
        "16gb"
    ],
}
```
すこしややこしい点として、dockerfileの指定は```devcontainer.json```があるディレクトリ(つまり```.devcontainer```の中)からの相対パスで指定しますが、```postCreateCommand```はコンテナ作成後に行われるので```.devcontainer```があるディレクトリからの相対パスでしています。微妙にハマったので注意してください。

また、コンテナ内でgpuを認識させるために```"runArgs": ["--gpus",　"all",　"--shm-size", "16gb"]```が必ず必要です。```shm-size```は適当な値に変えてください。extensionはお好みでどうぞ。

```poetry```でライブラリをインストールする際に必要な```pyproject.toml```を下に示しています。
```toml:pyproject.toml
[tool.poetry.dependencies]
python = "^3.9"
isort = "^5.10.1"
black = "^22.10.0"
pyproject-flake8 = "^5.0.4.post1"
pytest = "^7.2.0"
jupyterlab = "^3.5.0"
mypy = "^0.991"
torch={path="torch-1.13.1+cu116-cp39-cp39-linux_x86_64.whl"}
```
formatterやリンターの設定は割愛します。[こちら](https://zenn.dev/zenizeni/books/a64578f98450c2/viewer/c6af80)の方が書かれている設定が使いやすいです。
poetryを使ったpytorchのインストール方法はまだ確立されておらず、whlファイルをとってきてpyproject.tomlに直接書くというのがベストかと思います。(2023/2/4現在)

これらの設定ファイルを使って構築したコンテナの中できちんとGPUが認識されているか確認しました。
```terminal
# python 
Python 3.9.16 (main, Feb  4 2023, 17:32:04) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> torch.__version__
'1.13.1+cu116'
>>> torch.cuda.is_available()
True
```
torch.cuda.is_available()がTrueで帰ってきているのできちんとGPUを認識できていることがわかりました。

# さいごに
```devcontainer```を経由してコンテナを作ったのでかなり厄介でした。普通に```dockerfile```からイメージをビルドしてVScodeからアクセスすればよかったのではないかとも思いました。
一方で```devcontainer```はdockerの使い方に不慣れな人でもコンテナ環境で開発ができるのでそこは大きな利点です。
```dockerfile```から作る場合は```postCreateCommand```はないので、```CMD ["/bin/bash","-c","postcmd.sh"]```を使用しました。
```poetry```のインストールの際に山ほどエラーが出てきて心が折れかけましたが何とか環境構築ができてよかったです。
これより良い方法をご存じの方がいらっしゃればご教示いただけばと思います。

## githubリンク
[こちら](https://github.com/nyakiri0726/cuda_pytorch_poetry)に今回構築した設定ファイル群を置いています。
cloneしてそのまま使えるはずです。