MPICH2のインストール

(2008.10.8)

Linux機とWindows機が混在する (ヘテロ) 環境で、並列にプログラムを動かす方法について。

MPI

並列プログラミングはいろいろな方法がありますが、ここではMPIライブラリを使ってみます。

MPI (Message Passing Interface) は、その名前のとおり、並列に動作するプログラム間で、メッセージをやり取りするための仕様です。MPI-1, MPI-2 として標準化されています。

複数の機械(ノード)を結んで、あたかも一つの計算機として計算させるために用いられます。(クラスタ)

MPIは、

クラスタを構成する各ノードでは、同じ場所(パス)にプログラムを置きます。普通は、すべてのノードのCPUのアーキテクチャ、OSなどを揃えておいて、NFSなどでプログラム自体を共有するようです。

各ノードのプログラムの起動は、マスタノードから一括しておこないます。

私は、Windows機も参加させたかったので、ソースだけを配布し、各機械でコンパイルしました。WindowsではCygwin環境を使いました。

MPIライブラリ

MPIライブラリには、Open MPI, LAM/MPI, MPICH, GridMPI などがあります。

商用製品もあります。例えば、

Fedora 9 Linuxには、Open MPI のパッケージが含まれます。Open MPI でいこうかと思いましたが、試したときには、Cygwinで (ソースからコンパイルして) 動かすとクラッシュしてしまいました。

MPICH2 は、移植性を重視したライブラリです。こちらは Linux でも Cygwinでも大丈夫でした。以降では、MPICH2を使います。

MPICH2のインストール

MPICH2は次のところから入手できます。

2009.1現在の最新版はバージョン1.0.8です。Windows用のバイナリもありますが、Cygwin で動かすときは、ソースを取ってきて自分でコンパイルします。

あらかじめ Python をインストールしておかないといけません。

configureスクリプトに与えるオプションなどは MPICH2 Installer's Guide を参照してください。例えば、次のようにします。

$ ./configure --enable-fast=O3 --with-device=ch3:ssm

--with-device はノード間の通信方法を選択します。

ch3:ssmは、違うノード間はソケットで、同一ノード内のCPU間は共有メモリを利用します。マルチスレッドと併用するときは、ch3:sock (すべてソケット) にします。

$ make
$ su 
# make install

1台で動くかテスト

次に、秘密の文字列を含む .mpd.conf ファイルを各ノードのホームディレクトリに作ります。

内容は、例えば、次のようにします。

secretword=hogehogefunifuni

secretwordの内容はすべてのノードで同じにします。私は最初、ノードごとに変えるのかと勘違いし、動かなくて悩みました。

.mpd.confファイルのパーミッションを変更します。

$ chmod 600 ~/.mpd.conf

mpdコマンドを起動して、1台で動作するか確認します。mpdtraceコマンドでホスト名が表示されるはずです。

$ mpd &
$ mpdtrace
$ mpdallexit

mpdallexitは、すべてのノードのmpdを終了させます。

もう一度テストします。

$ mpd &
$ mpiexec -n 1 /bin/hostname
$ mpdallexit

sshの設定

MPICH2は SSH 経由で各ノードのプログラムを起動します。そのときにパスワードなしでコマンドを送れるようにします。

次の手順で鍵を各マシンに登録します。

  1. 秘密鍵と公開鍵を生成する

    鍵ペアは ssh-keygen コマンドで作ります。-t オプションで rsa か dsa を指定します。dsa は FIPS 186 Digital Signature Standard で定義されるデジタル署名アルゴリズムです。

    ここではrsaで作ってみます。

    $ ssh-keygen -t rsa
    

    デフォルトで、~/.ssh/id_rsa, ~/.ssh/id_rsa.pub ファイルが作られます。

  2. マスタノード以外に公開鍵を登録

    秘密鍵は本人だけが知っているべき鍵ですから、ログインする側の機械(クライアント)だけが持ちます。マスタノードがクライアントに、それ以外の機械がサーバになります。

    各ノードに(秘密鍵ではなく)公開鍵を配布し、それぞれのノードで決められたファイルに追記します。

    scpコマンドでコピーしてもいいでしょう。

    $ cat id_rsa.pub >> ~/.ssh/authorized_keys
    $ chmod 600 ~/.ssh/authorized_keys
    
  3. マスタノードで秘密鍵を使う

    秘密鍵は ~/.ssh/id_rsaに保存しておきます。

    マスタノードで ssh-agentを起動します。ssh-agentはforkして常駐し、必要な環境変数を出力します。

    evalしてシェルに環境変数を登録します。

    次に ssh-add を起動します。引数なしだと、~/.ssh にある id_rsaまたはid_dsa ファイルを ssh-agentに登録します。ssh-agentが常駐している間、sshコマンドは自動的にその秘密鍵を使います。

    $ eval `ssh-agent`
    $ ssh-add 
    Enter passphrase for /home/.../id_rsa: パスワードを入力
    

では、パスワードなしで別ノードのプログラムを起動できるかテストします。

$ ssh 別ノードのホスト名 date

複数台で動かす

ノードの一覧を記録したmpd.hostsファイルを作ります。1行1ノードで、先頭を#にするとコメントになります。

ホスト名の後ろに「:プロセス数」を付けることもできます。mpiexecコマンド(後述)に渡すことで、ノード毎の起動プロセス数を制御できます。

例えば次のように書きます。自ホストも書かなければならないことに注意してください。

host1 
host2:2

マスタノードでmpdbootコマンドを実行します。mpdbootはクラスタに参加する各ノードでmpdコマンドを常駐させます。

$ mpdboot -n 開始するノード数 -f mpd.hosts

mpdtraceで起動中のノードを表示させます。-lオプションを付けるとホスト名とIPアドレスを表示します。

$ mpdtrace
host1
host2

簡単なプログラム

ごく簡単なプログラムを書いてみます。

C
[POPUP]
  1. #include <mpi.h> // <stdio.h> より先
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. int main(int argc, char* argv[]) {
  5. int myrank, buffer;
  6. MPI_Status status;
  7. // MPIライブラリを初期化する。
  8. MPI_Init(&argc, &argv);
  9. // 自分のランクを調べる。
  10. MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
  11. printf("%d: start.\n", myrank);
  12. int const TAG = 1234;
  13. if (myrank == 0) {
  14. // マスタノード
  15. buffer = 10;
  16. MPI_Send(&buffer, 1, MPI_INT, 1, TAG, MPI_COMM_WORLD);
  17. printf("%d: sent %d to rank 1\n", myrank, buffer);
  18. // MPI_Recv()は、ブロックする。
  19. MPI_Recv(&buffer, 1, MPI_INT, 1, TAG, MPI_COMM_WORLD, &status);
  20. printf("%d: received %d from rank 1\n", myrank, buffer);
  21. }
  22. else if (myrank == 1) {
  23. MPI_Recv(&buffer, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD, &status);
  24. printf("%d: recv %d\n", myrank, buffer);
  25. sleep(3);
  26. buffer *= 2;
  27. MPI_Send(&buffer, 1, MPI_INT, 0, TAG, MPI_COMM_WORLD);
  28. }
  29. MPI_Finalize();
  30. return 0;
  31. }

C++はmpicxxコマンドでコンパイルします。

$ mpicxx -Wall first.cc

各ノードで実行ファイルを作ったら、mpiexecコマンドで実行します。-nオプションはプロセス数で、ノード数より多い数でも構いません。

$ mpiexec -n 2 ./a.out

あるいは、mpd.hostsファイルでノード毎の起動プロセスをコントロールします。このときは-nオプションでmpd.hosts内のプロセス数より多い数を指定するとエラーになります。

$ mpiexec -machinefile mpd.hosts -n 6 ./cpi