Skip to content

Instantly share code, notes, and snippets.

@kumpeishiraishi
Created February 3, 2022 04:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kumpeishiraishi/6d2fcc6da0ebcdc23c0ef301063ae8a5 to your computer and use it in GitHub Desktop.
Save kumpeishiraishi/6d2fcc6da0ebcdc23c0ef301063ae8a5 to your computer and use it in GitHub Desktop.

研究室の計算機でMPI並列するには

研究室の計算機クラスタでは、1つのノード内で24コア程度のCPUが使える。 単一ノード内の並列計算ならば、Open MPを使って比較的簡単にコード開発・実行が出来るが、並列数に制限がある。 これを越える並列数で計算するため、MPIの使い方のメモを残しておく。 MPI的に正しい書き方・術語である自信はないので、鵜呑みにせず各自で勉強してほしい。

プログラムを書く

ノード間並列をしたい場合MPIを使う。 なお、MPIは単一ノード内の並列化でも使用できる。

C++でMPIを使う場合、生のMPIを使う方法とBoost.MPIを使う方法がある。 C++的に自然な書き方ができるのは後者だと思われるが、Boost.MPIの導入はやや面倒である。 それ以外のboostライブラリと異なり、ビルドが必要だからである。 少なくとも piano に元々入っているコンパイラとMPIで私はビルドに成功しておらず、他のコンパイラでのトライなどはしていない。 ここでは、生のMPIを使う方法を簡単にまとめる。

先ずはヘッダー

#include <mpi.h>

を書く。

プログラム中では、最初に

int main(int argc, char** argv) {
    int rank;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
}

を書く。

MPI_Init で初期化をしている。 この際、コマンドライン引数で受け取った並列数を argv で渡している(引数の与え方については後述)。

MPI_Comm_rank では、自身が存在するプロセス・インデックスを int 型の変数 rank に格納している。

あとは自由にプログラム内部を記述していく。 プログラムの最後には

MPI_Finalize();

と書いてMPIを終了させる。

よく使用するのは

MPI_Barrier(MPI_COMM_WORLD);
MPI_Allgather(&vec[rank], 1, MPI_DOUBLE, vec.data(), 1, MPI_DOUBLE, MPI_COMM_WORLD);

あたりだろうか( vecstd::vector<double> 型)。

MPI_Barrier は、全プロセスが通過するまで計算を待機する命令である。 もちろん、律速のプロセスを待つ間、他のプロセスはアイドル状態なので、全体の実行速度は低下するので注意。 また、 MPI_Barrier 同士は識別されず、全プロセスがプログラム中の「どこかの」 MPI_Barrier を通過すれば計算が再開するので、留意する必要がある。

MPI_Gather は、計算結果をどこかのプロセスに集約する命令。 MPI_Allgather は、計算結果を全プロセスで共有する命令で、 MPI_Gather を繰り返すより高速に動作する。 その他はネットで検索すればたくさん出てくる。

コンパイルする

コンパイルではコマンド mpic++ を使う。 piano には複数のOpen MPIが入っているが、ここでは /usr/local/openmpi-2.1.6-intel64-v15.0.2/bin/mpic++ を使うべきである。 これより古いバージョンには、ノード間並列化が実行できないバグがあるからである。

この mpic++ が実際に何をやっているのか知りたい場合、

mpic++ -show

とすればverboseな表示となる。

このときは、実行時の並列数などは考えずにコンパイルしよう。 CMakeを使う際は、 find_package(MPI REQUIRED) などを書く必要がある(ネットに解説多数)。

実行する

例えば生成された a.out を20並列で実行するには、

mpirun -np 20 ./a.out

とする。 投入するジョブスクリプトファイルでもPATHを通して、使用する mpirun とコンパイル時に使った mpic++ のバージョンを同じにしよう。

さて、ここまではMPIの話であったが、以下ではジョブ管理システム torque の使い方の話である。

単一ノードの場合

希望する並列数が単一ノード上で用意できる場合、ジョブスクリプトに

#PBS -l nodes=1:ppn=20:X
#PBS -q beethoven01

と書けばノード指定して計算できる。 ノード指定しないで行う場合は、2行目末尾の :X と 3行目を削除する。

複数ノードの場合

ノード指定しない場合は、

#PBS -l nodes=2:ppn=10

と書く。 torque が10コアの空きを2ノードで探して計算が開始される(合計20並列)。

ノード指定する場合は、

#PBS -l nodes=ika01:ppn=5+ika02:ppn=15

として、ノード名とコア数を指定する(これも合計20並列)。 この場合、ノードを表すアルファベットを書かなくてもノード指定できるが、 qstat では default と表示される。

なお、単一・複数どちらの場合も、PBSで指定した並列数の合計と mpirun -np 後に指定した並列数が合致することを確認しよう。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment