Проблемы с выделением и доступом к памяти CUDA

Я работаю над изучением CUDA прямо сейчас. У меня есть некоторый базовый опыт работы с MPI, поэтому я решил начать с нескольких действительно простых векторных операций. Я пытаюсь написать параллельный точечный продукт. У меня либо возникают проблемы с выделением/записью памяти на устройство CUDA, либо я неправильно возвращаю ее на хост (cudaMemcpy()).

     /*Code for a CUDA test project doing a basic dot product with doubles
     *
     *
     *
     */
      #include <stdio.h>
      #include <cuda.h>

      __global__ void GPU_parallelDotProduct(double *array_a, double *array_b, double          *dot){
          dot[0] += array_a[threadIdx.x] * array_b[threadIdx.x];
      }

     __global__ void GPU_parallelSetupVector(double *vector, int dim, int incrSize,          int start){
             if(threadIdx.x<dim){
                vector[threadIdx.x] = start + threadIdx.x * incrSize;
            }
     }

     __host__ void CPU_serialDot(double *first, double *second, double *dot, int dim){
          for(int i=0; i<dim; ++i){
             dot[0] += first[i] * second[i];
         }
      }

     __host__ void CPU_serialSetupVector(double *vector, int dim, int incrSize, int          start){
          for(int i=0; i<dim; ++i){
             vector[i] = start + i * incrSize;
         }
      }

      int main(){
     //define array size to be used
         //int i,j;
         int VECTOR_LENGTH = 8;
         int ELEMENT_SIZE  = sizeof(double);
         //arrays for dot product
         //host
         double *array_a  = (double*) malloc(VECTOR_LENGTH * ELEMENT_SIZE);
         double *array_b  = (double*) malloc(VECTOR_LENGTH * ELEMENT_SIZE);
         double *dev_dot_product = (double*) malloc(ELEMENT_SIZE);
     double host_dot_product = 0.0;

     //fill with values
         CPU_serialSetupVector(array_a, VECTOR_LENGTH, 1, 0);
     CPU_serialSetupVector(array_b, VECTOR_LENGTH, 1, 0);
     //host dot
     CPU_serialDot(array_a, array_b, &host_dot_product, VECTOR_LENGTH);

     //device
     double *dev_array_a;
     double *dev_array_b;
         double *dev_dot;

     //allocate cuda memory
     cudaMalloc((void**)&dev_array_a, ELEMENT_SIZE * VECTOR_LENGTH);
     cudaMalloc((void**)&dev_array_b, ELEMENT_SIZE * VECTOR_LENGTH);
     cudaMalloc((void**)&dev_dot,     ELEMENT_SIZE);

     //copy to from host to device
     cudaMemcpy(dev_array_a, array_a, ELEMENT_SIZE * VECTOR_LENGTH, cudaMemcpyHostToDevice);
     cudaMemcpy(dev_array_b, array_b, ELEMENT_SIZE * VECTOR_LENGTH, cudaMemcpyHostToDevice);
     cudaMemcpy(dev_dot, &dev_dot_product, ELEMENT_SIZE, cudaMemcpyHostToDevice);

     //init vectors
     //GPU_parallelSetupVector<<<1, VECTOR_LENGTH>>>(dev_array_a, VECTOR_LENGTH, 1, 0);
     //GPU_parallelSetupVector<<<1, VECTOR_LENGTH>>>(dev_array_b, VECTOR_LENGTH, 1, 0);
     //GPU_parallelSetupVector<<<1, 1>>>(dev_dot, VECTOR_LENGTH, 0, 0);
     //perform CUDA dot product
     GPU_parallelDotProduct<<<1, VECTOR_LENGTH>>>(dev_array_a, dev_array_b, dev_dot);

    //get computed product back to the machine
    cudaMemcpy(dev_dot, dev_dot_product, ELEMENT_SIZE, cudaMemcpyDeviceToHost);

     FILE *output = fopen("test_dotProduct_1.txt", "w");
     fprintf(output, "HOST CALCULATION: %f \n", host_dot_product);
     fprintf(output, "DEV  CALCULATION: %f \n", dev_dot_product[0]);
     fprintf(output, "PRINTING DEV ARRAY VALS: ARRAY A\n");
     for(int i=0; i<VECTOR_LENGTH; ++i){
         fprintf(output, "value %i: %f\n", i, dev_array_a[i]);
     }

     free(array_a);
     free(array_b);
     cudaFree(dev_array_a);
         cudaFree(dev_array_b);
     cudaFree(dev_dot);

     return(0);
     }   

Вот пример вывода:

    HOST CALCULATION: 140.000000 
    DEV  CALCULATION: 0.000000 
    PRINTING DEV ARRAY VALS: ARRAY A
    value 0: -0.000000
    value 1: 387096841637590350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
    value 2: -9188929998371095800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
    value 3: 242247762331550610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
    value 4: -5628111589595087500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
    value 5: 395077289052074410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
    value 6: 0.000000
    value 7: -13925691551991564000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000

person Joe    schedule 18.01.2012    source источник


Ответы (2)


Я вижу две проблемы:

  1. Ваш точечный продукт GPU содержит гонку памяти здесь:

     dot[0] += array_a[threadIdx.x] * array_b[threadIdx.x];
    

    Это небезопасно — каждый поток в блоке будет пытаться записать/перезаписать одну и ту же ячейку памяти своим результатом. Модель программирования не дает никаких гарантий относительно того, что произойдет в случае, когда несколько потоков попытаются записать разные значения в один и тот же участок памяти.

  2. Ваш код пытается получить прямой доступ к ячейке памяти устройства на хосте, когда вы распечатываете вектор. Я удивлен, что код не выдает ошибку segfault или защиты. dev_array_a не доступен хосту напрямую, это указатель в памяти графического процессора. Вы должны использовать устройство для размещения копии в действительном расположении хоста, если хотите изучить содержимое dev_array_a.

Предложение о проверке ошибок, сделанное в другом ответе, также является очень хорошим моментом. Каждый вызов API возвращает статус, и вы должны проверять статус всех вызовов, которые вы делаете, чтобы убедиться, что во время выполнения не возникает никаких ошибок или сбоев.

person talonmies    schedule 18.01.2012
comment
Да, я понимаю это сейчас. Я должен быть более осторожным. Я думал, что это может быть проблемой. Есть ли что-то вроде MPI_Reduce() для CUDA? Или было бы лучше записать каждое значение в третий массив, а затем сжать этот третий массив? Теперь мне интересно, будет ли это даже быстрее, теперь я вернулся в линейное время. - person Joe; 18.01.2012
comment
SDK содержит действительно полезный пример сокращения и технический документ, на который стоит обратить внимание. В качестве альтернативы, библиотека шаблонов Thrust, которая поставляется с последними версиями набора инструментов CUDA, имеет реализацию параллельного сокращения на C++ с работой над STL-подобным векторным классом, который скрывает большую часть управления памятью устройства и сокращает ваш пример примерно до дюжины строк. кода. - person talonmies; 18.01.2012

Рекомендуется проверять статус вызовов времени выполнения CUDA, таких как cudaMalloc, cudaMemcpy и запуска ядра. Вы можете сделать следующее после каждого такого вызова или обернуть это в какой-то макрос и обернуть вызовы времени выполнения CUDA в макрос.

if (cudaSuccess != cudaGetLastError())
    printf( "Error!\n" );

Теперь я не уверен, что это ваша проблема, но это может убрать очевидное.

person keveman    schedule 18.01.2012
comment
Я реализовал код, который вы разместили. Он вызывается при каждом вызове CUDA. Есть ли что-то, что мне не хватает при настройке CUDA или моей карты? - person Joe; 19.01.2012
comment
Какие версии драйвера и компилятора CUDA вы используете? Всегда рекомендуется получать самые последние версии с сайта developer.nvidia.com/cuda-downloads - person keveman; 19.01.2012