3

ここ数週間、MPI の実装方法を学んでいますが、MPI_Allgatherv の入力引数の設定方法を理解するのに非常に苦労しています。ここでは赤ちゃんの一歩を踏み出す必要があるため、おもちゃの例を使用します。私が行った調査の一部は、この投稿の最後にリストされています (この質問につながった以前の質問を含む)。まず、私が達成しようとしていることの簡単な要約:

-- 概要 -- 私は std::vector A を取得し、A のさまざまな部分で複数のプロセッサを動作させてから、A の更新された部分を取得し、それらの更新をすべてのプロセッサに再配布します。したがって、すべてのプロセッサは、A のコピーで開始し、A の一部を更新し、A の完全に更新されたコピーで終了します。

次のように初期化された "mydata" と呼ばれる 5 つの要素を含む std::vector < double > があるとします。

for (int i = 0; i < 5; i++)
{
    mydata[i] = (i+1)*1.1;
}

ここで、コードを 2 つのノード (int tot_proc = 2) で実行しているとします。「int id_proc」を使用して「現在の」ノードを識別するため、ルート プロセッサは id_proc = 0 になります。mydata の要素数が奇数であるため、プロセッサ間で作業を均等に分散できません。私はいつも次のように作業を分割するとしましょう:

if (id_proc < tot_proc - 1)
{
   //handle mydata.size()/tot_proc elements
}
else
{
   //handle whatever is left over
}

この例では、次のことを意味します: id_proc = 0 は mydata[0] と mydata[1] (5/2 = 2 から 2 つの要素) で機能し、… id_proc = 1 は mydata[2] - mydata[4 で機能します。 ] (5/2 + 5%2 = 3 であるため、3 つの要素)

各プロセッサが mydata のそれぞれの部分を処理したら、Allgatherv を使用して結果をマージし、各プロセッサの mydata に更新されたすべての値が含まれるようにします。Allgatherv は 8 つの引数を取ることがわかっています: (1) 送信される要素/データの開始アドレス、(2) 送信される要素の数、(3) 送信されるデータのタイプ、この例では MPI_DOUBLE、(4 ) データを受信したい場所のアドレス (「開始」アドレスの言及なし)、(5) 受信する要素の数、(6) 引数 # の受信場所に対するメモリ内の「変位」 4、(7) 受信するデータのタイプ、これも MPI_DOUBLE、(8) 使用しているコミュニケーター、私の場合は単純に MPI_COMM_WORLD です。

ここから混乱が始まります。プロセッサ 0 は最初の 2 つの要素を処理し、プロセッサ 1 は最後の 3 つの要素を処理したため、プロセッサ 0 は最初の 2 つの要素を送信する必要があり、プロセッサ 1 は最後の 3 つの要素を送信する必要があります。私には、これは Allgatherv の最初の 2 つの引数が次のようになることを示唆しています。

プロセッサ 0: MPI_Allgatherv(&mydata[0],2,…</p>

プロセッサ 1: MPI_Allgatherv(&mydata[2],3,…

(Q1) そうですか。そうであれば、次の質問は引数 2 の形式に関するものです。たとえば、sendcount[0] = 2、sendcount[1] = 3 となる std::vector < int > sendcount を作成するとします。

(Q2) 引数 2 では、sendcount の最初の場所への参照が必要ですか、それとも各プロセッサに関連する場所への参照を送信する必要がありますか? つまり、これらのうちどれを行う必要がありますか。

Q2 - オプション 1

プロセッサ 0: MPI_Allgatherv(&mydata[0], &sendcount[0],…</p>

プロセッサ 1: MPI_Allgatherv(&mydata[2], &sendcount[0],…</p>

Q2 - オプション 2

プロセッサ 0: MPI_Allgatherv(&mydata[0], &sendcount[id_proc], … (ここでは id_proc = 0)

プロセッサ 1: MPI_Allgatherv(&mydata[2], &sendcount[id_proc], … (ここでは id_proc = 1)

... 引数 4 に進みます。mydata のさまざまなセクションをそれ自体に戻すので、この引数は引数 1 と同じように見えると思います。つまり、&mydata[?] のようなものにする必要があります。(Q3) この引数は単に mydata の先頭への参照 (つまり &mydata[0]) にすることはできますか? それとも、引数 1 で行った方法でインデックスを変更する必要がありますか? (Q4) 3 つのプロセッサを使用したとします。これは、プロセッサ 1 が、ベクトルの「中間」にある mydata[2] と mydata[3] を送信することを意味します。ベクトルの要素は連続しているため、プロセッサ 1 が受信しているデータを分割する必要があります (一部は前に、mydata[4] は後で)。この議論でその分裂を説明する必要がありますか? もしそうなら、どのように?

…議論 5 の方が少しややこしいですが、今朝思いついたことがあります。おもちゃの例を使用すると、プロセッサ 0 が 2 つの要素を送信している場合、3 つの要素を受信することになりますよね? 同様に、プロセッサ 1 が 3 つの要素を送信している場合、2 を受信して​​います。

for (int i = 0; i < tot_proc; i++)
{
    recvcount[i] = mydata.size() - sendcount[i];
}

それが本当なら、それを &recvcount[0] または &recvcount[id_proc] (引数 2 と同様) として Allgatherv に渡しますか?

最後に、引数 6 です。これが引数 4 の入力に関連付けられていることはわかっています。私の推測では、次のようになります。すべてのプロセッサで引数 4 として &mydata[0] を渡すと、変位はメモリ内の位置の数になります。実際にデータを受信する必要がある最初の場所に移動する必要があります。例えば、

プロセッサ 0: MPI_Allgatherv( … , &mydata[0], … , 2, … );

プロセッサー 1: MPI_Allgatherv( … , &mydata[0], … , 0, … );

(Q5) 上記の 2 行は、「プロセッサ 0 は &mydata[0+2] からデータを受信します。プロセッサ 1 は &mydata[0+0] からデータを受信します」という意味でしょうか。?? また、Q4 のようにデータを分割する必要がある場合はどうなりますか? 最後に、ベクトルの一部を収集してそれ自体に戻すので (mydata を更新された mydata に上書きして置き換える)、これは、ルート プロセス以外のすべてのプロセッサが &mydata[0] から始まるデータを受信することを示しています。(Q6) そうだとすれば、ルート以外のすべてのプロセッサのディスプレイスメントは 0 であるべきではないか?

私が読んだリンクのいくつか: MPI_allgather と MPI_allgatherv の違い MPI_Allgather と MPI_Alltoall 関数の違い? std::vector C++ の MPI_Gatherv の問題: MPI の gatherv を使用して長さの異なるベクトルを連結する http://www.mcs.anl.gov/research/projects/mpi/www/www3/MPI_Allgatherv.html https://computing. llnl.gov/tutorials/mpi/#Routine_Arguments

stackoverflow に関する以前の投稿: MPI C++ 行列の加算、関数の引数、および関数の戻り値

私が読んだほとんどのチュートリアルなどは、Allgatherv をざっと説明しただけです。

4

1 に答える 1

3

ここでの混乱の一部は、インプレース ギャザーを実行しようとしていることです。同じ配列から送受信しようとしています。その場合は、MPI_IN_PLACEオプションを使用する必要があります。この場合、送信場所またはカウントを明示的に指定しません。これらは、受信しているバッファーとは異なるバッファーから送信している場合に使用できますが、インプレース ギャザーは多少制限されています。

したがって、これは機能します:

#include <iostream>
#include <vector>
#include <mpi.h>

int main(int argc, char **argv) {
    int size, rank;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (size < 2) {
        std::cerr << "This demo requires at least 2 procs." << std::endl;
        MPI_Finalize();
        return 1;
    }

    int datasize = 2*size + 1;
    std::vector<int> data(datasize);

    /* break up the elements */
    int *counts = new int[size];
    int *disps  = new int[size];

    int pertask = datasize/size;
    for (int i=0; i<size-1; i++)
        counts[i] = pertask;
    counts[size-1] = datasize - pertask*(size-1);

    disps[0] = 0;
    for (int i=1; i<size; i++)
        disps[i] = disps[i-1] + counts[i-1];

    int mystart = disps[rank];
    int mycount = counts[rank];
    int myend   = mystart + mycount - 1;

    /* everyone initialize our data */
    for (int i=mystart; i<=myend; i++)
        data[i] = 0;

    int nsteps = size;
    for (int step = 0; step < nsteps; step++ ) {

        for (int i=mystart; i<=myend; i++)
            data[i] += rank;

        MPI_Allgatherv(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL,
                       &(data[0]), counts, disps, MPI_INT, MPI_COMM_WORLD);

        if (rank == step) {
            std::cout << "Rank " << rank << " has array: [";
            for (int i=0; i<datasize-1; i++)
                std::cout << data[i] << ", ";
            std::cout << data[datasize-1] << "]" << std::endl;
        }
    }

    delete [] disps;
    delete [] counts;

    MPI_Finalize();
    return 0;
}

ランニングは与える

$ mpirun -np 3 ./allgatherv
Rank 0 has array: [0, 0, 1, 1, 2, 2, 2]
Rank 1 has array: [0, 0, 2, 2, 4, 4, 4]
Rank 2 has array: [0, 0, 3, 3, 6, 6, 6]
于 2013-04-11T15:53:35.860 に答える