У меня есть виртуальная функция, которая возвращает другую лямбду в зависимости от производного класса:

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;
        };
    }
};

Затем я хочу передать эту лямбду ядру 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

Как вы можете видеть, я объявляю свою лямбду как __device__, но метод foo() сохраняет ее в std::function для ее возврата. В результате то, что передается kernel(), является адресом хоста и, конечно, он не работает. Я думаю, это моя проблема, верно? Итак, мои вопросы:

  • Возможно ли как-то создать __device__ std::function и вернуть его из метода foo()?

  • Если это невозможно, есть ли другой способ динамически выбрать лямбду и передать ее ядру CUDA? Жесткое кодирование множественных вызовов kernel() со всеми возможными лямбдами не является вариантом.

До сих пор, из проведенного мной быстрого исследования, CUDA не имеет / не поддерживает необходимый синтаксис, необходимый для того, чтобы функция возвращала лямбду устройства. Я просто надеюсь, что я не прав. :) Любые идеи?

Заранее спасибо

4
AstrOne 28 Май 2017 в 21:26

2 ответа

Лучший ответ

Прежде чем ответить, я должен спросить, не является ли ваш вопрос проблемой XY . То есть я по умолчанию скептически отношусь к тому, что у людей есть хорошее оправдание для выполнения кода через лямбда-выражения / указатели функций на устройстве.

Но я не буду уклоняться от твоего вопроса так ...

Возможно ли как-то создать __device__ std::function и вернуть его из метода foo ()?

Краткий ответ: нет, попробуйте что-нибудь еще.

Более длинный ответ: Если вы хотите реализовать большую часть стандартной библиотеки на стороне устройства, то, возможно, у вас может быть класс std::function, похожий на сторону устройства. Но я не уверен, что это даже возможно (вполне возможно, нет), и в любом случае - это выходит за рамки возможностей всех, кроме очень опытных разработчиков библиотек. Итак, сделай что-нибудь еще.

Если это невозможно, есть ли другой способ динамически выбрать лямбду и передать ее ядру CUDA? Жесткое кодирование множественных вызовов kernel () со всеми возможными лямбдами не допускается.

Во-первых, помните, что лямбды - это, по сути, анонимные классы - и, таким образом, если они ничего не захватывают, их можно приводить к указателям на функции, поскольку у анонимных классов нет данных, только operator().

Поэтому, если лямбды имеют одинаковую подпись и не имеют перехвата, вы можете привести их в указатель на функцию (не являющуюся членом) и передать эти функции; и это определенно работает, см. этот простой пример на форумах nVIDIA.

Другая возможность - использовать отображение во время выполнения из идентификаторов типов или других подобных ключей в экземпляры этих типов, или, скорее, в конструкторы. То есть используя factory . Но я не хочу вдаваться в детали этого, чтобы не сделать этот ответ дольше, чем он есть; и это, вероятно, не очень хорошая идея.

2
einpoklum 29 Май 2017 в 15:07

Хотя я не думаю, что вы можете достичь того, чего хотите, используя виртуальные функции, которые возвращают лямбда-символы устройства, вы можете добиться чего-то похожего, передав статическую функцию-член устройства в качестве параметра шаблона в ваше ядро. Пример приведен ниже. Обратите внимание, что классы в этом примере также могут быть структурами, если вы предпочитаете.

#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;


}
1
tdoublep 12 Фев 2018 в 20:13