CUDA/CUBLAS Матрично-векторное умножение

Ранее я задавал вопрос об умножении матрицы на вектор в CUDA и о написании собственного ядра. После этого я решил реализовать свою проблему с помощью CUBLAS, как было предложено некоторыми пользователями (спасибо @Robert Crovella) на SO в надежде на достижение более высокой производительности (мой проект ориентирован на производительность).

Просто для уточнения: я хочу умножить матрицу NxN на вектор 1xN.

Я уже несколько дней смотрю на код, вставленный ниже, и не могу понять, почему умножение дает мне неправильный результат. Я боюсь, что я создаю проблемы, используя массивы ‹ vector> (это часть гораздо большей системы, которая использует эти типы данных). Я не собираюсь использовать эту ветку в качестве инструмента отладки, но я думаю, что это также будет полезно для других пользователей, пытающихся достичь этого, поскольку я не нашел в Интернете особенно исчерпывающего источника для моей конкретной проблемы (и для cublas v2 API). Заранее спасибо!

#include <cuda.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <cublas_v2.h>
#include <time.h>

//#include "timenow.cu"

// error check macros
#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

// for CUBLAS V2 API
#define cublasCheckErrors(fn) \
    do { \
        cublasStatus_t __err = fn; \
        if (__err != CUBLAS_STATUS_SUCCESS) { \
            fprintf(stderr, "Fatal cublas error: %d (at %s:%d)\n", \
                (int)(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

// random data filler
void fillvector(float *data, int N){
    for(int i=0; i<N; i++){
        data[i] = float(rand() % 10);
    }
}

//printer
void printer(bool printOut, float *data, int N){
    if(printOut == true){
    for(int i=0; i<N; i++){
        printf("%2.1f ", data[i]);
    }
    printf("\n");
    }
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

int main(){

bool printOut = true;

int N;
std::cout << "Enter N: " ;
std::cin >> N;

std::vector<float> x0;
x0.resize(N);

std::vector<float> p;
p.resize(N*N);

// matrix A
std::vector<float> A[N];
for(int i=0;i<N;i++){
        A[i].resize(N);
    fillvector(A[i].data(), N);
    printer(printOut, A[i].data(), N);
}
printf("\n");
fillvector(x0.data(), N);
printer(printOut, x0.data(), N);

printf("\nStarting CUDA computation...");
///double startTime = timenow();

// device pointers
float *d_A, *d_p, *d_b, *d_x0, *d_v, *d_temp;

cudaMalloc((void**)&d_A, N*N*sizeof(float));
cudaMalloc((void**)&d_temp, N*sizeof(float));
cudaMalloc((void**)&d_x0, N*sizeof(float));
cudaCheckErrors("cuda malloc fail");

// might need to flatten A...
cublasSetVector(N, sizeof(float), &x0, 1, d_x0, 1);
//daMemcpy(d_x0, &x0, N*sizeof(float), cudaMemcpyHostToDevice);
cublasSetMatrix(N, N, sizeof(float), &A, N, d_A, N);
cudaCheckErrors("cuda memcpy of A or x0 fail");

float *temp;
temp = (float *)malloc(N*sizeof(temp));

cublasHandle_t handle;
cublasCheckErrors(cublasCreate(&handle));

float alpha = 1.0f;
float beta = 0.0f;
cublasCheckErrors(cublasSgemv(handle, CUBLAS_OP_N, N, N, &alpha, d_A, N, d_x0, 1, &beta, d_temp, 1));

cublasGetVector(N, sizeof(float), &temp, 1, d_temp, 1);
//cudaMemcpy(temp, d_temp, N*sizeof(float), cudaMemcpyDeviceToHost);
cudaCheckErrors("returning to host failed");

printf("\n");
printer(printOut, temp, N);

/*alpha = -1.0;
cublasSaxpy(handle, N, &alpha, d_temp, 1, d_v, 1);
cublasGetVector(N, sizeof(float) * N, d_v, 1, &v, 1);
printf("\n");
for(int i=0; i<N; i++){
    printf("%2.1f ",v[i]);
}*/

printf("\nFinished CUDA computations...");
//double endTime = timenow();

//double timeDiff = endTime - startTime;
//printf("\nRuntime: %2.3f seconds \n", timeDiff);

cudaFree(d_temp);
cudaFree(d_A);
cudaFree(d_p);
cudaFree(d_x0);

return 0;
} 

person rex    schedule 29.08.2013    source источник
comment
Какой у Вас вопрос? Если дело в том, что ваше умножение дает неправильный результат, пожалуйста, дайте нам представление о результатах, которые вы получаете, и о результатах, которые вы ожидаете. Кроме того, вы не выполняете проверку ошибок cublas при вызовах get/set matrix/vector.   -  person Robert Crovella    schedule 29.08.2013
comment
Ваша программа, похоже, допускает любой ввод. Когда я ввожу N = 2, я получаю сообщение об ошибке cuda о недопустимом аргументе при вашем возвращении на сообщение о сбое хоста. В чем именно заключается ваш вопрос?   -  person Robert Crovella    schedule 29.08.2013
comment
Привет, Роберт, я хотел бы умножить квадратную матрицу NxN на вектор 1xN. N должен быть любого размера.   -  person rex    schedule 29.08.2013
comment
Если я установлю N = 5, скажем, я получу следующий вывод для temp: До: 0,0 2,0 3,0 7,0 5,0 После: 0,0 0,0 0,0 0,0 -158953476882379259956604960768.0   -  person rex    schedule 29.08.2013
comment
Даже если я передам N=5 вашему коду, он вернет ошибку с сообщением о сбое возврата на хост. Это код, который вы используете? Пожалуйста, вставьте фактический вывод программы при ее запуске в вопрос (вы можете отредактировать свой вопрос, не пытайтесь поместить его в комментарии).   -  person Robert Crovella    schedule 29.08.2013


Ответы (1)


  • Мы не ссылаемся на первый элемент вектора таким образом:

    cublasSetVector(N, sizeof(float), &x0, 1, d_x0,   
    

Вместо этого вы должны сделать это:

cublasSetVector(N, sizeof(float), &(x0[0]), 1, d_x0, 1);

Аналогично для вашего SetMatrix звонка, ссылающегося на A:

cublasSetMatrix(N, N, sizeof(float), &(A[0]), N, d_A, N);
  • В вашем вызове GetVector есть 2 ошибки:

    cublasGetVector(N, sizeof(float), &temp, 1, d_temp, 1);
    

У вас обратные параметры temp и d_temp (вы копируете с устройства на host) и адрес temp брать не стоит: это уже указатель. Итак, сделайте следующее:

cublasGetVector(N, sizeof(float), d_temp, 1, temp, 1);
  • Вы не выполняете правильную проверку ошибок для всех вызовов cublas, таких как вызовы get/set matrix/vector. Используйте тот же метод, который вы используете для других вызовов cublas.

  • Вы создаете A как массив векторов. Это не будет работать с cublasSetMatrix. Вместо этого нам нужно создать A в виде плоского вектора достаточного размера (N*N) для хранения всей матрицы.

  • Наконец, cublas ожидает, что используемые им матрицы будут храниться в порядке столбцов. Если вы передаете массивы в стиле C в порядке строк, вы должны использовать транспонирование для этой матрицы в cublasSgemv:

    cublasCheckErrors(cublasSgemv(handle, CUBLAS_OP_T, N, N, &alpha, d_A, N, d_x0, 1, &beta, d_temp, 1));
    

В следующем коде исправлены эти различные проблемы:

$ cat t235.cu
#include <cuda.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <cublas_v2.h>
#include <time.h>

//#include "timenow.cu"

// error check macros
#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

// for CUBLAS V2 API
#define cublasCheckErrors(fn) \
    do { \
        cublasStatus_t __err = fn; \
        if (__err != CUBLAS_STATUS_SUCCESS) { \
            fprintf(stderr, "Fatal cublas error: %d (at %s:%d)\n", \
                (int)(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

// random data filler
void fillvector(float *data, int N){
    for(int i=0; i<N; i++){
        data[i] = float(rand() % 10);
    }
}

//printer
void printer(bool printOut, float *data, int N){
    if(printOut == true){
    for(int i=0; i<N; i++){
        printf("%2.1f ", data[i]);
    }
    printf("\n");
    }
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

int main(){

bool printOut = true;

int N;
std::cout << "Enter N: " ;
std::cin >> N;

std::vector<float> x0;
x0.resize(N);

std::vector<float> p;
p.resize(N*N);

// matrix A
std::vector<float> A(N*N);
fillvector(A.data(), N*N);
for (int i=0; i< N; i++){
  printer(printOut, &(A[(i*N)]), N);
  printf("\n");}
fillvector(x0.data(), N);
printer(printOut, x0.data(), N);

printf("\nStarting CUDA computation...");
///double startTime = timenow();

// device pointers
float *d_A, *d_x0, *d_temp;

cudaMalloc((void**)&d_A, N*N*sizeof(float));
cudaMalloc((void**)&d_temp, N*sizeof(float));
cudaMalloc((void**)&d_x0, N*sizeof(float));
cudaCheckErrors("cuda malloc fail");

// might need to flatten A...
cublasCheckErrors(cublasSetVector(N, sizeof(float), &(x0[0]), 1, d_x0, 1));
//daMemcpy(d_x0, &x0, N*sizeof(float), cudaMemcpyHostToDevice);
cublasCheckErrors(cublasSetMatrix(N, N, sizeof(float), &(A[0]), N, d_A, N));
//cudaCheckErrors("cuda memcpy of A or x0 fail");

float *temp;
temp = (float *)malloc(N*sizeof(temp));

cublasHandle_t handle;
cublasCheckErrors(cublasCreate(&handle));

float alpha = 1.0f;
float beta = 0.0f;
cublasCheckErrors(cublasSgemv(handle, CUBLAS_OP_T, N, N, &alpha, d_A, N, d_x0, 1, &beta, d_temp, 1));

cublasCheckErrors(cublasGetVector(N, sizeof(float), d_temp, 1, temp, 1));
//cudaMemcpy(temp, d_temp, N*sizeof(float), cudaMemcpyDeviceToHost);
//cudaCheckErrors("returning to host failed");

printf("\n");
printer(printOut, temp, N);

/*alpha = -1.0;
cublasSaxpy(handle, N, &alpha, d_temp, 1, d_v, 1);
cublasGetVector(N, sizeof(float) * N, d_v, 1, &v, 1);
printf("\n");
for(int i=0; i<N; i++){
    printf("%2.1f ",v[i]);
}*/

printf("\nFinished CUDA computations...\n");
//double endTime = timenow();

//double timeDiff = endTime - startTime;
//printf("\nRuntime: %2.3f seconds \n", timeDiff);

cudaFree(d_temp);
cudaFree(d_A);
//cudaFree(d_p);
cudaFree(d_x0);

return 0;
}
$ nvcc -arch=sm_20 -O3 -o t235 t235.cu -lcublas
$ ./t235
Enter N: 5
3.0 6.0 7.0 5.0 3.0

5.0 6.0 2.0 9.0 1.0

2.0 7.0 0.0 9.0 3.0

6.0 0.0 6.0 2.0 6.0

1.0 8.0 7.0 9.0 2.0

0.0 2.0 3.0 7.0 5.0

Starting CUDA computation...
83.0 86.0 92.0 62.0 110.0

Finished CUDA computations...
$
person Robert Crovella    schedule 29.08.2013
comment
Большое спасибо, Роберт. Пункты, касающиеся того, как указывать/использовать массивы и их указатели, особенно полезны! - person rex; 30.08.2013
comment
Как бы вы сказали, что это справедливо по сравнению с параллельным вычислением процессора, использующим, скажем, 6 ядер? Я спрашиваю, потому что не совсем уверен, что мой модуль синхронизации работает правильно. Я смотрю на N около 1000. - person rex; 30.08.2013
comment
Меня также беспокоит, насколько сглаживание матрицы A повлияет на производительность? есть ли компромисс с точки зрения матричных манипуляций после? - person rex; 30.08.2013
comment
Ваш код, возможно, придется переписать, но нет причин, по которым он должен снижать производительность. - person Robert Crovella; 30.08.2013
comment
Привет, Роберт, можно ли использовать cublasSgemv() с двумя плоскими матрицами d_A и d_Q, которые находятся на устройстве, я хочу умножить A[N x N] на столбец из Q[N x (0,5N+1)] ? - person rex; 02.09.2013
comment
Вероятно, вам следует опубликовать это как новый вопрос SO. - person Robert Crovella; 02.09.2013
comment
Привет, Роберт, я до сих пор не совсем понимаю, почему мы используем функцию cublasSetMatrix(). Поскольку мы просто передавали одномерный массив (то есть сплющенную матрицу), не можем ли мы просто использовать cublasSetVector() или даже cudaMemcpy()? - person rex; 05.09.2013
comment
Да, вы также можете использовать любой из них. - person Robert Crovella; 05.09.2013
comment
Есть ли разница в том, для чего они используются? Означает ли использование cublasSetMatrix(), что я могу избежать выравнивания матриц? Иначе есть ли в них смысл? - person rex; 05.09.2013
comment
Да, есть отличия. Возможно, вам следует прочитать документацию. Все 3 подхода предполагают использование одиночных указателей, поэтому в каждом случае по-прежнему используется сглаживание. SetMatrix понимает, что базовые данные представляют собой 2D-матрицу, и поэтому позволяет вам делать такие вещи, как, например, передача частичного подмножества матрицы вместо всей матрицы. Я не могу осветить все это в комментариях, пожалуйста, задавайте новые вопросы. Я не собираюсь отвечать на какие-либо новые вопросы, заданные в этой области комментариев. - person Robert Crovella; 05.09.2013