OpenCL:是否可以使用模板化对象作为 Boost::compute 的内核参数?

OpenCL: Is possible to use templated objects as kernel arguments with 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++ 特性的一个子集来使用 extension 将内核写入 AMD 的 OpenCL 实现,但生成的代码仅限于 运行仅限 AMD 卡。

2.0 之前版本的 OpenCL 语言标准规范限制程序员使用 C99 编写内核,我相信 2.1 和 2.2 版本尚未广泛用于 Linux 发行版。但是,我发现 here Boost::compute 在某种程度上允许在内核规范中使用 c++ 功能的子集。但是,尚不清楚是否可以使用 Boos::compute 实现上述代码片段中的内核签名。在多大程度上可以实现这样的内核?代码示例将不胜感激。

TL;DR:是也不是。在某种程度上确实可以编写模板化内核,但它们的功能远不如 CUDA 内核。

I know that is possible to use a subset of the features of c++ to write the kernel using an extension to the implementations of OpenCL from AMD but the resulting code is restricted to run on AMD cards only.

它不仅限于 运行 在 AMD 卡上。它被限制只能在 AMD 的 OpenCL 实现上编译。例如,它应该 运行 在 Intel CPU 上就好了,只要它是在 AMD 的实现上编译的。

I found here that Boost::compute allows to some extent to use a subset of c++ features in the specification of the kernels. However is not clear if it is possible to implement a kernel signature as in the code snippet above using Boos::compute.

Boost.Compute 本质上是 OpenCL C API 之上的奇特抽象层,使其更易于使用且使用起来不那么乏味,但它仍然使您可以完全访问底层 C API。这意味着如果某些东西在 C API 中是可行的,理论上它在 Boost.Compute.

中也应该是可行的

由于 OpenCL 代码是在 运行时间 编译的,因此您将无法像 CUDA 在编译时间。 CUDA 编译器同时查看主机和设备代码,并且可以在整个调用图中执行适当的模板实例化,就好像它是一个翻译单元一样。按照设计,这在 OpenCL 中是不可能的。

1.您将必须手动实例化您需要的所有可能的模板实例化,破坏它们的名称,然后分派到正确的实例化。

2。模板实例化中使用的所有类型也必须在 OpenCL 代码中定义。

这个限制使得 OpenCL 模板化内核并非完全无用,但与 CUDA 内核相比也不是很实用。它们的主要目的是避免代码重复。

这种设计的另一个后果是 non-type 模板参数在内核模板模板参数列表中是不允许的(至少据我所知,但我 真的 想在这个问题上犯错!)。这意味着您必须将内核模板的 non-type 模板参数降低为 参数 之一的 non-type 模板参数。换句话说,转换看起来像这样的东西:

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> 的东西(或任何其他可以在整数常量上模板化的类型)作为第一个参数来区分不同的实例化。这里的指针只是一个技巧,可以避免需要 host-side 大小类型的定义(因为我们不关心它)。


Disclaimer: my system doesn't support OpenCL, so I could not test the below code. It probably requires some tweaking to work as expected. It does compile, however.

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

祝您好运,随时编辑此 post 您的更改,以便其他人可以从中受益!