6

格子ボルツマン モデリング用の CUDA コードを MPI しようとしていますが、MPI_Send 関数と MPI_Recv 関数でイライラする問題に遭遇しました。デバイス バッファーへの単純なデバイス バッファー MPI send/recv コードを備えた CUDA 対応 MPI があることを確認したので、CPU/ホストを経由せずに GPU デバイス メモリ間で配列を正常に送受信できます。

私のコードは、さまざまなノード間で z 方向に沿って分割された 3D ラティス用であり、これらの分割間で流体が流れるように、ノード間に Halos が渡されます。Halos は GPU 上にあります。以下のコードは簡略化したもので、コンパイルするとメイン コードと同じエラーが発生します。ここで、ランク 0 ノードの GPU Halo はランク 1 ノードへの MPI_Send() であり、MPI_Recv() です。現時点では、私の問題は非常に単純に思えます。MPI_Send と MPI_Recv の呼び出しを機能させることができません! コードは「//CODE DOES NOT REACH HERE.」に進みません。MPI_etc() 呼び出しが機能していないと結論付けました。

私のコードは基本的に次のとおりです。コードの多くは削除されていますが、同じエラーでコンパイルするには十分です。

#include <mpi.h>
using namespace std; 

    //In declarations:
    const int DIM_X = 30;
    const int DIM_Y = 50;
    const int Q=19;
    const int NumberDevices = 1;
    const int NumberNodes = 2;

    __host__        int SendRecvID(int UpDown, int rank, int Cookie) {int a =(UpDown*NumberNodes*NumberDevices) + (rank*NumberDevices) + Cookie; return a;} //Use as downwards memTrnsfr==0, upwards==1

    int main(int argc, char *argv[])
    {
       //MPI functions (copied from online tutorial somewhere)
       int numprocessors, rank, namelen;
       char processor_name[MPI_MAX_PROCESSOR_NAME];

       MPI_Init(&argc, &argv);
       MPI_Comm_size(MPI_COMM_WORLD, &numprocessors);
       MPI_Comm_rank(MPI_COMM_WORLD, &rank);
       MPI_Get_processor_name(processor_name, &namelen);

       /* ...code for splitting other arrays removed... */

       size_t size_Halo_z   = Q*DIM_X*DIM_Y*sizeof(double);  //Size variable used in cudaMalloc and cudaMemcpy.
       int NumDataPts_f_halo    = DIM_X*DIM_Y*Q;                 //Number of data points used in MPI_Send/Recv calls.
       MPI_Status status;                                        //Used in MPI_Recv.

       //Creating arrays for GPU data below, using arrays of pointers:
       double   *Device_HaloUp_Take[NumberDevices];              //Arrays on the GPU which will be the Halos.
       double   *Device_HaloDown_Take[NumberDevices];            //Arrays on the GPU which will be the Halos.
       double   *Device_HaloUp_Give[NumberDevices];              //Arrays on the GPU which will be the Halos.
       double   *Device_HaloDown_Give[NumberDevices];            //Arrays on the GPU which will be the Halos.

       for(int dev_i=0; dev_i<NumberDevices; dev_i++)   //Initialising the GPU arrays:
       {
          cudaSetDevice(dev_i);

          cudaMalloc( (void**)&Device_HaloUp_Take[dev_i],   size_Halo_z);
          cudaMalloc( (void**)&Device_HaloDown_Take[dev_i],     size_Halo_z);
          cudaMalloc( (void**)&Device_HaloUp_Give[dev_i],   size_Halo_z);
          cudaMalloc( (void**)&Device_HaloDown_Give[dev_i],     size_Halo_z);
       }

       int Cookie=0;             //Counter used to count the devices below.
       for(int n=1;n<=100;n++)   //Each loop iteration is one timestep.
       {    
       /* Run computation on GPUs */


          cudaThreadSynchronize();

          if(rank==0)   //Rank 0 node makes the first MPI_Send().
          {
             for(Cookie=0; Cookie<NumberDevices; Cookie++)
             {
                if(NumberDevices==1)            //For single GPU codes (which for now is what I am stuck on):
                {
                   cout << endl << "Testing X " << rank << endl;
                   MPI_Send(Device_HaloUp_Take[Cookie],     NumDataPts_f_halo,  MPI_DOUBLE, (rank+1), SendRecvID(1,rank,Cookie), MPI_COMM_WORLD);
                   cout << endl << "Testing Y " << rank << endl;   //CODE DOES NOT REACH HERE.
                   MPI_Recv(Device_HaloUp_Give[Cookie], NumDataPts_f_halo,  MPI_DOUBLE, (rank+1), SendRecvID(0,rank+1,0), MPI_COMM_WORLD, &status);     
                   /*etc */
                }
             }

          }
          else if(rank==(NumberNodes-1))
          {
             for(Cookie=0; Cookie<NumberDevices; Cookie++)
             {
                if(NumberDevices==1)
                {
                   cout << endl << "Testing  A " << rank << endl;
                   MPI_Recv(Device_HaloDown_Give[Cookie],   NumDataPts_f_halo,  MPI_DOUBLE, (rank-1), SendRecvID(1,rank-1,NumberDevices-1), MPI_COMM_WORLD, &status);
                   cout << endl << "Testing  B " << rank << endl;    //CODE DOES NOT REACH HERE.
                   MPI_Send(Device_HaloUp_Take[Cookie],     NumDataPts_f_halo,  MPI_DOUBLE, 0, SendRecvID(1,rank,Cookie), MPI_COMM_WORLD);
                   /*etc*/
                }
            }
         }
      }
      /* Then some code to carry out rest of lattice boltzmann method. */

   MPI_Finalize();
}

2 つのノード (コード内の NumberNodes==2 変数) があるため、1 つをランク ==0、もう 1 つをランク==1==NumberNodes-1 としています。ランク 0 のコードは if(rank==0) ループに行き、そこで「Testing X 0」を出力しますが、MPI_Send() 関数で事前にブレークするため、「Testing Y 0」を出力することはありません。この時点で変数 Cookie は 0 です。これは、GPU/デバイスが 1 つしかないため、SendRecvID() 関数が "(1,0,0)" を取るためです。MPI_Send の最初のパラメーターはポインターです。Device_Halo_etc はポインターの配列であり、データが送信される場所は (rank+1)=1 です。

同様に、ランク 1 コードは if(rank==NumberNodes-1) ループに移動し、MPI_Recv 呼び出しを完了する前にコードが停止するため、"Testing A 1" を出力しますが、"Testing B 1" を出力しません。私の知る限り、MPI_Recv のパラメータは正しいです。(rank-1)=0 が正しいので、送受信されるデータ ポイントの数は正しく、ID は同じです。

私がこれまでに試したことは、それぞれが正確に同じタグを持っていることを確認することです (ただし、SendRecvID() はそれぞれの場合 (1,0,0) を取るため、とにかく同じです) 999 程度を手書きで作成しましたが、これで変わりはない。また、両方の MPI 呼び出しで Device_Halo_etc パラメーターを &Device_Halo_etc に変更しました。念のため、そこでポインターを台無しにしましたが、違いはありません。これまでのところ動作させる唯一の方法は、MPI_Send/Recv() 呼び出しの Device_Halo_etc パラメーターをホスト上の任意の配列に変更して、転送するかどうかをテストすることです。そうすることで、最初の MPI 呼び出しを通過させることができます。もちろん、次のものに行き詰まりますが、それでも、Send/Recv の変数の数を 1 に変更した場合にのみ機能します (NumDataPts_f_halo==14250 ではなく)。そしてもちろん、ホスト アレイを移動することは重要ではありません。

追加のリンク変数を使用して nvcc コンパイラを使用してコードを実行します (メソッドをどこかにオンラインでコピーしたため、これらがどのように機能するかはよくわかりませんが、より単純なデバイスからデバイスへの MPI 呼び出しが機能していることを考えると、これで問題はないと思います)。 :

nvcc TestingMPI.cu -o run_Test -I/usr/lib/openmpi/include -I/usr/lib/openmpi/include/openmpi -L/usr/lib/openmpi/lib -lmpi_cxx -lmpi -ldl

そしてコンパイル:

mpirun -np 2 run_Test

そうすると、通常は次のようなエラーが表示されます。

Testing  A 1

Testing X 0
[Anastasia:16671] *** Process received signal ***
[Anastasia:16671] Signal: Segmentation fault (11)
[Anastasia:16671] Signal code: Invalid permissions (2)
[Anastasia:16671] Failing at address: 0x700140000
[Anastasia:16671] [ 0] /lib/x86_64-linux-gnu/libc.so.6(+0x364a0) [0x7f20327774a0]
[Anastasia:16671] [ 1] /lib/x86_64-linux-gnu/libc.so.6(+0x147fe5) [0x7f2032888fe5]
[Anastasia:16671] [ 2] /usr/lib/libmpi.so.1(opal_convertor_pack+0x14d) [0x7f20331303bd]
[Anastasia:16671] [ 3] /usr/lib/openmpi/lib/openmpi/mca_btl_sm.so(+0x20c8) [0x7f202cad20c8]
[Anastasia:16671] [ 4] /usr/lib/openmpi/lib/openmpi/mca_pml_ob1.so(+0x100f0) [0x7f202d9430f0]
[Anastasia:16671] [ 5] /usr/lib/openmpi/lib/openmpi/mca_pml_ob1.so(+0x772b) [0x7f202d93a72b]
[Anastasia:16671] [ 6] /usr/lib/libmpi.so.1(MPI_Send+0x17b) [0x7f20330bc57b]
[Anastasia:16671] [ 7] run_Test() [0x400ff7]
[Anastasia:16671] [ 8] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7f203276276d]
[Anastasia:16671] [ 9] run_Test() [0x400ce9]
[Anastasia:16671] *** End of error message ***
--------------------------------------------------------------------------
mpirun noticed that process rank 0 with PID 16671 on node Anastasia exited on signal 11 (Segmentation fault).
--------------------------------------------------------------------------

Linux Ubuntu 12.04LTSで実行されているデュアルGT650m NVIDIAグラフィックスカードを搭載したLenovo Y500であるラップトップ(アナスタシア)でコードを実行しています。nvcc --version「リリース 5.0、V0.2.1221」をmpirun --version提供し、「mpirun (Open MPI) 1.5.4」を提供します。

4

1 に答える 1

4

コードを支援してくれた Anycorn に感謝します。

同様の問題を抱えている人に興味がある場合は、ここでの私のエラーは、MPI 呼び出しを使用して CUDA メモリにアクセスできたかどうかを判断する際に発生したものであることが判明しました。MPI_Send/Recv() GPU メモリを実行できなかったため、「無効な権限」エラーが発生しました。誰かが同様の問題を抱えている場合は、上記の質問のコメント セクションで Anycorn が提案しているように、MPI_Send/Recv() 関数を使用してデバイス メモリを送信するための簡単なコードをテストすることをお勧めします。

デバイスメモリへのポインターではなく、デバイスメモリへのポインターへのポインターを誤って送信しないように注意してください (MPI_Send/Recv() 関数では、最初の引数としてポインターが必要です)。そのポインターを異なるノード間で送信しましたが、ポインターがホスト/CPU メモリ上にあるため、呼び出しは正常に機能しました。その結果、ノード 1 はノード 0 にポインターへのポインターを与えることになりました。ノード 1 から収集したと思われるデータを出力すると、新しく受け取ったポインターによってノード 0 でポイントされたデータを取得しました...これはずさんなコーディングで両方のノードで初期化した同じ配列を指しています(「if(node = = 1)初期化配列」行でそこに保存されていました)。したがって、正しい出力を受け取り、すべてがうまくいったと思いました。

ありがとうございました!

于 2013-08-08T18:48:26.160 に答える