如何收集二进制内核以分发专有代码
How to collect binary kernels to distribute proprietary code
我有一段代码,其中包含我不想在源代码中分发的专有技术。解决方案之一是提供一堆预编译内核,并根据用户的硬件选择正确的二进制文件。
如何用最少的二进制文件和最少的机器覆盖大多数用户(AMD 和英特尔,因为 Nvidia 可以使用 CUDA 代码)运行 我的离线编译器?是否有可以使用相同二进制文件的 GPU 系列? CUDA 编译器可以针对不同的架构进行编译,那么 OpenCL 呢?二进制兼容性数据似乎没有很好的记录,但也许有人为自己收集了这些数据。
我知道有 SPIR,但旧硬件不支持它。
如果有人发现了这个问题并且做的比我少,这里是我的实现细节。我制作了一个将内核编译成文件的工具,然后我将所有这些二进制文件收集到一个 C 数组中,以包含到主应用程序中:
const char* binaries[] = {
//kernels/HD Graphics 4000
"\x62\x70\x6c\x69\x73\x74\x30\x30\xd4\x01\x02\x03"
"\x04\x05\x06\x07\x08\x5f\x10\x0f\x63\x6c\x42\x69"
"\x6e\x61\x72\x79\x56\x65\x72\x73\x69\x6f\x6e\x5c"
...
"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x47\xe0"
,
//here more kernels
};
size_t binaries_sizes[] = {
204998,
205907,
...
};
然后我使用以下代码迭代所有内核(我没有发明比试错更聪明的东西,选择第一个成功构建的内核,可能有更好的解决方案):
int e3 = -1;
int i = 0;
while (e3 != CL_SUCCESS) {
if (i == lenof(binaries)) {
throw Error();
}
program = clCreateProgramWithBinary(context, 1, &deviceIds[devIdx], &binaries_sizes[i],
(const unsigned char**)&binaries[i],
nullptr, &e3);
if (e3 != CL_SUCCESS) {
++i;
continue;
}
int e4 = clBuildProgram(program, 1, &deviceIds[devIdx],
"", nullptr, nullptr);
e3 = e4;
++i;
}
很遗憾,没有针对您的问题的标准解决方案。 OpenCL 是独立于平台的,并且没有标准的方法(除了 SPIR)来处理这个问题。每个供应商在内部决定不同的编译器工具链,甚至这可能会在同一驱动程序的多个版本或不同设备之间发生变化。
您可以向内核添加一些元数据来识别您为哪个平台编译它,这将节省您的试验和错误部分(即,而不是只存储二进制文件和 binaries_size,您还可以存储 binary_platform 和 binary_device,然后遍历这些数组以查看您应该加载哪个二进制文件)。
最适合您的解决方案是 SPIR(或新的 SPIRV),它们是中间表示,然后可以由 OpenCL 驱动程序 "re-compiled" 到实际的架构指令集。
如果您将二进制文件存储在 SPIRV 中,并且可以访问 to/knowledge 一些编译器魔法,您可以使用翻译器工具取回 LLVM-IR,然后编译到其他平台,例如 AMD 或 PTX,使用LLVM 基础设施(参见 https://github.com/KhronosGroup/SPIRV-LLVM)
我有一段代码,其中包含我不想在源代码中分发的专有技术。解决方案之一是提供一堆预编译内核,并根据用户的硬件选择正确的二进制文件。
如何用最少的二进制文件和最少的机器覆盖大多数用户(AMD 和英特尔,因为 Nvidia 可以使用 CUDA 代码)运行 我的离线编译器?是否有可以使用相同二进制文件的 GPU 系列? CUDA 编译器可以针对不同的架构进行编译,那么 OpenCL 呢?二进制兼容性数据似乎没有很好的记录,但也许有人为自己收集了这些数据。
我知道有 SPIR,但旧硬件不支持它。
如果有人发现了这个问题并且做的比我少,这里是我的实现细节。我制作了一个将内核编译成文件的工具,然后我将所有这些二进制文件收集到一个 C 数组中,以包含到主应用程序中:
const char* binaries[] = {
//kernels/HD Graphics 4000
"\x62\x70\x6c\x69\x73\x74\x30\x30\xd4\x01\x02\x03"
"\x04\x05\x06\x07\x08\x5f\x10\x0f\x63\x6c\x42\x69"
"\x6e\x61\x72\x79\x56\x65\x72\x73\x69\x6f\x6e\x5c"
...
"\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x47\xe0"
,
//here more kernels
};
size_t binaries_sizes[] = {
204998,
205907,
...
};
然后我使用以下代码迭代所有内核(我没有发明比试错更聪明的东西,选择第一个成功构建的内核,可能有更好的解决方案):
int e3 = -1;
int i = 0;
while (e3 != CL_SUCCESS) {
if (i == lenof(binaries)) {
throw Error();
}
program = clCreateProgramWithBinary(context, 1, &deviceIds[devIdx], &binaries_sizes[i],
(const unsigned char**)&binaries[i],
nullptr, &e3);
if (e3 != CL_SUCCESS) {
++i;
continue;
}
int e4 = clBuildProgram(program, 1, &deviceIds[devIdx],
"", nullptr, nullptr);
e3 = e4;
++i;
}
很遗憾,没有针对您的问题的标准解决方案。 OpenCL 是独立于平台的,并且没有标准的方法(除了 SPIR)来处理这个问题。每个供应商在内部决定不同的编译器工具链,甚至这可能会在同一驱动程序的多个版本或不同设备之间发生变化。
您可以向内核添加一些元数据来识别您为哪个平台编译它,这将节省您的试验和错误部分(即,而不是只存储二进制文件和 binaries_size,您还可以存储 binary_platform 和 binary_device,然后遍历这些数组以查看您应该加载哪个二进制文件)。
最适合您的解决方案是 SPIR(或新的 SPIRV),它们是中间表示,然后可以由 OpenCL 驱动程序 "re-compiled" 到实际的架构指令集。 如果您将二进制文件存储在 SPIRV 中,并且可以访问 to/knowledge 一些编译器魔法,您可以使用翻译器工具取回 LLVM-IR,然后编译到其他平台,例如 AMD 或 PTX,使用LLVM 基础设施(参见 https://github.com/KhronosGroup/SPIRV-LLVM)