Продукт точки CUDA

Я пытаюсь реализовать классическое ядро точечного продукта для массивов двойной точности с атомарным вычислением конечной суммы по различным блокам. Я использовал atomicAdd для двойной точности, как указано на странице 116 руководства по программированию.Возможно, я делаю что-то не так.Частичные суммы по потокам в каждом блоке вычисляются правильно, но после слов атомарная операция, похоже, работает неправильно, так как каждый раз,когда я запускаю свое ядро с теми же данными, я получаю разные результаты. Я буду благодарен, если кто-нибудь заметит ошибку или предоставит альтернативное решение! Вот мое ядро:

__global__ void cuda_dot_kernel(int *n,double *a, double *b, double *dot_res)
{
    __shared__ double cache[threadsPerBlock]; //thread shared memory
    int global_tid=threadIdx.x + blockIdx.x * blockDim.x;
    int i=0,cacheIndex=0;
    double temp = 0;
    cacheIndex = threadIdx.x;
    while (global_tid < (*n)) {
        temp += a[global_tid] * b[global_tid];
        global_tid += blockDim.x * gridDim.x;
    }
    cache[cacheIndex] = temp;
    __syncthreads();
    for (i=blockDim.x/2; i>0; i>>=1) {
        if (threadIdx.x < i) {
            cache[threadIdx.x] += cache[threadIdx.x + i];
        }
        __syncthreads();
    }
    __syncthreads();
    if (cacheIndex==0) {
        *dot_res=cuda_atomicAdd(dot_res,cache[0]);
    }
}

и вот моя функция устройства atomicAdd:

__device__ double cuda_atomicAdd(double *address, double val)
{
    double assumed,old=*address;
    do {
        assumed=old;
        old= __longlong_as_double(atomicCAS((unsigned long long int*)address,
                    __double_as_longlong(assumed),
                    __double_as_longlong(val+assumed)));
    }while (assumed!=old);

    return old;
}

3 ответов


вы используете cuda_atomicAdd неправильно. Этот раздел вашего ядра:

if (cacheIndex==0) {
    *dot_res=cuda_atomicAdd(dot_res,cache[0]);
}

виновник. Здесь вы атомарно добавляете к dot_res. тогда не атомарно set dot_res С результатом он возвращается. Возвращаемым результатом этой функции является предыдущим значением атомарно обновляемого местоположения, и он поставляется только для" информации " или локального использования вызывающего абонента. Вы не назначаете его тому, что вы атомарно обновлены, что полностью побеждает цель использования атомарного доступа к памяти в первую очередь. Сделайте что-нибудь вроде этого:

if (cacheIndex==0) {
    double result=cuda_atomicAdd(dot_res,cache[0]);
}

получение права сокращения с помощью специального кода CUDA может быть сложным, поэтому вот альтернативное решение с использованием алгоритма тяги, который входит в набор инструментов CUDA:

#include <thrust/inner_product.h>
#include <thrust/device_ptr.h>

double do_dot_product(int *n, double *a, double *b)
{
  // wrap raw pointers to device memory with device_ptr
  thrust::device_ptr<double> d_a(a), d_b(b);

  // inner_product implements a mathematical dot product
  return thrust::inner_product(d_a, d_a + n, d_b, 0.0);
}

Не проверять ваш код, но вот несколько советов.
Я бы только советовал использовать Thrust, если вы используете свой GPU только для таких общих задач, так как если возникнет сложная проблема, люди не знают, как эффективно программировать параллельно на gpu.

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

  2. ваше ядро, похоже, не масштабируется по максимальному количеству возможных блоков на новейшем GPU. Если бы это было так, и ваше ядро смогло бы вычислить точечное произведение миллионов значений, производительность резко снизилась бы из-за сериализованной атомной операции.

  3. ошибка новичка: ваши входные данные и доступ к общей памяти