CUDA:忘记内核启动配置不会导致 NVCC 编译器警告或错误
CUDA: Forgetting kernel launch configuration does not result in NVCC compiler warning or error
当我尝试使用函数指针调用 CUDA 内核(__global__
函数)时,一切似乎都运行良好。但是,如果我在调用内核时忘记提供启动配置,NVCC 不会导致错误或警告,但如果我尝试 运行 它,程序将编译然后崩溃。
__global__ void bar(float x) { printf("foo: %f\n", x); }
typedef void(*FuncPtr)(float);
void invoker(FuncPtr func)
{
func<<<1, 1>>>(1.0);
}
invoker(bar);
cudaDeviceSynchronize();
编译并运行以上。一切都会好起来的。然后,删除内核的启动配置(即 <<<1, 1>>>)。该代码可以正常编译,但是当您尝试 运行 它时它会崩溃。
知道发生了什么事吗?这是一个错误,还是我不应该传递 __global__
函数的指针?
CUDA 版本:8.0
OS 版本:Debian(测试回购)
显卡:NVIDIA GeForce 750M
如果我们使用稍微复杂一点的重现版本,并查看 CUDA 工具链前端发出的代码,就可以了解发生了什么:
#include <cstdio>
__global__ void bar_func(float x) { printf("foo: %f\n", x); }
typedef void(*FuncPtr)(float);
void invoker(FuncPtr passed_func)
{
#ifdef NVCC_FAILS_HERE
bar_func(1.0);
#endif
bar_func<<<1,1>>>(1.0);
passed_func(1.0);
passed_func<<<1,1>>>(2.0);
}
所以让我们用几种方法编译它:
$ nvcc -arch=sm_52 -c -DNVCC_FAILS_HERE invoker.cu
invoker.cu(10): error: a __global__ function call must be configured
即前端可以检测到 bar_func
是一个全局函数,需要启动参数。另一次尝试:
$ nvcc -arch=sm_52 -c -keep invoker.cu
如您所见,这不会产生编译错误。让我们看看发生了什么:
void bar_func(float x) ;
# 5 "invoker.cu"
typedef void (*FuncPtr)(float);
# 7 "invoker.cu"
void invoker(FuncPtr passed_func)
# 8 "invoker.cu"
{
# 12 "invoker.cu"
(cudaConfigureCall(1, 1)) ? (void)0 : (bar_func)((1.0));
# 13 "invoker.cu"
passed_func((2.0));
# 14 "invoker.cu"
(cudaConfigureCall(1, 1)) ? (void)0 : passed_func((3.0));
# 15 "invoker.cu"
}
标准内核调用语法 <<<>>>
被扩展为对 cudaConfigureCall
的内联调用,然后调用主机包装函数。主机包装器具有启动内核所需的 API 内部结构:
void bar_func( float __cuda_0)
# 3 "invoker.cu"
{__device_stub__Z8bar_funcf( __cuda_0); }
void __device_stub__Z8bar_funcf(float __par0)
{
if (cudaSetupArgument((void *)(char *)&__par0, sizeof(__par0), (size_t)0UL) != cudaSuccess) return;
{ volatile static char *__f __attribute__((unused)); __f = ((char *)((void ( *)(float))bar_func));
(void)cudaLaunch(((char *)((void ( *)(float))bar_func)));
};
}
因此存根仅处理参数并通过 cudaLaunch
启动内核。它不处理启动配置
崩溃的根本原因(实际上是未检测到的运行时 API 错误)是内核启动时没有预先配置。显然,这是因为 CUDA 前端(和 C++ 就此而言)无法在编译时进行指针自省并检测到您的函数指针是用于调用内核的存根函数。
我认为描述这一点的唯一方法是运行时 API 和编译器的“限制”。我不会说你在做什么是错的,但我可能会使用驱动程序 API 并在这种情况下自己显式管理内核启动。
当我尝试使用函数指针调用 CUDA 内核(__global__
函数)时,一切似乎都运行良好。但是,如果我在调用内核时忘记提供启动配置,NVCC 不会导致错误或警告,但如果我尝试 运行 它,程序将编译然后崩溃。
__global__ void bar(float x) { printf("foo: %f\n", x); }
typedef void(*FuncPtr)(float);
void invoker(FuncPtr func)
{
func<<<1, 1>>>(1.0);
}
invoker(bar);
cudaDeviceSynchronize();
编译并运行以上。一切都会好起来的。然后,删除内核的启动配置(即 <<<1, 1>>>)。该代码可以正常编译,但是当您尝试 运行 它时它会崩溃。
知道发生了什么事吗?这是一个错误,还是我不应该传递 __global__
函数的指针?
CUDA 版本:8.0
OS 版本:Debian(测试回购) 显卡:NVIDIA GeForce 750M
如果我们使用稍微复杂一点的重现版本,并查看 CUDA 工具链前端发出的代码,就可以了解发生了什么:
#include <cstdio>
__global__ void bar_func(float x) { printf("foo: %f\n", x); }
typedef void(*FuncPtr)(float);
void invoker(FuncPtr passed_func)
{
#ifdef NVCC_FAILS_HERE
bar_func(1.0);
#endif
bar_func<<<1,1>>>(1.0);
passed_func(1.0);
passed_func<<<1,1>>>(2.0);
}
所以让我们用几种方法编译它:
$ nvcc -arch=sm_52 -c -DNVCC_FAILS_HERE invoker.cu
invoker.cu(10): error: a __global__ function call must be configured
即前端可以检测到 bar_func
是一个全局函数,需要启动参数。另一次尝试:
$ nvcc -arch=sm_52 -c -keep invoker.cu
如您所见,这不会产生编译错误。让我们看看发生了什么:
void bar_func(float x) ;
# 5 "invoker.cu"
typedef void (*FuncPtr)(float);
# 7 "invoker.cu"
void invoker(FuncPtr passed_func)
# 8 "invoker.cu"
{
# 12 "invoker.cu"
(cudaConfigureCall(1, 1)) ? (void)0 : (bar_func)((1.0));
# 13 "invoker.cu"
passed_func((2.0));
# 14 "invoker.cu"
(cudaConfigureCall(1, 1)) ? (void)0 : passed_func((3.0));
# 15 "invoker.cu"
}
标准内核调用语法 <<<>>>
被扩展为对 cudaConfigureCall
的内联调用,然后调用主机包装函数。主机包装器具有启动内核所需的 API 内部结构:
void bar_func( float __cuda_0)
# 3 "invoker.cu"
{__device_stub__Z8bar_funcf( __cuda_0); }
void __device_stub__Z8bar_funcf(float __par0)
{
if (cudaSetupArgument((void *)(char *)&__par0, sizeof(__par0), (size_t)0UL) != cudaSuccess) return;
{ volatile static char *__f __attribute__((unused)); __f = ((char *)((void ( *)(float))bar_func));
(void)cudaLaunch(((char *)((void ( *)(float))bar_func)));
};
}
因此存根仅处理参数并通过 cudaLaunch
启动内核。它不处理启动配置
崩溃的根本原因(实际上是未检测到的运行时 API 错误)是内核启动时没有预先配置。显然,这是因为 CUDA 前端(和 C++ 就此而言)无法在编译时进行指针自省并检测到您的函数指针是用于调用内核的存根函数。
我认为描述这一点的唯一方法是运行时 API 和编译器的“限制”。我不会说你在做什么是错的,但我可能会使用驱动程序 API 并在这种情况下自己显式管理内核启动。