OpenCL: возможно ли использовать шаблонные объекты в качестве аргументов ядра с Boost :: compute?

Сигнатура функции моего ядра выглядит следующим образом:

template< size_t S, typename Field, typename Type1, typename Type2>
void kernel(const Type1 arg1, const Type2 arg2, Field *results) {
// S is known at compile time
// Field might be float or double
// Type1 is an object holding data and also methods
// Type2 is an object holding data and also methods

// The computation start here

}

Я знаю, что можно использовать подмножество функций c ++ для написания ядра с использованием расширение реализациям OpenCL от AMD, но полученный код ограничен для запуска только на картах AMD.

Стандартная спецификация языка OpenCL для версий, предшествующих 2.0, ограничивает программиста использованием C99 для написания ядер, и я полагаю, что версии 2.1 и 2.2 еще не широко доступны для дистрибутивов Linux. Тем не менее, я нашел Вот тот Boost :: compute позволяет в некоторой степени использовать подмножество функций c ++ в спецификации ядер. Однако не ясно, возможно ли реализовать сигнатуру ядра, как в приведенном выше фрагменте кода, используя Boos :: compute. Насколько возможно реализовать такое ядро? примеры кода будут очень благодарны.

6

Решение

TL; DR: да и нет. На самом деле возможно до некоторой степени писать шаблонные ядра, но они не так мощны, как их аналог CUDA.

Я знаю, что можно использовать подмножество функций c ++ для написания ядра, используя расширение для реализации OpenCL от AMD, но полученный код ограничен для запуска только на картах AMD.

Запрещено работать только на картах AMD. Это ограничено быть скомпилированным только в реализации AMD OpenCL. Например, он должен нормально работать на процессорах Intel, если он скомпилирован в реализации AMD.

Здесь я обнаружил, что Boost :: compute позволяет в некоторой степени использовать подмножество функций c ++ в спецификации ядер. Однако не ясно, возможно ли реализовать сигнатуру ядра, как в приведенном выше фрагменте кода, используя Boos :: compute.

Boost.Compute, по сути, представляет собой причудливый уровень абстракции над C API OpenCL, чтобы сделать его более приятным и менее утомительным для работы, но он по-прежнему дает вам полный доступ к базовому C API. Это означает, что если что-то выполнимо из C API, это теоретически должно быть выполнимо из Boost.Compute.


Поскольку код OpenCL скомпилирован во время выполнения, в отдельном проходе вы не сможете автоматически создавать экземпляры шаблона так, как это делает CUDA во время компиляции. Компилятор CUDA видит код как хоста, так и устройства, и может создавать правильные шаблоны по всему графу вызовов, как если бы это был один блок перевода. Это невозможно в OpenCL, по замыслу.

1. Вам нужно будет вручную создать все возможные шаблоны, указать их имя и отправить их в нужное место.

2. Все типы, используемые в экземплярах шаблона, также должны быть определены в коде OpenCL.

Это ограничение делает ядра с шаблонами OpenCL не совсем бесполезными, но и не очень практичными по сравнению с ядрами CUDA. Их основная цель — избежать дублирования кода.

Другим следствием этого дизайна является то, что нетипичные параметры шаблонов не допускаются в списках аргументов шаблонов ядра (по крайней мере, насколько я знаю, но я бы действительно хотел бы быть неправым в этом!). Это означает, что вам придется понизить параметр шаблона нетипичного типа шаблона ядра в параметр шаблона нетипичного типа одного из аргументы. Другими словами, преобразуйте что-то похожее на это:

template<std::size_t Size, typename Thing>
void kernel(Thing t);

На что-то вроде этого:

template<typename Size, typename Thing>
void kernel(Size* s, Thing t);

А затем различать различные экземпляры, используя что-то похожее по духу на std::integral_constant<std::size_t, 512> (или любой другой тип, который может быть основан на целочисленной константе) в качестве первого аргумента. Указатель здесь — всего лишь хитрость, чтобы избежать требования определения размера на стороне хоста (потому что нас это не волнует).


отказМоя система не поддерживает OpenCL, поэтому я не смог протестировать приведенный ниже код. Это, вероятно, требует некоторой настройки для работы, как ожидалось. Это компилируется, однако.

auto source = R"_cl_source_(
// Type that holds a compile-time size.
template<std::size_t Size>
struct size_constant {
static const std::size_t value = Size;
};

// Those should probably be defined somewhere else since
// the host needs to know about them too.
struct Thing1 {};
struct Thing2 {};

// Primary template, this is where you write your general code.
template<typename Size, typename Field, typename Type1, typename Type2>
kernel void generic_kernel(Size*, const Type1 arg1, const Type2 arg2, Field *results) {
// S is known at compile time
// Field might be float or double
// Type1 is an object holding data and also methods
// Type2 is an object holding data and also methods

// The computation start here
// for (std::size_t s = 0; s < Size::value; ++s)
// ...
}

// Instantiate the template as many times as needed.
// As you can see, this can very quickly become explosive in number of combinations.
template __attribute__((mangled_name(kernel_512_float_thing1_thing2)))
kernel void generic_kernel(size_constant<512>*, const Thing1, const Thing2, float*);

template __attribute__((mangled_name(kernel_1024_float_thing1_thing2)))
kernel void generic_kernel(size_constant<1024>*, const Thing1, const Thing2, float*);

template __attribute__((mangled_name(kernel_1024_double_thing1_thing2)))
kernel void generic_kernel(size_constant<1024>*, const Thing1, const Thing2, double*);
)_cl_source_";

namespace compute = boost::compute;

auto device = compute::system::default_device();
auto context = compute::context { device };
auto queue = compute::command_queue { context, device };

// Build the program.
auto program = compute::program::build_with_source(source, context, "-x clc++");

// Retrieve the kernel entry points.
auto kernel_512_float_thing1_thing2 = program.create_kernel("kernel_512_float_thing1_thing2");
auto kernel_1024_float_thing1_thing2 = program.create_kernel("kernel_1024_float_thing1_thing2");
auto kernel_1024_double_thing1_thing2 = program.create_kernel("kernel_1024_double_thing1_thing2");

// Now you can call these kernels like any other kernel.
// Remember: the first argument is just a dummy.
kernel_512_float_thing1_thing2.set_arg(0, sizeof(std::nullptr_t), nullptr);

// TODO: Set other arguments (not done in this example)

// Finally submit the kernel to the command queue.
auto global_work_size = 512;
auto local_work_size = 64;
queue.enqueue_1d_range_kernel(kernel_512_float_thing1_thing2, 0, global_work_size, local_work_size);

Удачи и не стесняйтесь редактировать этот пост со своими изменениями, чтобы другие могли извлечь из этого пользу!

1

Другие решения

Других решений пока нет …