【问题标题】:CUDA: Copy dynamically created array of function pointers on the CPU to GPU memoryCUDA:将 CPU 上动态创建的函数指针数组复制到 GPU 内存
【发布时间】:2015-10-20 02:18:51
【问题描述】:

我想在 CPU 上动态创建一个函数指针列表(使用从 main() 调用的某种 push_back() 方法)并将其复制到 GPU __constant____device__ 数组,而不需要求助于静态__device__ 函数指针。我相信this question 与我的问题有关;但是,我的目标是迭代地创建 __host__ 函数指针数组,然后将其复制到 __constant__ 函数指针数组,而不是在声明时初始化后者。

具有静态函数指针(如 herehere 所示)的工作代码示例将是:

common.h:

#ifndef COMMON_H
#define COMMON_H

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

#define num_functions 3

#define gpuErrchk(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);
   }
}

// fptr_t: Pointer to void function that takes two integer lvalues
typedef void (*fptr_t)(int&, int&);

// some examples of void(int&, int&) functions...
__device__ void Add(int &a, int &b) {printf("Add... %i + %i = %i\n", a, b, a+b);}
__device__ void Subtract(int &a, int &b) {printf("Subtract... %i - %i = %i\n", a, b, a-b);}
__device__ void Multiply(int &a, int &b) {printf("Multiply... %i * %i = %i\n", a, b, a*b);}

// List of function pointers in device memory
__constant__ fptr_t constant_fList[num_functions];

// Kernel called from main(): choose the function to apply whose index is equal to thread ID
__global__ void kernel(int a, int b) {
  fptr_t f;
  if (threadIdx.x < num_functions) {
    f = constant_fList[threadIdx.x];
    f(a,b);
  }
}

#endif

main.cu:

#include "common.h"

// Static device function pointers
__device__ fptr_t p_Add = Add;
__device__ fptr_t p_Sub = Subtract;
__device__ fptr_t p_Mul = Multiply;

// Load function list to constant memory
void loadList_staticpointers() {
  fptr_t h_fList[num_functions];
  gpuErrchk( cudaMemcpyFromSymbol(&h_fList[0], p_Add, sizeof(fptr_t)) );
  gpuErrchk( cudaMemcpyFromSymbol(&h_fList[1], p_Sub, sizeof(fptr_t)) );
  gpuErrchk( cudaMemcpyFromSymbol(&h_fList[2], p_Mul, sizeof(fptr_t)) );
  gpuErrchk( cudaMemcpyToSymbol(constant_fList, h_fList, num_functions * sizeof(fptr_t)) );
}

int main() {

  loadList_staticpointers();
  int a = 12, b = 15;
  kernel<<<1,3>>>(a, b);
  gpuErrchk(cudaGetLastError());
  gpuErrchk(cudaDeviceSynchronize());

  return 0;
}

规格:GeForce GTX 670,编译为 -arch=sm_30,CUDA 6.5,Ubuntu 14.04

我希望避免使用静态设备函数指针,因为附加每个函数都需要在用户端进行代码维护 - 声明一个新的静态指针,如 p_Addp_Mul,操作 void loadList_functionpointers() 等. 为了清楚起见,我正在尝试以下(崩溃)代码:

main_wrong.cu:

#include "common.h"
#include <vector>

// Global variable: list of function pointers in host memory
std::vector<fptr_t> vec_fList;

// Add function to functions list
void addFunc(fptr_t f) {vec_fList.push_back(f);}

// Upload the functions in the std::vector<fptr_t> to GPU memory
// Copies CPU-side pointers to constant_fList, therefore crashes on kernel call 
void UploadVector() {
  fptr_t* h_vpointer = vec_fList.data();
  gpuErrchk( cudaMemcpyToSymbol(constant_fList, h_vpointer, vec_fList.size() * sizeof(fptr_t)) );
}

int main() {

  addFunc(Add);
  addFunc(Subtract);
  addFunc(Multiply);
  int a = 12, b = 15;

  UploadVector();

  kernel<<<1,3>>>(a, b); // Wrong to call a host-side function pointer from a kernel
  gpuErrchk(cudaGetLastError());
  gpuErrchk(cudaDeviceSynchronize());

  return 0;
}

我的理解是函数指针指向主机地址被复制到GPU中,内核无法使用,函数@时需要指针指向GPU地址 987654339@ 被调用。使用设备端指针填充主机端数组对我来说适用于原始数据(请参阅this question),但不适用于函数指针。统一内存的简单尝试也失败了......到目前为止,我只发现静态设备端指针可以工作。没有其他方法可以将动态创建的 CPU 函数指针数组复制到 GPU 上吗?

【问题讨论】:

  • 附带说明:为什么需要使用函数指针?你想用他们解决什么问题?
  • 我正在开发用于粒子模拟的代码。粒子根据彼此的属性以不同的方式相互交互,因此必须动态确定应用于每个交互的函数。
  • 这比在内核中使用switch 更好吗?
  • 内核中的switch 涉及在用户端对代码的操作 - 每次他们引入一个新函数(他们已经需要编码)时,都必须为switch。我不希望用户需要了解 CUDA 机制,而是为他们提供一些从 main() 调用的 addFunc(f) 机制,唯一的 C++ 代码将涉及 f
  • 我明白了;用户在添加新功能时重新编译整个代码是否可行?然后您可以使用一些模板逻辑自动为您生成该开关。我也猜想这给了编译器更多优化代码的机会。如果您可以使用模板,是否可以升级到 CUDA 7 以启用 C++11?

标签: cuda function-pointers


【解决方案1】:

如果您可以使用 C++11(从 CUDA 7 开始支持),您可以使用以下内容自动生成函数表:

template <fptr_t... Functions>
__global__ void kernel(int a, int b)
{
  constexpr auto num_f = sizeof...(Functions);

  constexpr fptr_t table[] = { Functions... };

  if (threadIdx.x < num_f)
  {
    fptr_t f = table[threadIdx.x];
    f(a,b);
  }
}

然后你会调用这个内核使用

kernel<Add, Subtract, Multiply><<<1,3>>>(a, b);

【讨论】:

  • 严格来说,模板与函数指针无关,因为类在编译时生成..
【解决方案2】:

ms 的回答启发,我选择将函数指针作为模板参数传递 - 这实际上是解决我的问题的关键 - 并发现在没有静态函数指针的帮助的情况下,从main() 函数迭代地填充__device__ 函数指针数组dev_fList 确实是可能的,而且甚至不需要C++11 兼容性! p>

这是一个关于全局内存中 __device__ 数组的工作示例。我还没有尝试过它的常量内存对应物,但是一旦成功创建了一个全局内存数组,我的猜测是cudaMemcpyToSymbol(..., cudaMemcpyDeviceToDevice) 应该可以解决问题。

内核kernel() 为函数指针dev_f 创建一个GPU 地址,并复制作为模板参数传递的函数f。由于这是一个来自 CPU 的迭代过程,因此这个内核中只涉及一个线程(线程0),它以配置&lt;&lt;&lt;1,1&gt;&gt;&gt; 启动。静态变量count_f 负责dev_fList 中的索引。

common.h:

#ifndef COMMON_H
#define COMMON_H

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

#define num_functions 3

#define gpuErrchk(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);
   }
}

// fptr_t: Pointer to void function that takes two integer lvalues
typedef void (*fptr_t)(int&, int&);

// some examples of void(int&, int&) functions...
__device__ void Add(int &a, int &b) {printf("Add... %i + %i = %i\n", a, b, a+b);}
__device__ void Subtract(int &a, int &b) {printf("Subtract... %i - %i = %i\n", a, b, a-b);}
__device__ void Multiply(int &a, int &b) {printf("Multiply... %i * %i = %i\n", a, b, a*b);}

// List of function pointers in device memory
// Note that, in my example, it resides in global memory space, not constant memory
__device__ fptr_t dev_fList[num_functions];

#endif

main.cu:

#include "common.h"

// Index in dev_fList[] == number of times addFunc<>() was launched
static int count_f = 0;

// Kernel that copies function f to the GPU
template<fptr_t f>
__global__ void kernel(int a, int b, int idx) {
  fptr_t dev_f = f; // Create device function pointer
  dev_fList[idx] = dev_f; // Populate the GPU array of function pointers
  dev_fList[idx](a,b); // Make sure that the array was populated correctly
}

// Add function to functions list
template<fptr_t f>
void addFunc(const int &a, const int &b) {
  if (count_f >= num_functions) {
    std::cout << "Error: not enough memory statically allocated on device!\n";
    exit(EXIT_FAILURE);
  }
  kernel<f><<<1,1>>>(a,b,count_f);
  gpuErrchk(cudaGetLastError());
  gpuErrchk(cudaDeviceSynchronize());
  count_f++;
}

int main() {
  int a = 12, b = 15;
  addFunc<Add>(a,b);
  addFunc<Subtract>(a,b);
  addFunc<Multiply>(a,b);

  return 0;
}

编辑:添加了指向常量内存的函数指针数组的副本

不管怎样,这里是如何将我们的 dev_fList 数组复制到常量内存:

在 common.h:

__constant__ fptr_t cst_fList[num_functions];

__global__ void cst_test(int a, int b, int idx) {
   if (threadIdx.x < idx) cst_fList[threadIdx.x](a,b);
}

在 main.cu main() 函数中,添加了所有需要的函数后:

  fptr_t *temp;
  gpuErrchk( cudaMemcpyFromSymbol((void**)&temp, dev_fList[0], count_f * sizeof(fptr_t)) );
  gpuErrchk( cudaMemcpyToSymbol(cst_fList[0], &temp, count_f * sizeof(fptr_t)) );

  cst_test<<<1,count_f>>>(a,b, count_f);
  gpuErrchk(cudaGetLastError());
  gpuErrchk(cudaDeviceSynchronize());

它可能看起来很难看,因为我知道内存是通过temp 传输到主机然后返回到设备的;欢迎提出更优雅的建议。

【讨论】:

    【解决方案3】:

    不可能使用动态创建的 CUDA 设备函数指针(至少没有崩溃或 UB)。基于模板的解决方案在编译时工作(不是动态的)。 CUDA 设备函数指针方法随处可见,需要全局空间中的设备符号。这意味着对于每个函数,必须已经声明了一个设备函数指针。这也意味着您不能使用普通的 C 函数指针作为参考,例如在运行时设置。在理解上,使用 CUDA 设备函数指针是有问题的。基于模板的方法看起来用户友好,但根据定义不是动态的。

    使用函数指针显示结构的示例:

    这个例子展示了一个有一些函数指针的结构。在普通的 C++ 代码中,您可以在程序运行时(动态地)设置和更改设备函数指针。对于 CUDA,下面的这个例子是不可能,因为结构中的函数指针不是有效的设备符号。这意味着它们不能与“cudaMemcpyFromSymbol”一起使用。为了避免这种情况,必须创建原始函数(函数指针的目标)或全局 cuda 设备函数指针。两者都不是动态的。

    这是动态赋值:

    typedef float (*pDistanceFu) (float, float);
    typedef float (*pDecayFu)    (float, float, float);
    
    // In C++ you can set and reset the function pointer during run time whenever you want ..
    struct DistFunction {
      /*__host__ __device__*/ pDistanceFu distance; // uncomment for NVCC ..
      /*__host__ __device__*/ pDecayFu rad_decay;
      /*__host__ __device__*/ pDecayFu lrate_decay;
    };
    
    // you can do what you want ..
    DistFunction foo, bar;
    foo.distance = bar.distance;
    // ..
    

    CUDA 应该是这样的,但它会失败,因为没有有效的设备符号:(

    pDistanceFu hDistance; 
    pDecayFu hRadDay; 
    pDecayFu hLRateDecay; 
    
    void DeviceAssign(DistFunction &dist) {      
      cudaMemcpyFromSymbol(&hDistance, dist.distance, sizeof(pDistanceFu) );
      cudaMemcpyFromSymbol(&hRadDay, dist.rad_decay, sizeof(pDecayFu) );
      cudaMemcpyFromSymbol(&hLRateDecay, dist.lrate_decay, sizeof(pDecayFu) );
    
      dist.distance = hDistance;
      dist.rad_decay = hRadDay;
      dist.lrate_decay = hLRateDecay;
    } 
    

    这是经典方式,但您注意到,它不再是动态的,因为设备符号必须引用函数引用,而不是可能在运行时更改的指针..

    // .. and this would work
    #ifdef __CUDACC__
      __host__ __device__
    #endif
    inline float fcn_rad_decay (float sigma0, float T, float lambda) {
      return std::floor(sigma0*exp(-T/lambda) + 0.5f);
    }
    
    __device__ pDistanceFu pFoo= fcn_rad_decay; // pointer must target a reference, no host pointer possible 
    
    void DeviceAssign2(DistFunction &dist) {      
      cudaMemcpyFromSymbol(&hLRateDecay, &fcn_rad_decay, sizeof(pDecayFu) );
      // the same:
      // cudaMemcpyFromSymbol(&hLRateDecay, pFoo, sizeof(pDecayFu) );
      // ..
    
      dist.lrate_decay = hLRateDecay;
      // ..
    } 
    

    【讨论】:

    • 显然我很密集。道歉。您已经提出了一些关于 CUDA 设备函数指针的声明,我想澄清一下。我怀疑您正在重述已在其他地方声明的内容(并且是 [documented]())-您不能直接在主机代码中获取设备函数的地址。 1. 你觉得这和你的主张一样吗?如果没有,您能否给出一个在普通(主机/CPU)代码中使用“动态”设备指针的简短示例?
    • 2.据推测,您可以在主机上做的任何事情,我都可以在 CUDA 中执行在设备上。因此,您的主张围绕着(我认为)从主机代码调度 的设备函数指针的使用。那是对的吗?因为我认为我应该能够让设备函数指针在设备代码中可用,与您将生成的主机代码中的任何主机示例相媲美?
    • 对不起,here 是我打算在上面的第一条评论中包含的文档链接
    • 无法将设备函数指针设置为主机函数指针。您可以随心所欲地投票,代码是静态的:D
    • 我没有否决这个答案。如果你愿意,我可以证明它:D 而且你肯定是正确的,不可能将设备函数指针设置为主机函数指针(并且无论如何合理地使用它)。
    猜你喜欢
    • 2014-08-30
    • 1970-01-01
    • 2015-03-11
    • 1970-01-01
    • 2014-08-23
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 2015-06-10
    相关资源
    最近更新 更多