CUDA:忘记内核启动配置不会导致NVCC编译器警告或错误

CUDA:忘记内核启动配置不会导致NVCC编译器警告或错误,cuda,nvcc,Cuda,Nvcc,当我尝试使用函数指针调用CUDA内核(一个\uuuuu全局\uuuuu函数)时,一切看起来都很正常。但是,如果我在调用内核时忘记提供启动配置,NVCC将不会导致错误或警告,但如果我试图运行它,程序将编译并崩溃 __global__ void bar(float x) { printf("foo: %f\n", x); } typedef void(*FuncPtr)(float); void invoker(FuncPtr func) { func<<<1, 1&g

当我尝试使用函数指针调用CUDA内核(一个
\uuuuu全局\uuuuu
函数)时,一切看起来都很正常。但是,如果我在调用内核时忘记提供启动配置,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();
\uuuu全局\uuuuu无效条(float x){printf(“foo:%f\n,x);}
类型定义无效(*FuncPtr)(浮动);
无效调用程序(FuncPtr func)
{
func(1.0);
}
调用程序(bar);
cudaDeviceSynchronize();
编译并运行上面的代码。一切都会好起来的。然后,删除内核的启动配置(即)。代码可以很好地编译,但是当您试图运行它时,它会崩溃

__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();
知道发生了什么吗?这是一个bug,还是我不应该传递
\uuu全局\uuu
函数的指针

CUDA版本:8.0

操作系统版本:Debian(测试回购)
GPU: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);
}
i、 e.前端可以检测到
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并自己显式管理内核启动