问题描述
我试图让一个内核总结一个数组的所有元素来工作。内核旨在以每个块 256 个线程和任意数量的块启动。传入的数组长度a
总是512的倍数,实际上是#blocks * 512。内核的一个块应该总结“它的”512个元素(256个线程可以使用这个算法总结512个元素),将结果存储在out[blockIdx.x]
. 中的值的最终总和out
以及块的结果将在主机上完成。
该内核最多可用于 6 个块,即最多 3072 个元素。但是以超过 6 个块启动它会导致第一个块计算出比其他块更大的错误结果(即out = {572, 512, 512, 512, 512, 512, 512}
),这个错误的结果是可重现的,多次执行的错误值是相同的。
我想这意味着我的代码中某处存在结构错误,这与blockIdx.x
,但唯一的用途是计算blockStart
,并且这似乎是正确的计算,对于第一个块也是如此。
我验证了我的主机代码是否为内核计算了正确的块数并传入了一个正确大小的数组。那不是问题。
当然,我在 stackoverflow 上阅读了很多类似的问题,但似乎没有一个描述我的问题(参见此处或此处)
内核是通过managedCuda(C#)调用的,我不知道这是否是一个问题。
硬件
我使用具有以下规格的 MX150:
- 修订号:6.1
- 全局内存总量:2147483648
- 每个块的总共享内存:49152
- 每个块的总寄存器数:65536
- 经纱尺寸:32
- 每个块的最大线程数:1024
- 最大块数:2147483648
- 多处理器数量:3
代码
核心
__global__ void Vector_Reduce_As_Sum_Kernel(float* out, float* a)
{
int tid = threadIdx.x;
int blockStart = blockDim.x * blockIdx.x * 2;
int i = tid + blockStart;
int leftSumElementIdx = blockStart + tid * 2;
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
__syncthreads();
if (tid < 128)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if(tid < 64)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if (tid < 32)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if (tid < 16)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if (tid < 8)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if (tid < 4)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if (tid < 2)
{
a[i] = a[leftSumElementIdx] + a[leftSumElementIdx + 1];
}
__syncthreads();
if (tid == 0)
{
out[blockIdx.x] = a[blockStart] + a[blockStart + 1];
}
}
内核调用
//Get the cuda kernel
//PathToPtx and MangledKernelName must be replaced
CudaContext cntxt = new CudaContext();
CUmodule module = cntxt.LoadModule("pathToPtx");
CudaKernel vectorReduceAsSumKernel = new CudaKernel("MangledKernelName", module, cntxt);
//Get an array to reduce
float[] array = new float[4096];
for(int i = 0; i < array.Length; i++)
{
array[i] = 1;
}
//Calculate execution info for the kernel
int threadsPerBlock = 256;
int numOfBlocks = array.Length / (threadsPerBlock * 2);
//Memory on the device
CudaDeviceVariable<float> m_d = array;
CudaDeviceVariable<float> out_d = new CudaDeviceVariable<float>(numOfBlocks);
//Give the kernel necessary execution info
vectorReduceAsSumKernel.BlockDimensions = threadsPerBlock;
vectorReduceAsSumKernel.GridDimensions = numOfBlocks;
//Run the kernel on the device
vectorReduceAsSumKernel.Run(out_d.DevicePointer, m_d.DevicePointer);
//Fetch the result
float[] out_h = out_d;
//Sum up the partial sums on the cpu
float sum = 0;
for(int i = 0; i < out_h.Length; i++)
{
sum += out_h[i];
}
//Verify the correctness
if(sum != 4096)
{
throw new Exception("Thats the wrong result!");
}
更新:
非常有帮助且唯一的答案确实解决了我所有的问题。谢谢!问题是无法预料的比赛条件。
重要提示:
managedCuda 的作者在评论中指出,所有 NPP 方法确实已经在 managedCuda ( using ManagedCuda.NPP.NPPsExtensions;
) 中实现。我没有意识到这一点,我想很多人都在阅读这个问题。