MPICH2のインストール

(2008.10.8)

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

MPI

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

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

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

MPIは、

  • ブロードキャストなどデータを各ノードに配布したり集計したりする関数がある。かつ、効率のいい実装が期待できる。
  • RPC (Remote Procedure Call) ではない。送信データに型は付けれるが、どの関数に振り分けるかは受信側でプログラムを書かないといけない。

クラスタを構成する各ノードでは、同じ場所(パス)にプログラムを置きます。普通は、すべてのノードの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++
[RAW]
  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