MPI
openmpi
HPC

MPIプログラムにおけるCoreファイルデバッグ

MPIプログラムのデバッグ方法その2です。

既存のタグがopenmpi

今回はCoreファイルを使ったデバッグについて書きます。

Coreファイルを使ったデバッグ

Coreファイルってご存知でしょうか。
プログラムが異常終了したりした時、メモリ上のデータをファイルとして出力し、デバッグなどに役立てるためのファイルです。
Coreファイルを使ったデバッグのための設定はこのあたりの記事が詳しいと思います。

https://qiita.com/suzutsuki0220/items/aa84d7e2e8f37e867f3d

MPIプログラムにおけるCoreファイルデバッグ

今回はCoreファイルを使ったデバッグをMPIプログラムでやります。

#include <stdio.h>
#include "mpi.h"

int main(int argc, char *argv[]) {
    int numprocs, myid;
    MPI_Init(&argc,&argv);
    MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
    MPI_Comm_rank(MPI_COMM_WORLD,&myid);

    if (myid != numprocs-1) {
        MPI_Barrier(MPI_COMM_WORLD);
    }

    printf("Complete\n");
    MPI_Finalize();
    return 0;
}

…だいぶ無茶がありますが、こんなプログラムを実行するとします。

すると、一番大きなランク番号を持つプロセスだけはif文をスキップするので、他のプロセスはバリアでデッドロックするわけです。

今回はコレをデバッグしていきたいと思います。

#include <stdio.h>
#include "mpi.h"

int main(int argc, char *argv[]) {
    int numprocs, myid;
    MPI_Init(&argc,&argv);
    MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
    MPI_Comm_rank(MPI_COMM_WORLD,&myid);

    if (myid != numprocs-1) {
        abort();
        MPI_Barrier(MPI_COMM_WORLD);
    }

    printf("Complete\n");
    MPI_Finalize();
    return 0;
}

まぁ適当にこの辺にバリアの前辺りにabort(3)とかを仕込んで実行します。

ここで、きちんとCoreファイルを出すポイントなのですが、
$ mpiexec -np 4 ./a.out
ではなく、run.shを作成して、以下のように実行してください。

$ mpiexec -np 4 ./run.sh

#!/bin/sh

./a.out
sleep 10

これは、Open MPIの実装だと、複数あるa.outのプロセスのうち、異常終了をしてしまうプロセスがいたりした場合そのシグナルを受けとり、他の正常に動作するプロセスを止めるよう動くためです。
そのため、abortを呼び出す時刻が微妙に異なると、Coreファイルを本来出すはずのプロセスが出さずに終了したりしてしまいます。
run.shを噛ませると、異常終了したことが伝わる前にCoreファイルを吐ききってしまうので、毎回同じプロセスが同じようにCoreファイルを出してくれます。

とりあえずこれで実行すると以下のような感じでcoreファイルが出てきます。

$ ls
a.out  core.11319  core.11321  core.11327  run.sh  barrier.c

しかし、取り敢えず3つ出てきたのは分かるのですが、ランク番号からどのcoreファイルなのか対応付けるのはかなり難しいわけです。

4プロセスぐらいでも再現するバグならいいんですが、64プロセス…1024プロセスと増えていくに従ってどうしようにもなくなっていきます。

前置きは長くなりましたが、まぁコレを便利にやっていこうというのが今回の内容です。

ランク番号とCoreファイルの対応づけ

とりあえず前回の記事のように、--output-filenameオプションを実行時に付けます。

https://qiita.com/nariba/items/2277c2eb428886eae80d

$ mpirun -np 4 --output-filename hoge ./run.sh

実行すると、hogeディレクトリの下に1というのができて、rank.0というディレクトリ、その下に各プロセスのstdoutとかstderrというファイルが出てきます(Open MPI 3.0.0の場合のみ、Open MPIのバージョンが異なると出力のファイル構成も違うようです)。

各プロセスのstderrを見てみると、abort(3)を呼んだプロセスは以下のような出力が出てきていると思います。
[host:16591]がホスト名、PIDを含んでいます。

[host:16591] *** Process received signal ***
[host:16591] Signal: Aborted (6)
[host:16591] Signal code:  (-6)
...
[host:16591] *** End of error message ***
./run.sh: line 3: 16591 Aborted                 (core dumped) ./a.out

ランク番号のディレクトリの下に、ホスト名、PIDがかいてあるファイルがあるわけです。
これでランク番号とcore.${PID}みたいになっているのを一致させるといいわけです。

rank2pid

私は以下のようなシェルスクリプト(rank2pid)を書いて使っています。
一つ目の引数には--output-filenameで指定するディレクトリ名、二つ目の引数にはランク番号(ゼロパディングが必要)です。

#!/bin/sh

cat ./$1/1/rank.$2/stderr |grep '^\[' |head -n 1 |sed -e 's/\[\(.*\):\([0-9]*\)\].*/\2/' -e 's/0*\([0-9]*[0-9]$\)/\1/g'
$ rank2pid hoge 2
28669

これを使えば、ファイル名にPIDしか含まないcoreファイルに対して、ランク番号からPIDに変換することが可能になります。

余談

並列同時デバッグ

こんなものがあります。

https://qiita.com/greymd/items/8744d1c4b0b2b3004147

並列処理なんだからデバッグも並列にやりたいですよね。

PIDがかぶる

余談ですが、普通PCクラスタの環境ってGFS(Global File System)を利用していることが多い気がします。

https://ja.wikipedia.org/wiki/Global_File_System

それだと、coreファイルにはホストに対する情報なんて何も乗っていないので、たまたま別のホストでたまたまPIDがかぶるような事があると、同じcore.${PID}になってしまいます。

普通にroot権限あれば出力ファイルのフォーマットいじれるようなので変えたほうがいい気がしますが、他の方はどうなんでしょうか?