【问题标题】:cublas matrix inversion from device来自设备的 cublas 矩阵求逆
【发布时间】:2015-01-21 13:28:49
【问题描述】:

我正在尝试从设备运行矩阵求逆。如果从主机调用,此逻辑可以正常工作。

编译行如下(Linux):

nvcc -ccbin g++ -arch=sm_35 -rdc=true simple-inv.cu -o simple-inv -lcublas_device -lcudadevrt

我收到以下似乎无法解决的警告。 (我的 GPU 是 Kepler。我不知道它为什么要尝试链接到 Maxwell 例程。我有 Cuda 6.5-14):

nvlink warning : SM Arch ('sm_35') not found in '/usr/local/cuda/bin/../targets/x86_64-linux/lib/libcublas_device.a:maxwell_sm50_sgemm.o'

程序运行:

handle 0 n = 3
simple-inv.cu:63 Error [an illegal memory access was encountered]

测试程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>

#define PERR(call) \
  if (call) {\
   fprintf(stderr, "%s:%d Error [%s] on "#call"\n", __FILE__, __LINE__,\
      cudaGetErrorString(cudaGetLastError()));\
   exit(1);\
  }
#define ERRCHECK \
  if (cudaPeekAtLastError()) { \
    fprintf(stderr, "%s:%d Error [%s]\n", __FILE__, __LINE__,\
       cudaGetErrorString(cudaGetLastError()));\
    exit(1);\
  }

__global__ void
inv_kernel(float *a_i, float *c_o, int n)
{ 
  int p[3], info[1], batch;
  cublasHandle_t hdl;
  cublasStatus_t status = cublasCreate_v2(&hdl);
  printf("handle %d n = %d\n", status, n);

  info[0] = 0;
  batch = 1;
  float *a[] = {a_i};
  const float *aconst[] = {a_i};
  float *c[] = {c_o};
  // See
  // http://docs.nvidia.com/cuda/pdf/CUDA_Dynamic_Parallelism_Programming_Guide.pdf
  //http://stackoverflow.com/questions/27094612/cublas-matrix-inversion-from-device

  status = cublasSgetrfBatched(hdl, n, a, n, p, info, batch);
  __syncthreads();
  printf("rf %d info %d\n", status, info[0]);
  status = cublasSgetriBatched(hdl, n, aconst, n, p,
      c, n, info, batch);
  __syncthreads();
  printf("ri %d info %d\n", status, info[0]);

  cublasDestroy_v2(hdl);
  printf("done\n");
}
static void
run_inv(float *in, float *out, int n)
{
  float *a_d, *c_d;

  PERR(cudaMalloc(&a_d, n*n*sizeof(float)));
  PERR(cudaMalloc(&c_d, n*n*sizeof(float)));
  PERR(cudaMemcpy(a_d, in, n*n*sizeof(float), cudaMemcpyHostToDevice));

  inv_kernel<<<1, 1>>>(a_d, c_d, n);

  cudaDeviceSynchronize();
  ERRCHECK;

  PERR(cudaMemcpy(out, c_d, n*n*sizeof(float), cudaMemcpyDeviceToHost));
  PERR(cudaFree(a_d));
  PERR(cudaFree(c_d));
}

int
main(int argc, char **argv)
{
  float c[9];
  float a[] = {
    1,   2,   3,
    0,   4,   5,
    1,   0,   6 };

  run_inv(a, c, 3);
  return 0;
}

我已按照http://docs.nvidia.com/cuda/cublas/index.html#device-api 第 2.1.9 节的指南进行操作,但我怀疑我忽略了一些东西。

注意:于 11/24 编辑以使用正确的指针输入。这仍然会报告内核内部的非法内存访问。

【问题讨论】:

  • 您发布的代码中的第 63 行是空格。代码中发生的错误究竟在哪里?
  • 设备同步期间的第 64 行。我必须发布和较旧的输出。我怀疑在调用 cublasSgetrfBatched 期间。
  • (float**)a_i 看起来很可疑。你的意思是传递a_i 的地址而不是它的值吗?
  • @VAndrei:是的,这是可能的,您的评论与问题完全无关。
  • @Bob:你链接的代码和你的代码不一样,区别在于你有一个非法的演员表。 *a[] = {a_i}; cublasSgetrfBatched(..., a, ....)cublasSgetrfBatched(..., (float**)a_i, ...)不是等价的,如果你认为是,那你需要修改C++中的指针理论。

标签: cuda cublas


【解决方案1】:

注意:从 CUDA 10.0 起,从设备代码调用 cublas 函数的功能已从 CUDA 中删除。此答案中的描述仅适用于 CUDA 9.x 使用和之前的版本。见here

关于 sm_50 的警告是良性的。这就是我所说的“在这种情况下可以安全地忽略它们”。

关于您当前发布的代码,问题与动态并行文档中描述的有关使用线程本地内存here 的内容有关。

简而言之,父线程的本地内存在子内核启动中“超出范围”。虽然这并不完全明显,但来自设备代码的 cublas 调用正在(尝试)启动子内核。这意味着像这样的声明:

int p[3], info[1],

如果将这些指针(例如pinfo)传递给子内核,则会出现问题。指针本身的数值不会被破坏,但它们不会指向子内核内存空间中任何“有意义”的东西。

有多种方法可以解决这个问题,但一种可能的解决方案是用“设备堆”中的分配替换这种类型的任何堆栈/本地分配,可以通过in-kernel malloc 进行分配。

这是一个完整的代码/示例,对我来说似乎可以正常工作。对于给定的样本矩阵的求逆,输出似乎是正确的:

$ cat t605.cu
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>

#define PERR(call) \
  if (call) {\
   fprintf(stderr, "%s:%d Error [%s] on "#call"\n", __FILE__, __LINE__,\
      cudaGetErrorString(cudaGetLastError()));\
   exit(1);\
  }
#define ERRCHECK \
  if (cudaPeekAtLastError()) { \
    fprintf(stderr, "%s:%d Error [%s]\n", __FILE__, __LINE__,\
       cudaGetErrorString(cudaGetLastError()));\
    exit(1);\
  }

__global__ void
inv_kernel(float *a_i, float *c_o, int n)
{
  int *p = (int *)malloc(3*sizeof(int));
  int *info = (int *)malloc(sizeof(int));
  int batch;
  cublasHandle_t hdl;
  cublasStatus_t status = cublasCreate_v2(&hdl);
  printf("handle %d n = %d\n", status, n);

  info[0] = 0;
  batch = 1;
  float **a = (float **)malloc(sizeof(float *));
  *a = a_i;
  const float **aconst = (const float **)a;
  float **c = (float **)malloc(sizeof(float *));
  *c = c_o;
  // See
  // http://docs.nvidia.com/cuda/pdf/CUDA_Dynamic_Parallelism_Programming_Guide.pdf
  //http://stackoverflow.com/questions/27094612/cublas-matrix-inversion-from-device
  status = cublasSgetrfBatched(hdl, n, a, n, p, info, batch);
  __syncthreads();
  printf("rf %d info %d\n", status, info[0]);
  status = cublasSgetriBatched(hdl, n, aconst, n, p,
      c, n, info, batch);
  __syncthreads();
  printf("ri %d info %d\n", status, info[0]);
  cublasDestroy_v2(hdl);
  printf("done\n");
}
static void
run_inv(float *in, float *out, int n)
{
  float *a_d, *c_d;

  PERR(cudaMalloc(&a_d, n*n*sizeof(float)));
  PERR(cudaMalloc(&c_d, n*n*sizeof(float)));
  PERR(cudaMemcpy(a_d, in, n*n*sizeof(float), cudaMemcpyHostToDevice));

  inv_kernel<<<1, 1>>>(a_d, c_d, n);

  cudaDeviceSynchronize();
  ERRCHECK;

  PERR(cudaMemcpy(out, c_d, n*n*sizeof(float), cudaMemcpyDeviceToHost));
  PERR(cudaFree(a_d));
  PERR(cudaFree(c_d));
}

int
main(int argc, char **argv)
{
  float c[9];
  float a[] = {
    1,   2,   3,
    0,   4,   5,
    1,   0,   6 };

  run_inv(a, c, 3);
  for (int i = 0; i < 3; i++){
    for (int j = 0; j < 3; j++) printf("%f, ",c[(3*i)+j]);
    printf("\n");}

  return 0;
}
$ nvcc -arch=sm_35 -rdc=true -o t605 t605.cu -lcublas_device -lcudadevrt
nvlink warning : SM Arch ('sm_35') not found in '/shared/apps/cuda/CUDA-v6.5.14/bin/..//lib64/libcublas_device.a:maxwell_sgemm.asm.o'
nvlink warning : SM Arch ('sm_35') not found in '/shared/apps/cuda/CUDA-v6.5.14/bin/..//lib64/libcublas_device.a:maxwell_sm50_sgemm.o'
$ ./t605
handle 0 n = 3
rf 0 info 0
ri 0 info 0
done
1.090909, -0.545455, -0.090909,
0.227273, 0.136364, -0.227273,
-0.181818, 0.090909, 0.181818,
$

对于 CUDA 10.0 和更新的用户,我建议使用主机代码中的普通批处理 cublas 函数。特别是对于矩阵求逆,一种选择是使用matinvBatched

【讨论】:

  • 谢谢。这对我有用。我最初分配了 p 和 info 变量,但没有意识到我还需要分配 a、aconst 和 c 变量。在阅读了本地内存参考部分之后,这是有道理的。我想 n 被分配给全局内存堆,因为它是内核调用参数的一部分。句柄变量可能不适用。
  • 其他参数如nbatch等,都是按值传递的。按值传递的参数没有引用回调用环境。这是 C/C++ 的特点,而不是独特的 CUDA 概念。事实上,即使是指针也是“按值”传递的。但是当这些指针值在子内核中被取消引用时,就会发生不好的事情。对于非指针参数,在子内核中没有进行这样的取消引用,一切正常。事实上,这种按值传递实际上发生在 cublas 函数调用中(以及稍后,在底层发生的子内核启动时。)
【解决方案2】:

可能是您正在运行的某些 CUDA 功能仅受不同架构的支持(即使文档说使用的一切都是。如果我使用 -arch=sm_50 编译,我不会收到编译器警告。我没有一个支持 sm_50 的设备进行测试...

此外,这些警告看起来像是某些功能 asm 不适用于您的架构,因此它链接到了您的设备不支持的不同架构 asm,因此您会遇到一些奇怪的错误。我认为你应该接受这个更了解他们的编译器在做什么的 nvidia 开发人员。

我可以访问支持 Compute 3.5 的设备,但不幸的是,只有使用 CUDA v 6.0 并使用您的示例(稍微固定,在第 42 行编译 (const float *) -> (float *)),但我没有得到任何编译警告(遗憾的是结果相同)。

也如 cmets 中所述:

(float**)a_i 

没有使 a_i 成为类型 (float **)。你应该把地址: &a_i

更改这些并不能帮助解决问题,但这些是您可以探索的一些指针。

【讨论】:

  • 对,我的错。,对不起
  • 当我用 SM_50 编译时,我得到“ptxas info : 'device-function-maxrregcount' is a BETA feature”。另一个警告消失了。
猜你喜欢
  • 2016-10-10
  • 1970-01-01
  • 2013-05-05
  • 2011-08-30
  • 2017-04-13
  • 1970-01-01
  • 2017-11-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多