CUDA:如何从宿主函数

时间:2017-05-28 18:26:19

标签: c++ lambda cuda

我有一个虚函数,它根据派生类返回不同的lambda:

class Base
{
public:
    virtual std::function<float()> foo(void) = 0;
};

class Derived : public Base
{
public:
    std::function<float()> foo(void) {
        return [] __device__ (void) {
            return 1.0f;
        };
    }
};

然后我想将这个lambda传递给CUDA内核并从设备中调用它。换句话说,我想这样做:

template<typename Func>
__global__ void kernel(Func f) {
    f();
}

int main(int argc, char** argv)
{
    Base* obj = new Derived;
    kernel<<<1, 1>>>(obj->foo());
    cudaDeviceSynchronize();
    return 0;
}
上面提到的错误是这样的:calling a __host__ function("std::function<float ()> ::operator ()") from a __global__ function("kernel< ::std::function<float ()> > ") is not allowed

如您所见,我将lambda声明为__device__,但foo()方法将其存储在std::function中以便返回它。因此,传递给kernel()的是主机地址,当然它不起作用。我猜这是我的问题,对吗?所以我的问题是:

  • 是否有可能创建__device__ std::function并从foo()方法返回该内容?

  • 如果这不可能,有没有其他方法可以动态选择lambda并将其传递给CUDA内核?使用所有可能的lambdas对kernel()的多次调用进行硬编码不是一种选择。

到目前为止,从我做的快速研究来看,CUDA没有/支持使函数返回设备lambda所需的必要语法。我只是希望我错了。 :)有什么想法吗?

提前致谢

2 个答案:

答案 0 :(得分:2)

在实际回答之前,我不得不怀疑你的问题是不是XY problem。也就是说,我默认怀疑人们有一个很好的借口通过设备上的lambda / function指针执行代码。

但我不会像那样逃避你的问题......

  

以某种方式可以创建__device__ std::function并从foo()方法返回它吗?

简短回答:不,尝试别的。

更长的答案:如果你想在设备端实现一大块标准库,那么也许你可以拥有一个类似于设备端的std::function类。但我不确定这是否可能(很可能不是),无论如何 - 除了非常经验丰富的图书馆开发人员之外,它超出了所有人的能力范围。所以,做点别的事。

  

如果这不可能,有没有其他方法可以动态选择lambda并将其传递给CUDA内核?

是硬编码对kernel()的多次调用以及所有可能的lambdas

首先,请记住lambdas本质上是匿名类 - 因此,如果它们不捕获任何内容,它们可以简化为函数指针,因为匿名类没有数据,只有operator()

因此,如果lambda具有相同的签名而没有捕获,则将can cast them放入(非成员)函数指针并将其传递给函数;这绝对有效,请参阅nVIDIA论坛上的this simple example

另一种可能性是使用从类型ID或其他此类密钥到这些类型的实例的运行时映射,或者更确切地说,使用构造函数。也就是说,使用工厂。但我不想深入了解其中的细节,以使这个答案的时间不再像以前那么长;这可能不是一个好主意。

答案 1 :(得分:1)

虽然我认为你不能使用返回设备lambdas的虚函数来实现你想要的东西,你可以通过将静态设备成员函数作为模板参数传递给你的内核来实现类似的功能。下面提供一个例子。请注意,如果您愿意,此示例中的类也可以是结构。

#include <iostream>

// Operation: Element-wise logarithm
class OpLog {
    public:
    __device__ static void foo(int tid, float * x) {
        x[tid] = logf(x[tid]);
    };
};

// Operation: Element-wise exponential
class OpExp {
    public:
    __device__ static void foo(int tid, float * x) {
        x[tid] = expf(x[tid]);
    }
};

// Generic kernel
template < class Op >
__global__ void my_kernel(float * x) {
    int tid = threadIdx.x;
    Op::foo(tid,x);
}

// Driver
int main() {

    using namespace std;

    // length of vector
    int len = 10;

    // generate data
    float * h_x = new float[len];
    for(int i = 0; i < len; i++) {
        h_x[i] = rand()/float(RAND_MAX);
    }

    // inspect data
    cout << "h_x = [";
    for(int j = 0; j < len; j++) {
        cout << h_x[j] << " ";
    }
    cout << "]" << endl;

    // copy onto GPU
    float * d_x;
    cudaMalloc(&d_x, len*sizeof(float));
    cudaMemcpy(d_x, h_x, len*sizeof(float), cudaMemcpyHostToDevice);

    // Take the element-wise logarithm
    my_kernel<OpLog><<<1,len>>>(d_x);

    // get result
    cudaMemcpy(h_x, d_x, len*sizeof(float), cudaMemcpyDeviceToHost);
    cout << "h_x = [";
    for(int j = 0; j < len; j++) {
        cout << h_x[j] << " ";
    }
    cout << "]" << endl;

    // Take the element-wise exponential
    my_kernel<OpExp><<<1,len>>>(d_x);

    // get result
    cudaMemcpy(h_x, d_x, len*sizeof(float), cudaMemcpyDeviceToHost);
    cout << "h_x = [";
    for(int j = 0; j < len; j++) {
        cout << h_x[j] << " ";
    }
    cout << "]" << endl;


}