CUDA 10.2のイメージの作成とコンテナのセットアップ手順

## はじめに
ある論文のモデルを構築しようとした時、neural-renderer-pytorchがどうしてもインストールできませんでした。

その論文のpytorchが1.1.0を使用しているので、cuda10.xが必要みたいです。今使用しているcudaが12.1なので、ダウングレードしなければいけませんが他への影響も考えてDocker内で実装することにしました。
しかし、cuda10.2のimageはすでに削除されているみたいなので以下の記事を参考にimageから作成することにしました。

https://qiita.com/dandelion1124/items/31a3452b05510097daa0


この手順書では、まずCUDA 10.2のDockerイメージをローカルでビルドします。その後、Docker Composeを使ってCUDA 10.2イメージでコンテナをセットアップし、vscodeでアクセスするところまでまとめています。（個人的なメモです）

## 動作確認環境

- OS: Ubuntu 20.04(WSL)
## 1. NVIDIA Container Toolkitのインストール

1. NVIDIA Container Toolkitをインストールします。

   ```bash
   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
   ```

2. リポジトリを更新し、`nvidia-docker2`をインストールします。

   ```bash
   sudo apt update
   sudo apt install -y nvidia-docker2
   ```

3. Dockerデーモンを再起動します。

   ```bash
   sudo systemctl restart docker
   ```

4. `/etc/docker/daemon.json`に以下の設定が含まれていることを確認します。

   ```json
   {
     "default-runtime": "nvidia",
     "runtimes": {
       "nvidia": {
         "path": "nvidia-container-runtime",
         "runtimeArgs": []
       }
     }
   }
   ```

5. 設定後、Dockerデーモンを再起動します。

   ```bash
   sudo systemctl restart docker
   ```

## 2. CUDA 10.2 Dockerイメージのビルド

### ステップ 1: CUDA Dockerイメージのリポジトリをクローン

NVIDIAのCUDA Dockerイメージ作成用リポジトリをクローンします。

```bash
git clone https://gitlab.com/nvidia/container-images/cuda.git
cd cuda
```

### ステップ 2: `build.sh`スクリプトの修正

`build.sh`スクリプトに以下の変更を加え、`docker buildx build`を`docker build`に置き換えます。

```diff
-        run_cmd docker buildx build --pull ${LOAD_ARG} ${PUSH_ARG} ${PLATFORM_ARG} \
+        run_cmd docker build ${LOAD_ARG} ${PUSH_ARG} ${PLATFORM_ARG} \
             -t "${IMAGE_NAME}:${CUDA_VERSION}-base-${OS}${OS_VERSION}${IMAGE_SUFFIX:+-${IMAGE_SUFFIX}}" \
             "${BASE_PATH}/${OS_PATH_NAME}/base"
 
-        run_cmd docker buildx build --pull ${LOAD_ARG} ${PUSH_ARG} ${PLATFORM_ARG} \
+        run_cmd docker build ${LOAD_ARG} ${PUSH_ARG} ${PLATFORM_ARG} \
             -t "${IMAGE_NAME}:${CUDA_VERSION}-runtime-${OS}${OS_VERSION}${IMAGE_SUFFIX:+-${IMAGE_SUFFIX}}" \
             --build-arg "IMAGE_NAME=${IMAGE_NAME}" \
             "${BASE_PATH}/${OS_PATH_NAME}/runtime"
 
-        run_cmd docker buildx build --pull ${LOAD_ARG} ${PUSH_ARG} ${PLATFORM_ARG} \
+        run_cmd docker build ${LOAD_ARG} ${PUSH_ARG} ${PLATFORM_ARG} \
             -t "${IMAGE_NAME}:${CUDA_VERSION}-devel-${OS}${OS_VERSION}${IMAGE_SUFFIX:+-${IMAGE_SUFFIX}}" \
             --build-arg "IMAGE_NAME=${IMAGE_NAME}" \
```

### ステップ 3: EOL CUDAファイルの準備

EOLとなったCUDAバージョンのファイルを`dist`ディレクトリにコピーします。

```bash
cp -r dist/end-of-life/10.2 dist/
```

### ステップ 4: CUDA 10.2 + cuDNNありのDockerイメージをビルド

以下のコマンドを実行し、ビルドします。

```bash
cd dist/10.2/ubuntu1804/devel/cudnn8
docker build -t nvidia/cuda:10.2-cudnn8-devel-ubuntu18.04 --build-arg IMAGE_NAME=nvidia/cuda --build-arg TARGETARCH=amd64 .
```

ビルドが成功すると、以下のイメージがローカルに生成されます。

- `nvidia/cuda:10.2-cudnn8-devel-ubuntu18.04`
- `nvidia/cuda:10.2-cudnn8-runtime-ubuntu18.04`
- `nvidia/cuda:10.2-cudnn8-base-ubuntu18.04`

## 3. Docker Composeファイルの作成

1.次にローカルでworkspaceフォルダを作成後、 プロジェクトのルートディレクトリに`docker-compose.yml`を作成し、以下の内容を追加します。ここで`nvidia/cuda:10.2-devel-ubuntu18.04`イメージを使用します。また、ローカルの`workspace`フォルダをコンテナ内の`/workspace`にマウントします。

   ```yaml
   version: '3.8'

   services:
     3dpose:
       image: nvidia/cuda:10.2-devel-ubuntu18.04  
       container_name: cuda10.2-container
       runtime: nvidia
       deploy:
         resources:
           reservations:
             devices:
               - driver: nvidia
                 count: all
                 capabilities: [gpu]
       volumes:
         - ./workspace:/workspace  # ローカルのworkspaceをマウント
         - ./setup.sh:/setup.sh  # setup.shをマウント
       stdin_open: true
       tty: true
       command: ["bash", "/setup.sh", "bash"]
   ```

以下の部分の設定を追加することで、dockerがデバイスのGPUを使用することができます。
```yaml
reservations:
 devices:
   - driver: nvidia
     count: all
     capabilities: [gpu]
```

## 4. `setup.sh`の作成

1. プロジェクトルートに`setup.sh`を作成し、以下の内容を追加します。

   ```bash
   #!/bin/bash

   # パッケージリストを更新し、必要なライブラリをインストール
   apt-get update && apt-get install -y python3 python3-pip git

   # Pythonパッケージ管理ツールのアップグレード
   pip3 install --upgrade pip

   # まずtorchをインストール
   pip3 install torch==1.1.0 torchvision

   # GPUの確認
   echo "Checking for GPU availability..."
   python3 -c "import torch; print('CUDA available:', torch.cuda.is_available()); print('Device count:', torch.cuda.device_count()); print('Device name:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'No CUDA device')"

   # コンテナのbashシェルを起動して終了しないようにする
   exec "$@"
   ```

2. `setup.sh`はコンテナ起動時に実行され、必要なPythonライブラリと依存関係のインストール、GPUの確認を行います。

## 5. コンテナのビルドと起動

1. Docker Composeを使ってコンテナをビルド・起動します。

   ```bash
   docker-compose up --build
   ```

2. コンテナが正常に起動し、`setup.sh`が実行されていることを確認します。エラーが発生しない場合、セットアップは成功です。
また、最後に使用しているgpuが正しく認識されていれば、そのgpu名が出力されるはずです。

## 6. VS Codeでコンテナにアタッチ

1. **Dev Containers拡張機能**がインストールされている状態で、VS Codeを開きます。
2. コマンドパレット（`Ctrl + Shift + P`または`F1`）を開き、「**Dev Containers: Attach to Running Container...**」を選択します。
3. 起動中のコンテナ一覧から`cuda10.2-container`を選択します。
4. VS Codeがコンテナにアタッチされ、コンテナ内のファイルを編集したり、ターミナルでコマンドを実行できるようになります。

---

以上で、CUDA 10.2のイメージと、Docker Composeを使ってコンテナをセットアップする手順が完了します。

## 最後に
思ったよりも簡単だったので、これからもcuda環境構築はdockerでやろうかなと思いました。