cudaMallocManaged统一内存与 cuBLAS

问题描述

我正在尝试将统一内存与 cudamallocManaged() 与 cuBLAS 库一起使用。我正在执行一个简单的矩阵向量乘法作为一个简单的例子,并将结果存储在一个数组 results 中。但是,在打印 results 数组时,我返回全 0,而不是将矩阵 mat 乘以向量 vec 的结果。
我使用的流程是:

  1. 使用 cudamallocManaged() 分配内存
  2. 用数据初始化数组
  3. 分配cuBLAS句柄
  4. 调用 cublasDgemv 执行乘法,将结果存储在 results

当使用 newcublasSetMatrix()cublasSetVector() 时,效果很好。

如何在 cuBLAS 中使用统一内存?

以下是最低限度的工作示例:

统一内存尝试(这会返回 results 中的所有 0):

#include <cuda.h>
#include <cuda_runtime.h>
#include <iostream>
#include <ctime>
#include "cublas_v2.h"

#define cudaErrChk(ans) { gpuAssert((ans),__FILE__,__LINE__); }
inline void gpuAssert(cudaError_t code,const char *file,int line,bool abort=true)
{
    if (code != cudaSuccess)
    {
        fprintf(stderr,"GPUassert: %s %s %d\n",cudaGetErrorString(code),file,line);
        if (abort) exit(code);
    }
}

static const char *cublasErrChk(cublasstatus_t error)
{
    switch (error)
    {
        case CUBLAS_STATUS_SUCCESS:
            return "CUBLAS_STATUS_SUCCESS";

        case CUBLAS_STATUS_NOT_INITIALIZED:
            return "CUBLAS_STATUS_NOT_INITIALIZED";

        case CUBLAS_STATUS_ALLOC_Failed:
            return "CUBLAS_STATUS_ALLOC_Failed";

        case CUBLAS_STATUS_INVALID_VALUE:
            return "CUBLAS_STATUS_INVALID_VALUE";

        case CUBLAS_STATUS_ARCH_MISMATCH:
            return "CUBLAS_STATUS_ARCH_MISMATCH";

        case CUBLAS_STATUS_MAPPING_ERROR:
            return "CUBLAS_STATUS_MAPPING_ERROR";

        case CUBLAS_STATUS_EXECUTION_Failed:
            return "CUBLAS_STATUS_EXECUTION_Failed";

        case CUBLAS_STATUS_INTERNAL_ERROR:
            return "CUBLAS_STATUS_INTERNAL_ERROR";
    }

    return "<unkNown>";
}

int main() {

    size_t dims = 4;

    double *vec,*mat,*results;

    cudaErrChk( cudamallocManaged(&vec,dims * sizeof(double)) );
    cudaErrChk( cudamallocManaged(&mat,dims * dims * sizeof(double)) );
    cudaErrChk( cudamallocManaged(&results,dims * sizeof(double)) );

    printf("Vector:\n");
    for (int i = 1; i < dims + 1; i++) {
        vec[i] = 0.5 * i;
        printf("%.2lf ",vec[i]);
    } 
    printf("\n\nMatrix:\n");

    for (int i = 1; i < dims * dims + 1; i++) {
        mat[i] = 1.0 * i;
        printf("%.2lf ",mat[i]);

        if (i % dims == 0)
            printf("\n");
    }
    printf("\n");

    cublasHandle_t handle;
    cublasErrChk( cublasCreate(&handle) );

    double alpha = 1.f,beta = 1.f;

    // multiply mat by vec to get results
    cublasErrChk(
        cublasDgemv(
            handle,CUBLAS_OP_N,dims,&alpha,mat,vec,1,&beta,results,1
        )
    );

    for (int i = 0; i < dims; i++)
        printf("%.2lf ",results[i]);
    printf("\n");

    cudaErrChk( cudaFree(vec) );
    cudaErrChk( cudaFree(mat) );
    cudaErrChk( cudaFree(results) );

    return 0;
}

常规 malloc/setMatrix() 尝试:

#include <cuda.h>
#include <cuda_runtime.h>
#include <iostream>
#include <ctime>
#include "cublas_v2.h"

#define cudaErrChk(ans) { gpuAssert((ans),line);
        if (abort) exit(code);
    }
}

static const char *cublasErrChk(cublasstatus_t error)
{
    switch (error)
    {
        case CUBLAS_STATUS_SUCCESS:
            return "CUBLAS_STATUS_SUCCESS";

        case CUBLAS_STATUS_NOT_INITIALIZED:
            return "CUBLAS_STATUS_NOT_INITIALIZED";

        case CUBLAS_STATUS_ALLOC_Failed:
            return "CUBLAS_STATUS_ALLOC_Failed";

        case CUBLAS_STATUS_INVALID_VALUE:
            return "CUBLAS_STATUS_INVALID_VALUE";

        case CUBLAS_STATUS_ARCH_MISMATCH:
            return "CUBLAS_STATUS_ARCH_MISMATCH";

        case CUBLAS_STATUS_MAPPING_ERROR:
            return "CUBLAS_STATUS_MAPPING_ERROR";

        case CUBLAS_STATUS_EXECUTION_Failed:
            return "CUBLAS_STATUS_EXECUTION_Failed";

        case CUBLAS_STATUS_INTERNAL_ERROR:
            return "CUBLAS_STATUS_INTERNAL_ERROR";
    }

    return "<unkNown>";
}

int main() {

    size_t dims = 4;

    double *h_vec,*h_mat,*h_results;

    h_vec = new double[dims];
    h_mat = new double[dims * dims];
    h_results = new double[dims];

    printf("Vector:\n");
    for (int i = 1; i < dims + 1; i++) {
        h_vec[i] = 0.5 * i;
        printf("%.2lf ",h_vec[i]);
    } 
    printf("\n\nMatrix:\n");

    for (int i = 1; i < dims * dims + 1; i++) {
        h_mat[i] = 1.0 * i;
        printf("%.2lf ",h_mat[i]);

        if (i % dims == 0)
            printf("\n");
    }
    printf("\n");

    double *d_vec,*d_mat,*d_results;

    cudaErrChk( cudamalloc(&d_vec,dims * sizeof(double)) );
    cudaErrChk( cudamalloc(&d_mat,dims * dims * sizeof(double)) );
    cudaErrChk( cudamalloc(&d_results,dims * sizeof(double)) );

    cublasHandle_t handle;
    cublasErrChk( cublasCreate(&handle) );

    // copy the data manually to the GPUs
    cublasErrChk( cublasSetVector(dims,sizeof(*d_vec),h_vec,d_vec,1) );
    cublasErrChk( cublasSetMatrix(dims,sizeof(double),h_mat,d_mat,dims) );

    double alpha = 1.f,beta = 1.f;

    // // multiply mat by vec to get results
    cublasErrChk(
        cublasDgemv(
            handle,d_results,1
        )
    );

    cublasErrChk( cublasGetVector(dims,sizeof(*h_results),h_results,1) );

    for (int i = 0; i < dims; i++)
        printf("%.2lf ",h_results[i]);
    printf("\n");

    cudaErrChk( cudaFree(d_vec) );
    cudaErrChk( cudaFree(d_mat) );
    cudaErrChk( cudaFree(d_results) );

    delete [] h_vec;
    delete [] h_mat;
    delete [] h_results;

    return 0;
}

编译
nvcc -o main main.cu -lcublas

解决方法

正如@talonmies 指出的那样,问题在于我使用了异步调用并且没有及时返回结果。这是通过在 cublasDgemv() 调用后添加 cudaDeviceSynchronize() 来解决的:

#include <cuda.h>
#include <cuda_runtime.h>
#include <iostream>
#include <ctime>
#include "cublas_v2.h"

#define cudaErrChk(ans) { gpuAssert((ans),__FILE__,__LINE__); }
inline void gpuAssert(cudaError_t code,const char *file,int line,bool abort=true)
{
    if (code != cudaSuccess)
    {
        fprintf(stderr,"GPUassert: %s %s %d\n",cudaGetErrorString(code),file,line);
        if (abort) exit(code);
    }
}

static const char *cublasErrChk(cublasStatus_t error)
{
    switch (error)
    {
        case CUBLAS_STATUS_SUCCESS:
            return "CUBLAS_STATUS_SUCCESS";

        case CUBLAS_STATUS_NOT_INITIALIZED:
            return "CUBLAS_STATUS_NOT_INITIALIZED";

        case CUBLAS_STATUS_ALLOC_FAILED:
            return "CUBLAS_STATUS_ALLOC_FAILED";

        case CUBLAS_STATUS_INVALID_VALUE:
            return "CUBLAS_STATUS_INVALID_VALUE";

        case CUBLAS_STATUS_ARCH_MISMATCH:
            return "CUBLAS_STATUS_ARCH_MISMATCH";

        case CUBLAS_STATUS_MAPPING_ERROR:
            return "CUBLAS_STATUS_MAPPING_ERROR";

        case CUBLAS_STATUS_EXECUTION_FAILED:
            return "CUBLAS_STATUS_EXECUTION_FAILED";

        case CUBLAS_STATUS_INTERNAL_ERROR:
            return "CUBLAS_STATUS_INTERNAL_ERROR";
    }

    return "<unknown>";
}

int main() {

    size_t dims = 4;

    double *vec,*mat,*results;

    cudaErrChk( cudaMallocManaged(&vec,dims * sizeof(double)) );
    cudaErrChk( cudaMallocManaged(&mat,dims * dims * sizeof(double)) );
    cudaErrChk( cudaMallocManaged(&results,dims * sizeof(double)) );

    printf("Vector:\n");
    for (int i = 1; i < dims + 1; i++) {
        vec[i] = 0.5 * i;
        printf("%.2lf ",vec[i]);
    } 
    printf("\n\nMatrix:\n");

    for (int i = 1; i < dims * dims + 1; i++) {
        mat[i] = 1.0 * i;
        printf("%.2lf ",mat[i]);

        if (i % dims == 0)
            printf("\n");
    }
    printf("\n");

    cublasHandle_t handle;
    cublasErrChk( cublasCreate(&handle) );

    double alpha = 1.f,beta = 1.f;

    // multiply mat by vec to get results
    cublasErrChk(
        cublasDgemv(
            handle,CUBLAS_OP_N,dims,&alpha,mat,vec,1,&beta,results,1
        )
    );
    cudaDeviceSynchronize();

    for (int i = 0; i < dims; i++)
        printf("%.2lf ",results[i]);
    printf("\n");

    cudaErrChk( cudaFree(vec) );
    cudaErrChk( cudaFree(mat) );
    cudaErrChk( cudaFree(results) );

    return 0;
}