背景
初めて記事を投稿してみます。
私はこれまで単一ノード内で完結するようなSlurm/PBSジョブの面倒を見ることが多く、基本的にはDockerを使ったGPU並列計算を行うケースがほとんどでした。
しかし、計算規模が大きくなってくると複数ノードを利用したくなり、結果として、MPIを使ったコンテナ間通信を実現したくなりました。
この時、Docker + MPIを使ってコンテナ間の並列処理を実現しようとすると、どうしてもコンテナの名前解決をする必要があったり、MPIが使えるようにimageをbuildし直さないといけなかったりと設定すべき項目が多数あり、なかなか気軽に実現しにくい部分があると感じていました。
そこで、MPIと相性の良いコンテナ技術としてApptainer(Singularity)を分からないながらに初めて使ってみた時のメモとして本記事を書いてみました。
本記事のゴール地点として、まずは試験的に単一ノード実行としてWSL2上のコンテナで姫野ベンチマークを実行できるところまでにします。
# 次回以降の記事で計算ノードを複数台にした場合に挑戦してみます。
実行環境
今回、Apptainer + MPIを実行した際のWSL2環境は下記の通りです。
・OS:Ubuntu 22.04.3 LTS (5.15.153.1-microsoft-standard-WSL2)
・Open MPI version 4.1.2 (WSL2に直でインストールしているMPI)
・apptainer version 1.3.3
・CPU:AMD Ryzen 7 3700X 8-Core Processor (論理プロセッサ数 16)
Apptainerのインストール作業
基本的にはApptainer公式のリンク[1]に従い、インストールしました。
version確認まで出来たらインストールは完了です。スムーズに導入できました。
sudo apt update
# 任意のディレクトリにdebファイルを保存する
wget https://github.com/apptainer/apptainer/releases/download/v1.3.3/apptainer_1.3.3_amd64.deb
sudo apt install -y ./apptainer_1.3.3_amd64.deb
# インストールが完了したら下記の通りversionが返ってくることを確認
> apptainer --version
apptainer version 1.3.3
<補足事項>
Singularityコマンドについて確認してみます。Apptainerは、元々Singularityとして呼ばれており、Linux Foundationへ移管されたタイミングで名称が変わっているようです。(リンク[2])
そのため下記のようにsingularityコマンドが使えるようになっており、
singulairtyコマンドがapptainerコマンドへシンボリックリンクが張られているようでした。
> which singularity
/usr/bin/singularity
> ls -l /usr/bin/singularity
lrwxrwxrwx 1 root root 9 Jan 1 2022 /usr/bin/singularity -> apptainer
SIFファイルのダウンロードとコンテナの起動方法
話を戻します。ここまででApptainerが利用できるようになりました。
そのためDockerと同様Containerを作るためのimageをpullする必要があります。
SIF (Singularity Image File) ファイルはContainerをファイルとして保存したものと理解しており、個人的にDocker imageに近い印象があります。
Docker imageとSIFファイルは互換性があり、apptainer-buildのマニュアルページを参照すると下記のように記載されています。
Targets can also be remote and defined by a URI of the following formats:
docker:// a Docker/OCI registry (default Docker Hub)
上記の記述方法を参考にDocker Hubからubuntuの最新版imageをpullしてみます。後ほどコンテナの中でaptコマンドをはじめとした書き込みを行いたいので、sandboxオプションを付与して「ubuntu-sandbox」コンテナを作成します。下記のようにコマンドを実行しました。
> sudo apptainer build --sandbox ubuntu-sandbox docker://ubuntu:latest
INFO: Starting build...
# 私はimageをpull済みのため、already existsが出力されている。
Copying blob 9c704ecd0c69 skipped: already exists
Copying config 35a8880255 done |
Writing manifest to image destination
2024/08/12 21:00:14 info unpack layer: sha256:9c704ecd0c694c4cbdd85e589ac8d1fc3fd8f890b7f3731769a5b169eb495809
INFO: Creating sandbox directory...
INFO: Build complete: ubuntu-sandbox
> ls -l
total 4
drwxr-xr-x 18 root root 4096 Aug 12 21:32 ubuntu-sandbox
末尾の3行から確認できる通り、buildコマンドを実行したディレクトリには想定通り、ubuntu-sandboxという名前でディレクトリが生成されていることが確認できます。これでコンテナを起動する準備が整いました。
コンテナ内部で対話的に書き込み(パッケージのインストール等)を行う際、下記のようにwオプションを付与し、apptainer shellコマンドを実行します。今回はsudoにてimageをpullしているため、sudoを付与してコンテナを立ち上げています。(コンテナの内部で権限が不足しないようにするため)
> sudo apptainer shell -w ubuntu-sandbox
WARNING: Skipping mount /etc/localtime [binds]: /etc/localtime doesn't exist in container
# ここから下はコンテナ内部での実行。OS versionがホストマシンよりも新しいことがわかる
Apptainer> cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04 LTS"
ここで、Dockerには無いApptainerの便利なポイントとしてホストマシン側でroot権限を持っておらず、コンテナ内部でのみrootを利用したい場合、下記のようにfakerootオプションを利用することで、コンテナ内でroot権限を使えるようになります。
# sudoを付与せず実行。
> apptainer build --sandbox ubuntu-sandbox docker://ubuntu:latest
INFO: Starting build...
Copying blob 31e907dcc94a skipped: already exists
Copying config edbfe74c41 done |
Writing manifest to image destination
2024/08/19 23:18:28 info unpack layer: sha256:31e907dcc94a592a57796786399eb004dcbba714389fa615f5efa05a91316356
INFO: Creating sandbox directory...
INFO: Build complete: ubuntu-sandbox
# fakerootオプションを付与してコンテナを実行する
> apptainer shell --fakeroot -w ubuntu-sandbox
WARNING: Skipping mount /etc/localtime [binds]: /etc/localtime doesn't exist in container
Apptainer> su -
# rootへの昇格が確認できる
root@DESKTOP-7MLU1A3:~#
Apptainerコンテナ内でのMPI環境整備
Apptainerコンテナの中に入ることができたら、コンテナの中でMPIを実行できるように設定を加えます。以下のようにコンテナ内でaptコマンドを実行し、MPI実行に必要なツールをインストールしました。これで単一コンテナ内でMPIプログラムが実行できる状態になりました。ここまでで一旦コンテナからはexitしてホストマシンに戻ります。
Apptainer> apt update
Apptainer> apt-get install openmpi-bin/noble openmpi-doc/noble libopenmpi-dev/noble
Apptainer> which mpirun
/usr/bin/mpirun
Apptainer> mpirun --version
# ホストマシン側のMPI versionと揃えたほうがよかったかもしれない...
mpirun (Open MPI) 4.1.6
ベンチマークの配備
当初の目的であった姫野ベンチマークを実行できるようにします。
ベンチマークプログラムはこちら[3]のリンクからC + MPI, static allocate versionをダウンロードして利用しました。
ダウンロードしたソースコードはホストマシン側で解凍 + ビルドを済ませて、実行ファイルbmtを生成しておきました。手順はダウンロードしたサイトに記載はありますが、個人的にはこちら[4]のサイトが非常に参考になりました。本投稿ではベンチマークをbuildする手順は省略します。
ホストマシン上でベンチマークのbuildが完了したら、そのフォルダ一式をApptainerコンテナ側から参照/実行できるようにする必要があります。コンテナ内部のファイルやフォルダには、ホスト側から直アクセスできるので、下記のようにApptainerコンテナ内部の/home/ubuntuにbuild済みベンチマーク一式をコピーしました。 (このような直コピーの良し悪しについてはもう少し調査の余地がありそうです…)
コピー完了後、下記のようにファイルがコンテナ内から参照できます。
ここまで完了すればベンチマークプログラムは実行できるようになっています。
# ホストマシン側からコンテナ内にbuildしたベンチマーク一式をコピー
> sudo cp -r ~/benchmark/ ./ubuntu-sandbox/home/ubuntu
# コンテナの中に再度アクセスする
> sudo apptainer shell -w ubuntu-sandbox
Apptainer> cd /home/ubuntu
Apptainer> pwd
/home/ubuntu
# コピーしたフォルダ一式が参照できることが分かる
Apptainer> ls -l
total 4
drwxr-xr-x 4 ubuntu ubuntu 4096 Aug 3 07:39 benchmark
ベンチマークの実行
まずはコンテナの中からベンチマークを実行してみます。
/home/ubuntu/benchmark配下に生成した実行ファイルbmtを、以下のようにオプションを付与して実行しました。
ホストマシン(WSL2)への割り当てメモリ量が少ないとMPIのプロセスがkillされてしまう可能性があるので、適宜姫野ベンチマークのサイズを「M」や「S」等の小規模なものに変更して実行する。([3]を参考のこと)
### コンテナの内側からbmtを実行する
Apptainer> cd /home/ubuntu/benchmark/
Apptainer> pwd
/home/ubuntu/benchmark
### ベンチマークプログラムをオプションを付与して実行する
### 並列数(np)はご利用のcpuやベンチマークのサイズに合わせて変更してください。
Apptainer> mpirun --use-hwthread-cpus -np 16 --allow-run-as-root ./bmt
Sequential version array size
mimax = 257 mjmax = 257 mkmax = 513
Parallel version array size
mimax = 257 mjmax = 131 mkmax = 67
imax = 256 jmax = 129 kmax =65
I-decomp = 1 J-decomp = 2 K-decomp =8
Start rehearsal measurement process.
Measure the performance in 3 times.
MFLOPS: 6728.006030 time(s): 0.498829 8.459592e-04
Now, start the actual measurement process.
The loop will be excuted in 360 times
This will take about one minute.
Wait for a while
cpu : 59.592028 sec.
Loop executed for 360 times
Gosa : 6.922458e-04
MFLOPS measured : 6758.197218
Score based on Pentium III 600MHz : 81.581328
ここまで動くことが確認出来たらOK。
最後にこれをホストマシン側からMPI実行できることを確認してみます。
Apptainerコンテナからexitで離脱し、ホストマシン (WSL2) 側のMPIでコンテナプロセスを並列実行してみた結果、下記のように正常終了を確認しました!
### オプションを上記の例と揃えてホストマシン側からMPI実行する。
### 並列数(np)はご利用のcpuやベンチマークのサイズに合わせて変更してください。
sudo mpirun --use-hwthread-cpus -np 16 --allow-run-as-root apptainer exec ubuntu-sandbox /home/ubuntu/benchmark/bmt
Sequential version array size
mimax = 257 mjmax = 257 mkmax = 513
Parallel version array size
mimax = 257 mjmax = 131 mkmax = 67
imax = 256 jmax = 129 kmax =65
I-decomp = 1 J-decomp = 2 K-decomp =8
Start rehearsal measurement process.
Measure the performance in 3 times.
MFLOPS: 6818.885504 time(s): 0.492180 8.459592e-04
Now, start the actual measurement process.
The loop will be excuted in 365 times
This will take about one minute.
Wait for a while
cpu : 59.909275 sec.
Loop executed for 365 times
Gosa : 6.903522e-04
MFLOPS measured : 6815.776282
Score based on Pentium III 600MHz : 82.276392
まとめ
本記事の最後の実行結果から、単一ノード内でApptainerを使ったプロセス並列が実現できることが確認できました。このことから複数ノードで実行した場合でも同様にApptainerのプロセスをMPI並列処理できることが分かります。
次回の記事では2台のRaspberry Piを利用して、Apptainer + MPI並列にて想定通りベンチマークを実行できるのか確認してみる予定です。
参考にさせていただいた記事
[4] 姫野ベンチマークを使ってOpenMPIの性能を測定する
WSL2上でapptainerを実行していた例
ROS 2+apptainerで仮想環境生活 (zenn.dev)
コメントを残す