带有函数指针和可变参数模板的CUDA内核

时间:2014-11-04 14:48:16

标签: c++ cuda function-pointers variadic-templates

我正在尝试设计一个cuda框架,它接受用户函数并通过设备函数指针将它们转发到内核。 CUDA可以使用可变参数模板(-stc = c ++ 11),到目前为止一直很好。

但是,当内核调用设备函数指针时,我遇到了问题。显然内核运行没有问题,但GPU使用率为0%。如果我只是用实际函数替换回调指针,那么GPU使用率为99%。这里的代码非常简单,大循环范围只是为了使事情可以测量。我用以下方法测量了gpu状态:

nvidia-smi --query-gpu=utilization.gpu,utilization.mory,memory.used --format=csv -lms 100 -f out.txt
IIRC,用户函数需要与内核(可能包括#inc)位于同一个文件单元中,以便nvcc成功。 func_d就在源代码中,它编译并运行正常,除了不使用函数指针(这是本设计中的重点)。

我的问题是: 为什么带回调设备函数指针的内核无效?

请注意,当我打印回调和func_d地址时,它们是相同的,就像在此示例输出中一样:

size of Args = 1
callback() address = 4024b0
func_d()   address = 4024b0

另一个奇怪的事情是,如果取消注释callback()中的kernel()调用,则GPU使用率会回到0%,即使func_d()调用仍在那里... func_d版本需要大约4秒才能运行,而回调版本则不需要任何东西(好吧,~0.1秒)。

系统规格和编译命令位于下面代码的开头。

谢谢!

// compiled with:
// nvcc -g -G -O0 -std=c++11 -arch=sm_20 -x cu sample.cpp
//
// Nvidia Quadro 6000 (compute capability 2.0)
// CUDA 6.5 (V6.5.12),
// Arch Linux, Nvidia driver 343.22-4, gcc 4.9.1
// Nov, 2014


#include <stdio.h>

__device__
void func_d(double* vol)
{
*vol += 5.4321f;
}


// CUDA kernel function
template <typename... Types>
__global__ void kernel( void (*callback)(Types*...) )
{
double val0 = 1.2345f;

//  // does not use gpu (0% gpu utilization)
//  for ( int i = 0; i < 1000000; i++ ) {
//  callback( &val0 );
//  }

// uses gpu (99% gpu utilization)
for ( int i = 0; i < 10000000; i++ ) {
func_d( &val0 );
}
}


// host function
template <typename... Types>
void host_func( void (*callback)(Types*...) )
{
// get user kernel number of arguments.
constexpr int I = sizeof...(Types);
printf("size of Args = %d\n",I);

printf("callback() address = %x\n",callback);
printf("func_d()   address = %x\n",func_d);

dim3 nblocks = 100;
int nthread = 100;
kernel<Types...><<<nblocks,nthread>>>( callback );
}


__host__
int main(int argc, char** argv)
{
host_func(func_d);
}

1 个答案:

答案 0 :(得分:2)

  
    

我的问题是:为什么带回调设备函数指针的内核无效?

  

可能有几个问题需要解决。但最简单的答案是因为在主机代码中获取设备实体的地址是非法的。对于设备变量和设备功能都是如此。现在,您可以获取这些实体的地址。但地址是垃圾。它在主机或设备上都不可用。如果您仍尝试使用它们,您将在设备上获得未定义的行为,这通常会使您的内核停止运行。

主机地址可能会在主机代码中被观察到。可以在设备代码中观察设备地址。任何其他行为都需要API干预。

  1. 您似乎正在使用nvidia-smi利用率查询来衡量事情是否正常运行。我建议改为proper cuda error checking,您也可以使用cuda-memcheck运行代码。

  2. &#34;为什么func_d的地址与callback的地址匹配?&#34;因为您在主机代码中使用两个地址,并且这两个地址都是垃圾。为了说服自己,在内核的最后添加类似这样的行:

    if ((!threadIdx.x)&&(!blockIdx.x)) printf("in-kernel func_d()   address = %x\n",func_d);
    

    你会看到它打印出与主机上印刷的内容不同的东西。

  3. &#34;设备利用率如何?&#34;一旦设备遇到错误,内核就会终止,并且利用率会变为零。希望这能为你解释这句话:&#34;另一个奇怪的事情是,如果一个取消注释kernel()中的callback()调用,那么即使func_d()调用仍在那里,GPU使用率也会回到0%。 ..&#34;

  4. &#34;我该如何解决这个问题?&#34;我不知道解决这个问题的好方法。如果您在编译时知道有限数量的CUDA函数,那么您希望用户能够从中进行选择,那么适当的事情可能只是创建一个合适的索引,并使用它来选择函数。如果你真的想,你可以运行一个初步/设置内核,它将获取你关心的函数的地址,然后你可以将这些地址传递回主机代码,并在随后的内核调用中将它们用作参数,这个应该允许你的机制工作。但我不知道它是如何阻止在编译时通过一组预定义函数编制索引的。如果您要进入的方向是您希望用户能够在运行时提供用户定义的函数我认为您会发现这很难做到目前使用CUDA运行时API(我怀疑这可能会在将来发生变化。)我提供了一个相当扭曲的机制来尝试这样做here(阅读整个问题和答案; talonmies回答那里也有信息量)。另一方面,如果你愿意使用CUDA驱动程序API,那么它应该是可能的,尽管有些涉及,因为这正是PyCUDA中以非常优雅的方式完成的,例如。

    < / LI>
  5. 将来,请缩进您的代码。

  6. 这是一个完整的例子,展示了上面的一些想法。特别是,我以一种相当粗略的方式显示,func_d地址可以在设备代码中获取,然后传递回主机,然后用作未来的内核参数来成功选择/调用该设备功能。

    $ cat t595.cu
    // compiled with:
    // nvcc -g -G -O0 -std=c++11 -arch=sm_20 -x cu sample.cpp
    //
    // Nvidia Quadro 6000 (compute capability 2.0)
    // CUDA 6.5 (V6.5.12),
    // Arch Linux, Nvidia driver 343.22-4, gcc 4.9.1
    // Nov, 2014
    
    
    #include <stdio.h>
    
    __device__
    void func_d(double* vol)
    {
      if ((!threadIdx.x) && (!blockIdx.x)) printf("value = %f\n", *vol);
      *vol += 5.4321f;
    }
    
    template <typename... Types>
    __global__ void setup_kernel(void (**my_callback)(Types*...)){
      *my_callback = func_d;
    }
    
    // CUDA kernel function
    template <typename... Types>
    __global__ void kernel( void (*callback)(Types*...) )
    {
      double val0 = 1.2345f;
    
    //  // does not use gpu (0% gpu utilization)
    //  for ( int i = 0; i < 1000000; i++ ) {
      callback( &val0 );
    //  }
    
      val0 = 0.0f;
    // uses gpu (99% gpu utilization)
    //  for ( int i = 0; i < 10000000; i++ ) {
        func_d( &val0 );
    //  }
      if ((!threadIdx.x)&&(!blockIdx.x)) printf("in-kernel func_d()   address = %x\n",func_d);
    }
    
    
    // host function
    template <typename... Types>
    void host_func( void (*callback)(Types*...) )
    {
    // get user kernel number of arguments.
      constexpr int I = sizeof...(Types);
      printf("size of Args = %d\n",I);
    
      printf("callback() address = %x\n",callback);
      printf("func_d()   address = %x\n",func_d);
    
      dim3 nblocks = 100;
      int nthread = 100;
      unsigned long long *d_callback, h_callback;
      cudaMalloc(&d_callback, sizeof(unsigned long long));
      setup_kernel<<<1,1>>>((void (**)(Types*...))d_callback);
      cudaMemcpy(&h_callback, d_callback, sizeof(unsigned long long), cudaMemcpyDeviceToHost);
      kernel<Types...><<<nblocks,nthread>>>( (void (*)(Types*...))h_callback );
      cudaDeviceSynchronize();
    }
    
    
    __host__
    int main(int argc, char** argv)
    {
      host_func(func_d);
    }
    $ nvcc -std=c++11 -arch=sm_20 -o t595 t595.cu
    $ cuda-memcheck ./t595
    ========= CUDA-MEMCHECK
    size of Args = 1
    callback() address = 4025dd
    func_d()   address = 4025dd
    value = 1.234500
    value = 0.000000
    in-kernel func_d()   address = 4
    ========= ERROR SUMMARY: 0 errors
    $