6

我正在尝试 MPI 一个用于格子 Boltzmann 建模的 CUDA 代码,并且遇到了 MPI_Send 和 MPI_Recv 函数的令人沮丧的问题。我已经验证我有 CUDA-aware MPI 和一些简单的设备缓冲区到设备缓冲区 MPI 发送/接收代码,所以我可以在 GPU 设备内存之间发送和接收数组,而无需通过 CPU/主机。

我的代码是一个 3D 晶格,它在各个节点之间沿 z 方向划分,在节点之间传递光晕以确保流体可以在这些划分之间流动。Halos 在 GPU 上。下面的代码是一个简化并编译给出与我的主代码相同的错误。这里,Rank 0 节点上的 GPU Halo 是 MPI_Send() 到 rank 1 节点,MPI_Recv() 发送它。目前我的问题似乎很简单,我无法让 MPI_Send 和 MPI_Recv 调用函数!代码未进展到“//代码未到达此处”。行,使我得出结论 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 变量),所以我有一个作为 rank==0,另一个作为 rank==1==NumberNodes-1。rank 0 代码进入 if(rank==0) 循环,在该循环中它输出“Testing X 0”,但永远不会输出“Testing Y 0”,因为它事先在 MPI_Send() 函数上中断。此时变量 Cookie 为 0,因为只有一个 GPU/设备,因此 SendRecvID() 函数采用“(1,0,0)”。MPI_Send 的第一个参数是一个指针,因为 Device_Halo_etc 是一个指针数组,而数据发送到的位置是 (rank+1)=1。

同样,排名 1 的代码进入 if(rank==NumberNodes-1) 循环,在该循环中输出“Testing A 1”而不是“Testing B 1”,因为代码在完成 MPI_Recv 调用之前停止。据我所知,MPI_Recv的参数是正确的,因为(rank-1)=0是正确的,发送和接收的数据点数是正确的,ID是一样的。

到目前为止,我尝试的是通过手写 999 左右来确保它们每个都具有完全相同的标签(尽管 SendRecvID() 在每种情况下都需要 (1,0,0) 所以无论如何都是一样的),但这使得没有不同。我还在两个 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).
--------------------------------------------------------------------------

我在我的笔记本电脑 (Anastasia) 上运行代码,这是一台带有双 GT650m NVIDIA 显卡的联想 Y500,在 Linux Ubuntu 12.04LTS 上运行,如果有区别的话。nvcc --version给出“release 5.0, V0.2.1221”,并mpirun --version给出“mpirun (Open MPI) 1.5.4”。

4

1 回答 1

4

感谢 Anycorn 对代码的帮助!

如果它对任何有类似问题的人感兴趣,我的错误是在确定我是否能够使用 MPI 调用访问 CUDA 内存。我无法 MPI_Send/Recv() GPU 内存,因此出现“无效权限”错误。如果有人有类似的问题,我建议您使用 MPI_Send/Recv() 函数测试一个简单的代码来发送设备内存,正如 Anycorn 在上述问题的评论部分所建议的那样。

请注意意外发送指向设备内存指针而不是设备内存指针的指针(在 MPI_Send/Recv() 函数中需要一个指针,它采用的第一个参数)。我已经在不同节点之间发送了该指针,并且由于指针位于主机/CPU 内存上,因此调用工作正常。结果是节点 1 会给节点 0 指向一个指针的指针 - 当我输出我认为从节点 1 收集的数据时,我得到了新收到的指针指向节点 0 的数据......这是指向我通过草率编码在两个节点上初始化的同一个数组(“if(node==1)初始化数组”行将我保存在那里)。因此,我收到了正确的输出,并认为一切都很好。

再次感谢安康!

于 2013-08-08T18:48:26.160 回答