検証用コンテナはDocker？いいえ、Singularityです。

#はじめに
ストックマークの森長です。
弊社では、AWS、GCP、AzureのGPUインスタンス、自前のGPUサーバを利用して、自然言語モデルのモデル化・検証を行っています。
以前は、Dockerを導入することで、他のメンバやプロジェクトと機械学習基盤を共有する場合でも、それぞれのコンテナで環境を分離していました。
しかし、メンバが増えてきたことと検証環境を多数作成したことで以下の懸念点が見えてきました。

- Dockerコンテナを起動するために、利用ユーザにsudo権限を付与しなければならない。
- sudo権限で別の利用者のDockerコンテナを(誤って)操作できてしまう。
- Dockerコンテナとホストでディレクトリを共有すると、コンテナ内で作成・編集したファイルのownerがrootになってしまう。

本番環境ではあまり上記の懸念点は問題ないかもしれませんが、弊社の検証環境では多くのメンバが利用し、学習済モデルや学習データの入れ替え・コピー、ソースの改変が頻発するため、検証目的としては煩雑かつセキュリティ的にも微妙でした。
また、nvidia-dockerが必須だったため、docker以外の選択肢は考えていませんでした。
(nvidia-dockerが必須な理由は[以前の記事](https://qiita.com/mkt3/items/5e698fce8fc8884bb524#1-6nvidia-docker%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)で書いた通りです。)

しかし、今年になって記事タイトルにあるとおり、検証用コンテナをDockerから[Singularity](https://www.sylabs.io/)に移行しました。
Singularityは弊社の検証環境にフィットしましたので、Singularityの概要と簡単な利用方法を紹介します。

# 1. Singularityとは
Singularityは、科学および高性能コンピューティング環境用に設計されたオープンソースベースのコンテナプラットフォームであり、以下のような特徴があります。

- 一般ユーザアカウントでコンテナを起動・停止可能
 - つまり、ユーザアカウントにsudo権限を付与しなくても良い！
- Dockerイメージを利用可能
 - つまり、docker及びnvidia-dockerのコンテナイメージが使える！！
- 実行ユーザアカウントの権限をそのままコンテナに引き継ぎ
 - つまり、コンテナ内でファイルの作成・編集をしてもownerは実行ユーザのまま！！！
- GPUの仮想化に対応
 - つまり、コンテナ起動時にオプションを付与すれば、コンテナ内でGPUも利用できる！x 4
- ホストの$HOME配下をコンテナにバインド(共有)
 - つまり、$HOME以外にも以下がデフォルトでコンテナ側に共有されるので、モデルや教師データの移動やコピーが楽 ！x 5 
 - システム管理者がユーザにバインドを許可していた場合は、ユーザ側でバインド元と先を指定することも可能です。

| ホスト(バインド元) | コンテナ(バインド先) |
|:--|:--|
| $HOME | $HOME |
| /sys | /sys |
| /proc | /proc |
| /tmp | /tmp |
| /var/tmp | /var/tmp|
| /etc/resolv.conf | /etc/resolv.conf |
| /etc/passwd | /etc/passwd |
| $PWD | $PWD |

他にも色々特徴がありますが、ここまででも便利なことが伝わったと思います。
それでは、早速Singularityのインストールを始めます。

# 2.Singularityのインストール
以前、構築した[GPUによる機械学習基盤](https://qiita.com/mkt3/items/5e698fce8fc8884bb524)へSingularityをインストールしていきます。他のGPU環境でも[この記事](https://qiita.com/mkt3/items/5e698fce8fc8884bb524#1-4titan-xp%E3%81%AE%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)のように適切なGPUドライバのインストールが終わっていれば、SingularityでGPUを利用できると思います。

| サーバ構成 | バージョン等 |
|:--|:--|
| ディストリビューション | CentOS 7.5(最小構成) |
| ファイルシステム | xfs |
| GPU | NVIDIA GeForce RTX 2080 |

基本的には、公式ドキュメントの[Installation](https://www.sylabs.io/guides/3.2/user-guide/installation.html#installation)に従ってインストールを進めます。

## 2-1.インストールに必要なパッケージのインストール
CentOSでコンパイルに必要なパッケージを以下のコマンドでインストールします。ディストリビューションがdebian系の場合は、必要なパッケージを公式ドキュメントで確認してください。

```Bash
$ sudo yum update -y && \
    sudo yum groupinstall -y 'Development Tools' && \
    sudo yum install -y \
    openssl-devel \
    libuuid-devel \
    libseccomp-devel \
    wget \
    squashfs-tools
```
## 2-2.Goのインストールと環境変数の設定
Singularityのインストールには、Goが必要になります。
CentOSでは2-1.でインストールしたパッケージにGoが含まれているので、別途Goのインストールの必要はありませんが、以下のコマンドでGoの環境変数を設定する必要があります。

```Bash
# 環境変数の設定
$ echo 'export GOPATH=${HOME}/go' >> ~/.bashrc
$ echo 'export PATH=/usr/local/go/bin:${PATH}:${GOPATH}/bin' >> ~/.bashrc
# 環境変数の読み込み
$ source ~/.bashrc
```
Singularityのインストール完了後は、追加したGOPATHとPATHを削除して問題ありません。

## 2-3. Singularityのダウンロード
Singularityのリリース状況を[ここ](<https://github.com/sylabs/singularity/releases)で確認します。
今回のバージョンは3.1.1です。以下のコマンドでダウンロードします。

```Bash
# バージョンを指定
$ export VERSION=3.1.1
# 保存先を指定して移動
$ mkdir -p $GOPATH/src/github.com/sylabs && \
    cd $GOPATH/src/github.com/sylabs
# Singularityをダウンロードして、展開後移動
$ wget https://github.com/sylabs/singularity/releases/download/v${VERSION}/singularity-${VERSION}.tar.gz && \
    tar -xzf singularity-${VERSION}.tar.gz && \
    cd singularity
```

## 2-4. Singularityのコンパイルとインストール
以下のコマンドでSingularityのコンパイルとインストールを行います。

```bash
$ ./mconfig && \
    make -C ./builddir && \
    sudo make -C ./builddir install
```
以上で、Singularityのインストールは完了です。

# 3. Singularityの利用方法
Singularityを利用するためには、以下の手順を行います。

1. SingularityのコンテナであるSingularity Image File (SIF)コンテナ(もしくはsandboxコンテナ)をビルド
1. SIFコンテナ(もしくはsandboxコンテナ)に対して、アクセス(shellやexec)

上記について具体的に以下記載します。

## 3-1. SIFコンテナ(もしくはsandboxコンテナ)のビルド
SIFコンテナは読取専用としてビルドされます。変更可能としたい際は、ビルド時にsandboxオプションを付与して、sandboxコンテナを作成します。

| 形式 | 概要 |
|:--|:--|
| SIFコンテナ(デフォルト) | プロダクションに適した安定な読取専用ファイル。$HOME配下等はホスト側をバインドしているので、ユーザ権限の範囲で変更可能。 |
| sandboxコンテナ(オプション) | 変更可能なchrootディレクトリ。対話的な開発・検証向け。 コーディングの途中で、root権限でライブラリを追加したり、pipでinstallできる|

SIFコンテナのビルド方法は、公式ドキュメントでbuildコマンドを利用する以下の5つが記載されています。

1. [Container Libirary](https://cloud.sylabs.io/library)からビルド
1. [Docker Hub](https://hub.docker.com/)からビルド
1. [Singularity Hub](https://singularity-hub.org/)からビルド
1. マシン上の既存のコンテナ(もしくはsandboxコンテナ)からビルド
1. Singularity定義ファイルからビルド

なお、buildコマンドにsandboxオプションを付与することでsandboxコンテナを作成できます。

### 3-1-1.Container Libiraryからビルド
公式の[Container Libirary](https://cloud.sylabs.io/library)からダウンロードして、ビルドします。
以下コマンドでは、centos7をビルドします。(URIがlibrary://から始まります)

```bash
$ sudo singularity build centos7.sif library://library/default/centos:latest
```

上記コマンドでは、Centos7.5.1804(Core)がビルドされました。

sandboxオプションを付与する場合は、以下のコマンドになります。なお、sandboxコンテナの場合は、出力がファイルからディレクトリ(chrootディレクトリ)に変更されますので、注意してください。

```bash
$ sudo singularity build --sandbox centos7/ library://library/default/centos:latest
```
### 3-1-2.Docker Hubからビルド
[Docker Hub](https://hub.docker.com/)からダウンロードして、ビルドします。
以下コマンドは、centos7をビルドします。(URIがdocker://から始まります)

```bash
$ sudo singularity build centos7.sif docker://centos:latest
```

上記コマンドでは、Centos7.6.1810(Core)がビルドされました。Docker Hubのほうがバージョンが上ですね。

### 3-1-3.Singularity Hubからビルド
[Singularity Hub](https://singularity-hub.org/)からダウンロードして、ビルドします。
以下コマンドは、centos7をビルドします。(URIがshub://から始まります)

```bash
$ sudo singularity build centos7.sif shub://singularityhub/centos:latest
```

上記コマンドでは、Centos7.4.1708(Core)がビルドされました。こちらもDocker Hubのほうがバージョンが上ですね。

### 3-1-4. マシン上の既存のコンテナ(もしくはsanbbox)からビルド
すでにローカルに保存されているコンテナがある場合は、それをターゲットとして使用して、新しいコンテナをビルドできます。
これにより、コンテナ形式の変換ができます。
例えば、色々と変更を加えたdevelopmentというsandboxコンテナをproduction.sifというSIFコンテナに変換したい場合は、次のコマンドを使います。

```bash
$ sudo singularity build production.sif development/
```

上記のようにsandboxコンテナは後からSIFファイルに変更できますが、コンテナの再現性を考えるとSingulrity定義ファイルを更新して、Singularity定義ファイルからプロダクション用のSIFコンテナをビルドするほうが良いと思います。

### 3-1-5. Singularity定義ファイルからビルド
Singularity定義ファイルは、Dockerでいうdockerfileです。
Singularityでは、Singularity定義ファイルをかなり柔軟に記載できますので、詳細は公式ドキュメントの[Definition Files]
(https://www.sylabs.io/guides/3.2/user-guide/definition_files.html)を参照してください。
ここでは、以下のようにCentos7用のSingularity定義ファイル(centos7.def)を作成しています。
Bootstrpにdockerを指定すると、dokcerイメージを元にコンテナを作成できます。(他にもBootstrpにlibrary、shubやyum等指定できます。)

```bash:centos7.def
Bootstrap: docker
From:centos:latest

%post
    yum update -y
    yum install -y emacs vim

%environment
    export PATH=/hoge/hoge:$PATH
```

以下のコマンドでSingularity定義ファイル(ここではcentos7.def)からビルドします。

```bash
$ sudo singularity build centos7.sif centos7.def
```

以上が5つのビルド方法になります。
私としては、3-1-5.のSigularity定義ファイルでBootstrapを用いてdockerイメージをイメージ元にする方法が、他のdockerを利用されている開発者の方との環境を合わせやすく、またビルド時に何をやっているのかが分かりやすいので、おすすめです。

#3-2.Singularityコンテナへのアクセス
では、実際にビルドしたSingularityコンテナにアクセスします。
Singularityコンテナへのアクセス方法は以下の方法があります。どちらもDockerで慣れている方法だと思います。

- shellコマンドでコンテナに入る
- execコマンドでホスト側からコンテナ内のコマンドを実行

## 3-2-1.shellコマンドでのアクセス
以下のコマンドを実行すると、Singularityコンテナで新しいシェルが起動され、そのシェル上でコンテナにアクセスできます。なお、コンテナ内のユーザは、ホスト側と同じユーザになります。

```bash
# SIFコンテナにアクセスする場合
$ singularity shell centos7.sif

# sandboxコンテナにアクセスする場合
$ singularity shell centos7
```
また、以下のようにSingularityコンテナではなく、library://、docker://やshub://を指定した場合は、一時的なコンテナが作成され、シェルから抜けると一時的なコンテナが削除されます。

```bash
$ singularity shell library://library/default/centos:latest
```

## 3-2-2.execコマンドでのアクセス
execコマンドを使うことで、Singularityコンテナで任意のコマンドを実行できます。
execコマンドの後にSingularityコンテナを指定して、その後に実行したいコマンドを指定します。

```bash
# Singularityコンテナのsystemリリース情報を表示
$ singularity exec centos7.sif cat /etc/system-release

CentOS Linux release 7.6.1810 (Core)
```
また、shellと同様にlibrary://、docker://やshub://を指定した場合は、一時的なコンテナが作成され、一時的なコンテナ上でコマンドが実行され、その後一時的なコンテナは削除されます。

上記2つの方法で、Singularityコンテナにアクセス可能となります。

# 4.GPUの仮想化
Singularityでは、Dockerと同様にGPUを仮想化して利用できます。
実際にSingularityコンテナ内でGPUを利用できるか試してみます。

## 4-1. nvidia-smiコマンドの実行
今回は、nvidia-dockerのコンテナイメージを利用して、nvidia-smiコマンドが利用できるか試してみます。
SingularityコンテナでGPUを利用する場合は、shellやexecにnvオプションを付与します。

```bash
# nvidia-dockerのコンテナイメージからsifコンテナを作成
$ sudo singularity build  cuda10.0-base.sif docker://nvidia/cuda:10.0-base

# execでnvidia-smiが起動することを確認
$ singularity exec --nv cuda10.0-base.sif nvidia-smi

# shellでsifコンテナのシェルにアクセス
$ singularity shell --nv cuda10.0-base.sif
# sifコンテナ内でnvidia-smiを実行
nvidia-smi
```

exec及びshellのどちらでも、以下の通りnvidia-smiで正常にGPUを確認できました。

```
+-----------------------------------------------------------------------------+
| NVIDIA-SMI xxx.xx       Driver Version: xxx.xx       CUDA Version: xx.xx    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 2080    Off  | 00000000:01:00.0 Off |                  N/A |
| 28%   45C    P0    49W / 215W |      0MiB /  7949MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
```

## 4-2. PytorchのSNLIサンプルの実行
PyTorchのSNLI[サンプルソース](https://github.com/pytorch/examples/tree/master/snli)をCPUのみ、CPU+GPU(Docker)、CPU+GPU(Singularity)の3パターンで実行します。
ちなみに、[SNLI(Standord Natural Language Inference)](https://nlp.stanford.edu/projects/snli/)は自然言語推論のためのデータセットです。
PyTorchのサンプルソースでは、前提と仮説からなる２つの文章を用いて学習を行い、与えられた２つの文章が前提と仮説の関係になっているかどうかをクラス分類します。

実行結果は以下の通りです。

| パターン| CPU | GPU | メモリ | epoch数| 学習時間  | 
|:--|:--|:--|:--|:--|:--|
|CPUのみ| Core i7 8700K 3.7GHz | 無し| 32GB | 5 | 3時間29分 | 
|CPU+GPU(Docker)| Core i7 8700K 3.7GHz | Geforce RTX2080 | 32GB | 5 | 6分50秒 |
|CPU+GPU(Singularity,SIFコンテナ)| Core i7 8700K 3.7GHz | Geforce RTX2080 | 32GB | 5 | 6分43秒 |

上記の結果から、SingularityによるGPU仮想化でもDockerによるGPU仮想化と同等の性能は出せそうです。
なお、弊社の環境ではGPUをsandboxコンテナで利用すると性能がかなり落ちます。


#まとめ
最後までお読みいただき、ありがとうございました。
今回は、Singularityの特徴と一通りの使い方を紹介しました。
まだまだ色々と便利な機能があるのですが、そちらについては[公式ドキュメント](https://www.sylabs.io/guides/3.2/user-guide/)をぜひ読んでみてください。色々発見があって面白いです。

Singularityの利用者が増え、これからさらにSingularityが便利になっていってくれると嬉しいです。