병렬기법
홈 > CUDA > 병렬기법 > MPI+CUDA

MPI+CUDA

미루웨어는 현재 GPU 클러스터 환경에서 MPI + CUDA를 이용한 병렬 프로그래밍을 지원해 드리고 있습니다. 궁금한 점이 있으시면 메일로 연락주시기 바랍니다.

 

일단, MPI와 CUDA의 병렬 프로그래밍을 하기 위해서는 다음의 checklist를 살펴보시기 바랍니다.

1. 기존의 CPU cluster 환경에 대해 익숙하다

2. MPI 프로그래밍을 통한 병렬화에 익숙하다

3. MPI 관련 헤더, 라이브러리 관련 컴파일 환경에 익숙하다

4. CUDA 프로그래밍에 익숙하다

5. CUDA 컴파일 관련 NVCC, 헤더, 라이브러리 환경에 익숙하다

 

 

위의 5가지에 익숙하다면 큰 어려움 없이 MPI + CUDA 병렬화를 프로그래밍을 하실 수 있습니다.

1.에 대해 익숙하지 않으시다면 Tesla Cluster 구축 Linux Cluster 파트를 참고하시기 바랍니다.

 

일단, MPI + CUDA의 컴파일은 크게 2가지 방법으로 나뉩니다.

C컴파일러는 .cu파일을 인식할 수 없지만 NVCC 는 MPI 라이브러리를 인식할 수 있기 때문에,

MPI는 C 컴파일러를 사용하고 CUDA는  NVCC컴파일러를 사용하는 방법입니다. 혹은 MPI와 CUDA 파트 모두 NVCC 컴파일러를 사용하는 방법입니다. MPI파트만 C컴파일러를 사용하는 방법의 장점은 mpicc 등의 기존 사용하던 환경을 그대로 이용할 수 있습니다. NVCC를 사용하시려면, 기존에 MPI 컴파일시 고민하지 않았던 헤더파일과 라이브러리의 위치를 잡아줘야 합니다.

 

실제 컴파일 명령은 다음과 같습니다.

nvcc -c mpi_gpu.cu

mpicc -o mpicuda mpi_gpu.o mpi_cpu.c  -lcudart -L/usr/local/cuda/lib -I/usr/local/cuda/include

 

물론, 이때 library관련 링크 등은 모두 정의 되야 합니다.

대부분 64비트 linux를 사용하는 경우가 많으므로

mpicc -o mpicuda mpi_gpu.o mpi_cpu.c  -lcudart -L/usr/local/cuda/lib64 -I/usr/local/cuda/include

 

명령 실행은 mpirun을 실행해주면 됩니다.

 


주의사항 :

만약 8코어 네할렘을 사용하는데, GPU가 4개씩 연결될 경우 host.list를 적절히 조절해주셔야 합니니다. 즉,  8개의 GPU를 2 노느가 가지고 있으므로 -np 8 명령을 통해 mpirun을 실행한 경우 4개의 GPU가 연결된 node001에서만 작업이 할당될 수 있습니다. 따라서 hostlist를 4개 node001, 4개 node002로 잡아주신 후 -np 8 로 잡업을 해주셔야 합니다.

 

 

다음은 일리노이대학에 제공한 MPI + CUDA의 가장 간단한 예제입니다.

 

 

표준적인 MPI 명령을 그대로 사용합니다. 단, MPI Loop 내에 CUDA를 사용하는 subroutine을 호출하여 사용하게 됩니다. 단, 메모리 제어, 값복사, CUDA kernel launch 등의 모든 부분이 subroutine 안에 내장되어 있습니다.

 

또한, 컴파일러 설정과 MPIrun 등은 표준 MPI기법을 따르고, .cu파일만 NVCC로 컴파일 하면 됩니다.

 

  #include

  #include

  #include

  #define PPN 4

  #define INTARRAYLEN 65535

  #define BCASTREPS 1000

 

int main(int argc, char  *argv[])   {

          int bcastme[INTARRAYLEN], ranksum;

          int rank, size, len;

          int gpudevice;

          int vecadd(int, int);

          char name[MPI_MAX_PROCESSOR_NAME];        

 

          MPI_Init(&argc, &argv);

          MPI_Comm_rank(MPI_COMM_WORLD,  &rank);

          MPI_Comm_size(MPI_COMM_WORLD,  &size);

          MPI_Get_processor_name(name, &len);      // do some MPI work, showing MPI and CUDA being run from one  routine

          if (rank == 0) {

                 bcastme[3]=3;

          }

          for (int i=0; i   {

                 MPI_Bcast(bcastme, INTARRAYLEN, MPI_INT, 0,  MPI_COMM_WORLD);

          }      // modulo is useful in determining unique gpu device ids if  ranks

          // are packed into nodes and not assigned in round robin  fashion

         

          gpudevice= rank % PPN;  

          printf("rank %d of %d on %s received bcastme[3]=%d [gpu  %d]\n", rank, size, name,bcastme[3], gpudevice);     

 

          vecadd(gpudevice, rank);      // more MPI work showing MPI is functional after CUDA

 

          MPI_Reduce(&rank, &ranksum, 1,  MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

          if (rank == 0) {

                 printf("ranksum= %d\n", ranksum);

          }     

          MPI_Finalize();

  }

 

 

 

 main()에서 호출하는 vecadd_host 함수와  vecAdd 함수를 정의합니다.

 

#include

#define SIZE 10

#define KERNELINVOKES  5000000

 

  __global__ void  vecAdd(float* A, float* B, float* C)   {

          int i = threadIdx.x;

          A[i]=0;

          B[i]=i;

          C[i] = A[i] + B[i];

  }

 

int vecadd_host(int  gpudevice, int rank) {

          int devcheck(int, int);

          devcheck(gpudevice, rank);    

          float A[SIZE], B[SIZE], C[SIZE];   // Kernel invocation     float *devPtrA;

          float *devPtrB;

          float *devPtrC;

          int memsize= SIZE * sizeof(float);    

          cudaMalloc((void**)&devPtrA, memsize);

          cudaMalloc((void**)&devPtrB, memsize);

          cudaMalloc((void**)&devPtrC, memsize);

          cudaMemcpy(devPtrA, A, memsize,  cudaMemcpyHostToDevice);

          cudaMemcpy(devPtrB, B, memsize,  cudaMemcpyHostToDevice);

 

          for (int i=0; i  {

                 vecAdd<<<1,  gpudevice>>>(devPtrA, devPtrB, devPtrC);

          }

          cudaMemcpy(C, devPtrC, memsize,  cudaMemcpyDeviceToHost);  

 

          for (int i=0; i"rank %d: C[%d]=%f\n",rank,i,C[i]);  

          cudaFree(devPtrA);

          cudaFree(devPtrA);

          cudaFree(devPtrA);

 

}

int devcheck(int  gpudevice, int rank) {

          int device_count=0;

          int device;   // used with cudaGetDevice() to verify cudaSetDevice()

          cudaGetDeviceCount( &device_count);

          if (gpudevice >= device_count)        {

                       printf("gpudevice >=  device_count ... exiting\n");

                       exit(1);

          }

          cudaError_t cudareturn;

          cudaDeviceProp deviceProp;

          cudaGetDeviceProperties(&deviceProp,  gpudevice);

 

         if (deviceProp.warpSize <= 1)   {

               printf("rank %d: warning, CUDA Device Emulation (CPU)  detected, exiting\n", rank);

               exit(1);

         }

          cudareturn=cudaSetDevice(gpudevice);

         if (cudareturn == cudaErrorInvalidDevice)   {

               perror("cudaSetDevice returned  cudaErrorInvalidDevice");

         }  else  {

               cudaGetDevice(&device);

               printf("rank %d:  cudaGetDevice()=%d\n",rank,device);

         }

}

 

보다 자세한 내용은 원본 사이트를 확인하시기 바랍니다. 

http://www.ncsa.illinois.edu/UserInfo/Training/Workshops/CUDA/presentations/tutorial-CUDA.html