Docker Composeを用いたHPC学習環境の構築(C, Python, Fortran)

以前からGPUを入手したらHPC(High Performance Computing)の勉強をしたいと思っていました。ただ、環境構築は(色々な意味で)非常に面倒くさい作業です。何とか手軽に学習環境を構築出来ないかと考えていた時に、Docker、Docker Composeを活用した環境構築を思いつきました。本記事ではCUDAやOpenCLを初めとするC言語をベースとしたHPC用ライブラリやPythonでHPCを実現するPyCUDAやPyOpenCLが実行出来るコンテナ環境を構築します。

# 最終目標
Dockerを用いてコンテナ上にHPC関連ライブラリの開発環境を構築します。また、ポートの開放も設定し、SSHで乗り込める用にします。これはVSCodeのRemote Development Extensionの機能を活用して、SSHでコンテナに乗り込んで開発が出来る環境を実現する為に行います。

# 導入するライブラリ
導入するライブラリを一覧にまとめます。このうち、OpenCLとPyCUDA、PyOpenCL以外のライブラリはNVIDIA HPC SDKに含まれている物をインストールします。

|名称|言語|備考|
|:---|:---:|:---|
|CUDA|C|CUDAのデフォルトバージョンCベースの独自拡張|
|CUDA|Fortran[^fortran]|NVIDIA公式のコンパイラnvfortranがリリース|
|OpenCL|C|CUDAと二大巨頭と言われる並列計算ライブラリ|
|PyCUDA|Python|PythonからCUDAを実行するためのラッパー|
|PyOpenCL|Python|PythonからOpenCLを実行するためのラッパー|
|OpenMP|C|マルチコアCPUを手軽に活用出来るライブラリ|
|OpenACC|C|GPUを用いた並列計算用ライブラリ|

# Docker Composeeを用いた環境構築
環境構築に使用するDockerfileとdocker-compose.ymlを以下の様に作成します。Docker Composeは本来複数のコンテナを起動し、コンテナ間でネットワークを構築する環境構築に便利なアプリケーションですが、筆者の場合Makeの様な使い方をしていることが多い様に思います。Dockerfileは一部過去の公式コンテナイメージの中身を参考に作成しました。cuDNN、PyCUDA、OpenCL、PyOpenCLはNVIDIA HPC SDKには含まれていない為、CUDA10.1版の公式コンテナイメージをベースにインストールする方法を採用しています。また、コンテナにSSHでログイン出来る様に、ログインパスワードをビルド時に引数として渡して設定出来る様にしています。

2020/9/21追記: 今回の構築では、最終的クライアント端末からリモートSSHログインを実施して開発環境として使うことを目的に環境構築を行っています。故にクライアント端末からアクセスする運用を実施しない場合はDockerfile末尾のSSHの設定部分(# Setup SSHの部分)の記載は不要です。

```dockerfile:Dockerfile
FROM nvidia/cuda:10.1-cudnn7-devel

ARG PASSWD
ENV CUDA_VERSION=10.1

# Upgrade OS
RUN apt update && apt upgrade -y

# Install some ruquirements
RUN apt install -y bash-completion build-essential gfortran vim wget git openssh-server python3-pip

# Install NVIDA HPC SDK
RUN wget https://developer.download.nvidia.com/hpc-sdk/nvhpc-20-7_20.7_amd64.deb \
    https://developer.download.nvidia.com/hpc-sdk/nvhpc-2020_20.7_amd64.deb \
    https://developer.download.nvidia.com/hpc-sdk/nvhpc-20-7-cuda-multi_20.7_amd64.deb
RUN apt install -y ./nvhpc-20-7_20.7_amd64.deb ./nvhpc-2020_20.7_amd64.deb ./nvhpc-20-7-cuda-multi_20.7_amd64.deb

# Install cuDNN
ENV CUDNN_VERSION=7.6.5.32
RUN apt install -y --no-install-recommends libcudnn7=$CUDNN_VERSION-1+cuda$CUDA_VERSION && \
    apt-mark hold libcudnn7 && rm -rf /var/lib/apt/lists/*

# Install OpenCL
RUN apt update && apt install -y --no-install-recommends ocl-icd-opencl-dev && rm -rf /var/lib/apt/lists/*

RUN ln -fs /usr/bin/python3 /usr/bin/python
RUN ln -fs /usr/bin/pip3 /usr/bin/pip
ENV PATH=/usr/local/cuda-$CUDA_VERSION/bin:$PATH
ENV CPATH=/usr/local/cuda-$CUDA_VERSION/include:$CPATH
ENV LIBRARY_PATH=/usr/local/cuda-$CUDA_VERSION/lib64:$LIBRARY_PATH

# Install PyCUDA
RUN pip install pycuda

# Install PyOpenCL
RUN pip install pyopencl 

# Setup SSH
RUN mkdir /var/run/sshd
# Set "root" as root's password
RUN echo 'root:'${PASSWD} | chpasswd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's/#PasswordAuthetication/PasswordAuthetication/' /etc/ssh/sshd_config
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```

```yml:docker-compose.yml
version: "2.4"                                                                                                                                               
services:
    hpc_env:
        build:
            context: .
            dockerfile: Dockerfile
            args:
                - PASSWD=${PASSWD}
        runtime: nvidia
        ports:
            - '12345:22'
        environment:
            - NVIDIA_VISIBLE_DEVICES=all
            - NVIDIA_DRIVER_CAPABILITIES=all
        volumes:
            - ../work:/root/work
        restart: always
```

## イメージのビルド
Dockerfileからコンテナイメージを作成します。パスワードが`.bash_history`に残るのは好ましくないので、外部ファイル(auth.txt:ファイル内に設定したいパスワードを記載)から読み込んだ内容を引数渡しする様にします。コマンド操作を記載したシェルファイルを以下に示します。

```sh:build_env.sh
#!/bin/bash                                                                                                                                                  

cat auth.txt | xargs -n 1 sh -c 'docker-compose build --build-arg PASSWD=$0'
```

### ビルドの実行
シェルファイルを実行するだけです。途中の過程は省略していますが、Successfully...と最後に出力されれば成功です。途中cuDNNのセットアップでaptがエラー終了してしまうことが有りますが、何度かやり直すとビルド出来ます。(詳細な理由は不明)

```bash
$ ./build_env.sh
...
...
Successfully tagged building-hpc-env_hpc_env:latest
```

## コンテナの起動
後はコンテナを起動するだけです。PASSWDの値が設定されていないという警告が出ていますが、ビルド時に用いる一時的な値で有る為、無視しても問題有りません。

```bash
$ docker-compose up -d
WARNING: The PASSWD variable is not set. Defaulting to a blank string.
Creating network "building-hpc-env_default" with the default driver
Creating building-hpc-env_hpc_env_1 ... done
```

## コンテナへの接続確認
SSHで接続出来るか確認します。無事パスワードログイン出来ました。

2020/9/21追記: ここでは`localhost`で接続確認していますが、
`ssh -p 12345 root@<REMOTE_IP_ADDRESS>`
でも接続可能です。クライアント上のVSCodeからRemote Development Extensionを用いて接続可能です。

```bash
$ ssh -p 12345 root@localhost
root@localhost's password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 5.4.0-45-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@bd12d0a840e6:~# 
```

# おまけ1： VSCodeとの連携
詳細な構築方法は記載しませんが、VSCodeのRemote Development Extensionという拡張機能を用いると以下の様にVSCodeからHPCのお勉強が出来ます。(図はcufファイルを表示している所)

![hpcenv](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/55f94a6b-6102-e473-24bb-ef0ebdbceaff.png)

* [参考: Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)

# おまけ2: NVIDIA HPC SDK
NVIDIAが2020年8月にリリースしたHigh Performance Computing向けのライブラリやコンパイラまとめて開発環境として提供している物です。技術サポートは有償ですが、ライブラリやコンパイラを使用するだけなら無料で利用出来ます。従来はCUDA FortranのコンパイラはPGIコンパイラという他社製コンパイラが必要でしたが、NVIDIA製のコンパイラでCでもFortranでもCUDAが利用出来る様になります。含まれているコンパイラの一覧表を以下に示します。選り取り見取りといった所です。

![hpcsdk](https://developer.nvidia.com/sites/default/files/akamai/hpc-sdk-explainer-diagram-update-r1.png)

```bash:/opt/nvidia/hpc_sdk/Linux_x86_64/20.7/compilers/binの中は選り取り見取りな様子
# ls
addlocalrc                 jide-common.jar  ncu               nvaccelinfo  nvcudainit  nvprof        pgaccelinfo  pgf77      pgsize               tools
balloontip.jar             jide-dock.jar    nsight-sys        nvc          nvdecode    nvsize        pgc++        pgf90      pgunzip
cuda-gdb                   llvmversionrc    nsys              nvc++        nvextract   nvunzip       pgcc         pgf95      pgzip
cudarc                     localrc          nv-nsight-cu-cli  nvcc         nvfortran   nvzip         pgcpuid      pgfortran  rcfiles
ganymed-ssh2-build251.jar  makelocalrc      nvaccelerror      nvcpuid      nvprepro    pgaccelerror  pgcudainit   pgprepro   rsyntaxtextarea.jar
```

# まとめ
環境構築が煩雑で面倒くさいHPC向けライブラリの開発環境構築をDockerを用いることで簡単かつ手間無く出来る様にしました。HPCの専門書が数冊手元に溜まっているので勉強に活用していこうと思います。

# Reference
* [CUDA10.2 Dockerfile](https://gitlab.com/nvidia/container-images/cuda/blob/master/dist/ubuntu18.04/10.2/base/Dockerfile)
* [cuDNN Dockerfile](https://gitlab.com/nvidia/container-images/cuda/blob/master/dist/ubuntu18.04/10.2/runtime/cudnn7/Dockerfile)
* [OpenCL Dockerfile](https://gitlab.com/nvidia/container-images/opencl/-/blob/ubuntu18.04/devel/Dockerfile)
* [Installing PyCUDA on Ubuntu Linux](https://wiki.tiker.net/PyCuda/Installation/Linux/Ubuntu)
* [Dockerのコンテナを起動したままにする](https://qiita.com/reflet/items/dd65f9ffef2a2fcfcf30)
* [NVIDIA HPC SDK](https://developer.nvidia.com/hpc-sdk)
* [NVIDIA HPC SDK Version 20.7 Downloads](https://developer.nvidia.com/nvidia-hpc-sdk-download)
* [NVIDIA HPC SDK の HPC Compilers について](https://www.softek.co.jp/NVSDK/index.html)
* [SSHでログイン出来るコンテナの作り方(Ubuntu18対応版)](https://qiita.com/y-vectorfield/items/587d3f3a6eec8f251f3c)

[^fortran]: Fortranを業務で扱っている友人にも使って貰うのでCUDA Fortranに対応させておくことにしました。