我有一个CUDA内核,每个线程遍历一棵树。因此,我有一个循环,循环直到线程到达叶子。在树的每一步中,它都会检查应该选择哪个孩子。
代码如下:
__global__ void search(float* centroids, float* features, int featureCount, int *votes)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
if(tid < featureCount)
{
int index = 0;
while (index < N)
{
votes[tid] = index;
int childIndex = index * CHILDREN + 1;
float minValue = FLT_MAX;
if(childIndex >= (N-CHILDREN)) break;
for(int i = 0; i < CHILDREN; i++)
{
int centroidIndex = childIndex + i;
float value = distance(centroids, features, centroidIndex, tid);
if(value < minValue)
{
minValue = value;
index = childIndex + i;
}
}
}
tid += blockDim.x * gridDim.x;
}
}
__device__ float distance(float* a, float* b, int aIndex, int bIndex)
{
float sum = 0.0f;
for(int i = 0; i < FEATURESIZE; i++)
{
float val = a[aIndex + i] - b[bIndex + i];
sum += val * val;
}
return sum;
}
此代码进入无限循环。这就是我觉得奇怪的事情。 如果我改变距离方法以返回一个常量,它就可以工作(即在树中向左移动)。
我是否错过了CUDA中的循环或者是否有一些我看不到的隐藏错误?因为我没有看到代码如何进入无限循环。
答案 0 :(得分:4)
CUDA C ++中的循环与C ++中的循环具有相同的语义,因此代码中必定存在错误。调试它的一个策略是在主机上执行此操作。
首先,因为您的代码是标量(例如,它不包含对__syncthreads
的调用),您可以将其重构为__host__ __device__
函数。
distance
不包含特定于CUDA的标识符或函数,因此您可以简单地添加__host__
:
__host__ __device__ float distance(float* a, float* b, int aIndex, int bIndex);
要重构您的search
函数,请将tid
(取决于特定于CUDA的标识符threadIndex
等)提升到参数之外,并将其设为{{ 1}}功能:
__host__ __device__
现在编写一个__host__ __device__ void search(int tid, float* centroids, float* features, int featureCount, int *votes)
{
if(tid < featureCount)
{
int index = 0;
while (index < N)
{
votes[tid] = index;
int childIndex = index * CHILDREN + 1;
float minValue = FLT_MAX;
if(childIndex >= (N-CHILDREN)) break;
for(int i = 0; i < CHILDREN; i++)
{
int centroidIndex = childIndex + i;
float value = distance(centroids, features, centroidIndex, tid);
if(value < minValue)
{
minValue = value;
index = childIndex + i;
}
}
}
}
}
函数,除了计算__global__
并调用tid
之外什么都不做:
search
因为__global__ void search_kernel(float *centroids, float features, int featureCount, int *votes)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
search(tid, centroids, features, featureCount, votes);
}
现在是search
,你可以通过从CPU调用来调试它,模仿内核启动会做什么:
__host__ __device__
它应该完全像在设备上一样挂在主机上。坚持for(int tid = 0; tid < featureCount; ++tid)
{
search(tid, centroids, features, featureCount, votes);
}
在里面找出原因。当然,您需要确保制作数组的主机端副本,例如printf
,因为主机无法取消引用指向设备内存的指针。
即使centroids
可用于具有较新硬件的printf
函数,您可能更喜欢这种方法的原因是从内核调用__device__
直到 之后内核退休。如果内核永远不会退休(因为在你的情况下显然没有),那么你的调试输出将永远不会出现在屏幕上。