Числовая ошибка в простом ядре cuda/cublas с использованием определенного ввода

Я работаю с cuda и cubas, и я пытался реализовать простые операции, такие как матричное поэлементное умножение/деление. Я использую только float для своих экспериментов. Я знаю, что самый очевидный способ сделать это — написать такое ядро:

__global__ void mul_elementwise(const unsigned int n, float* source, float* dest, const float value)
{
    const unsigned int offset = blockIdx.x * blockDim.x + threadIdx.x;
    const unsigned int stride = blockDim.x * gridDim.x;
    for (unsigned int i = offset; i < n; i += stride)
    {
        dest[i] = source[i] * value;
    }
}

Это ядро ​​может работать как для умножения, так и для деления (просто используя 1/x в качестве значения). Но этого можно добиться и с помощью библиотеки cublas: предположим, у нас есть матрица A mxn, хранящаяся в столбцовом стиле, и скаляр x, затем установите alpha = x или alpha = 1/x и d_ones в качестве вектора m*n 1s, мы можем вызвать и получить тот же результат

cublasSaxpy(cublas_handle, m * n, &alpha, d_ones, 1, A_dev, 1);

Оба метода прекрасно работают, но я столкнулся с несколькими проблемами с какой-то конкретной матрицей, для которой оба метода не работают. Я изолировал эту большую матрицу и построил MCVE, доступный здесь (вы можете скомпилировать его с помощью nvcc mcve.cu -lcublas. Как вы видите результаты в обоих случаях абсолютно неверны: результат хоста совершенно другой, я пытаюсь понять, что происходит. Я не вижу никаких ошибок в коде, но, возможно, мне следует попробовать использовать double вместо float и посмотреть, что произойдет. Любые мнения о эта ситуация? Заранее спасибо!

РЕДАКТИРОВАТЬ №1 Я пробовал использовать двойники, но ничего не меняется, если я использую cublasDaxpy, в то время как он отлично работает с пользовательским ядром. Я думаю, что значения слишком малы, поэтому одной точности с плавающей запятой недостаточно.


person gr1ph00n    schedule 16.04.2015    source источник


Ответы (1)


Интересный MCVE. Разве нельзя было сократить ваш вектор до нескольких элементов? Разве нельзя показать расхождение вычислений только на основе 1 элемента вектора?

В любом случае я вижу несколько проблем.

  1. В вашем ядре реализована следующая функция: y=alpha*x. Но SAXPY реализует y=alpha*x+y. Теперь, если бы y начиналось как (все) нули, то эти два были бы одинаковыми. Но это не то, что у вас есть:

             CUBLAS    Your Kernel
             ---------------------------
    alpha:   alpha     alpha
        x:       1     ahost    (ahost is  your huge data array)
        y:   ahost         -
    

    Итак, ваше ядро ​​вычисляет y=alpha * ahost, но ваш вызов CUBLAS вычисляет y = alpha*1 + ahost. Я бы не ожидал такого же результата от них, в общем.

  2. Ваш анализ ошибок кажется ошибочным в нескольких отношениях. Сначала вы вычисляете абсолютную ошибку в переменной float (число, которое всегда будет положительным, поскольку это абсолютное значение), а затем сравниваете его с отрицательным числом:

                    float diff = abs(host[i]-dev[i]);
                    ...
                    if (diff > (-1e12))
    

    не будет ли этот if тест всегда верным? Возможно, вы имели в виду 1e-12, хотя это все равно будет ошибкой. Поиск фиксированного порога ошибки при сравнении с плавающей запятой должен масштабироваться до размера сравниваемых чисел. float количества содержат только около 6-7 точных десятичных цифр. (И суммирование этих ошибок также проблематично.)

Вот полный код, в котором устранены вышеуказанные проблемы и выдается ошибка с нулевой суммой для всех сравнений (host‹->kernel и host‹->cublas):

static float array[] = {0x00000000,
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xB58DA1CF,0xB50D2FEC,0x34A48536,0xB4A1D5BC,0x358E1345,0x35943AAC,0xB5983F40,0xB43628BB,0xB4A95348,0xB4DB751C,0xB50C8D1A,0xB3EFCBB5,0x3552B8CD,0x3538A167,0x358FDE0D,0xB4D54CE9,0xB5D29BB7,0xB4A234EE,0x346EF2F4,0x35B5D9F2,0xB40F1487,0x3554BC20,0x33FD9466,0xB536D37D,0xB3C2E594,0xB59DA581,0x3584FC87,0x34438F09,0x35D293CB,0xB4FBB002,0xB59F41E9};

#include <iostream>
#include <stdio.h>
#include <cublas_v2.h>
#include <assert.h>

#define TOL 0.0001

typedef unsigned int u32;

#define GET_STRIDE() u32(blockDim.x * gridDim.x)
#define GET_OFFSET() u32(blockIdx.x * blockDim.x + threadIdx.x)

inline
cudaError_t checkCuda(cudaError_t result)
{
#if defined(DEBUG) || defined(_DEBUG)
  if (result != cudaSuccess) {
    fprintf(stderr, "CUDA Runtime Error: %s\n", cudaGetErrorString(result));
    assert(result == cudaSuccess);
  }
#endif
  return result;
}

__global__ void div_elementwise(const u32 n, float* source, float* dest, const float value)
{
        for (u32 i = GET_OFFSET(); i < n; i += GET_STRIDE())
        {
                dest[i] = source[i] * value;
        }
}

float check_eq(float* dev, float* host, u32 len)
{
        float sum = 0.0f;
        for (u32 i = 0; i < len; ++i)
        {
                if (dev[i]!=host[i])
                {
                        //printf("diff %d %f %f\n", i, dev[i], host[i]);
                        //break;
                        float diff = abs((host[i]-dev[i])/host[i]);
                        sum += diff;
                        if (diff > (TOL))
                                printf("diff %d %f\n", i, diff);
                }
        }
        printf("%f\n", sum);
        return sum;
}

void div_host(float* a, float v, u32 len)
{
        for (u32 i = 0; i < len; ++i)
        {
                a[i]=a[i]*v;
        }
}

int main()
{
        u32 len = sizeof(array)/sizeof(float);
        printf("array len = %d\n", len);
        for (int i =0; i < len; i++) if (isnan(array[i])) {printf("nan value at %d\n",i); return -1;}
        float* adev, *adevcublas, *d_zero;
        float* ahost = (float*) malloc(len * sizeof(float));

        checkCuda(cudaMalloc(&adev, len * sizeof(float)));
        checkCuda(cudaMalloc(&adevcublas, len * sizeof(float)));
        checkCuda(cudaMalloc(&d_zero, len * sizeof(float)));

        memcpy(ahost, &array[0], len * sizeof(float));
        checkCuda(cudaMemcpy(adev, ahost, len * sizeof(float), cudaMemcpyHostToDevice));
        checkCuda(cudaMemcpy(adevcublas, ahost, len * sizeof(float), cudaMemcpyHostToDevice));
        checkCuda(cudaMemset(d_zero, 0, len*sizeof(float)));

        float alpha = 1/2494.f;

        printf("%f\n", alpha);

        div_host(ahost, alpha, len);
        u32 tb = 256;
        div_elementwise<<<((len + tb - 1) / tb),tb>>>(len, adev, adev, alpha);

        float* r = (float*) malloc(len * sizeof(float));

        checkCuda(cudaMemcpy(r, adev, len * sizeof(float), cudaMemcpyDeviceToHost));

        check_eq(r,ahost,len);


        cublasHandle_t ch;
        cublasCreate(&ch);

        float* r0 = (float*) malloc(len * sizeof(float));

        cublasStatus_t stat = cublasSaxpy(ch, len, &alpha, adevcublas, 1, d_zero, 1);
        if (stat != CUBLAS_STATUS_SUCCESS) {std::cout << "CUBLAS error: " << (int)stat << std::endl; return 1;}

        checkCuda(cudaMemcpy(r0, d_zero, len * sizeof(float), cudaMemcpyDeviceToHost));


        check_eq(r0,ahost,len);

        free(r);
        free(r0);
        free(ahost);
        cudaFree(adev);

        return 0;
}
person Robert Crovella    schedule 16.04.2015