我正在编写一些用于在CUDA上激活神经网络的代码,而且我遇到了一个问题。我没有得到进入给定神经元的权重的正确总和。
所以这是内核代码,我将尝试用变量更清楚地解释它。
__global__ void kernelSumWeights(float* sumArray, float* weightArray, int2* sourceTargetArray, int cLength)
{
int nx = threadIdx.x + TILE_WIDTH*threadIdx.y;
int index_in = (blockIdx.x + gridDim.x*blockIdx.y)*TILE_WIDTH*TILE_WIDTH + nx;
if(index_in < cLength)
{
sumArray[sourceTargetArray[index_in].y] += fabs(weightArray[index_in]);
//__threadfence();
__threadfence_block();
}
}
首先,网络中的连接数为cLength
。对于每个连接,都有源神经元和目标神经元,以及该连接的权重。 SourceTargetArray
包含该信息。因此i
的索引sourceTargetArray
是连接i
的源神经元索引,以及连接i
的目标神经元索引。 weightArray
包含权重信息(因此i
的索引weightArray
对应于连接i
)。
如您所见,SumArray
是我存储金额的地方。因此,内核将sumArray
(在连接i
的目标神经元索引处)增加连接权重i
的绝对值。直观地,对于到神经元的所有传入连接,将所有权重相加。这就是我要用这个内核做的所有事情。最后,我将使用此总和来标准化权重。
问题在于它是错误的。我已经连续完成了这个,答案是不同的。答案不同,通常是大约12-15倍(所以正确答案将是700.0,我得到的是50s范围内的东西)。
您可以看到我添加了__threadfence()
(和__threadfence_block()
以尝试确保每个线程都没有同时完成写入操作。我不确定这是否是我的代码的问题。我已经确保权重数组与我测试的串行版本相同,并且源/目标信息也是相同的。我做错了什么?
编辑:作为参考,在{CUDA编程指南v3.1附录B.5记忆围栏功能
中描述了__threadfence()
usaged
答案 0 :(得分:4)
+=
不是原子的=&gt;不是线程安全的。使用atomicAdd。
此外,您应该避免写入相同的存储单元。问题是这些调用将被序列化,线程将排成一行并等待彼此。如果您无法避免此操作,请尝试将算法分为两个阶段:单个计算和合并。并行合并可以非常有效地实现。
答案 1 :(得分:3)
你需要做一个减少。
将分配给每个线程的元素求和并将结果放在一个数组中,cache [threadsPerBlock]然后__Syncthreads
现在通过添加连续的相邻小计来减少生成的小计:
int cacheIndex = threadIdx.x;
int i = blockDim.x / 2;
while (i != 0)
{
if (cacheIndex < i)
cache[cacheIndex] += cache[cacheIndex] + 1;
__syncthreads;
i /= 2;
}
}
以下套牌详细解释了这一点:
http://developer.download.nvidia.com/compute/cuda/1_1/Website/projects/reduction/doc/reduction.pdf
此处的示例代码如下:
http://www.nvidia.com/object/cuda_sample_data-parallel.html
在“CUDA BY Example”(这是代码片段的来源)中也有很好的解释。
这种方法有一个很大的警告。添加的顺序不会与串行代码的顺序相同。添加浮点数不是可交换的,因此舍入误差可能会导致略有不同的结果。