【问题标题】:CUDA Paralell prefix sum errorCUDA 并行前缀和错误
【发布时间】:2017-09-25 09:55:40
【问题描述】:

我正在尝试实现三相并行扫描,如 Programming Massively Parallel Processors 第 3 版的第 8 章所述(有任何代码行,但只有指令)。 该算法只允许使用 1 个块,该块中的线程数最多,并且受限于共享内存的大小,因为所有元素都必须适合共享内存

经过一些调试,当我使用大量元素(例如 8192 和 1 个以上的线程)时,在第 3 阶段求和期间遇到了一个问题。

算法的图形概念如下:

下面你可以看到内核代码:

__global__ 
void efficient_Kogge_Stone_scan_kernel(float *X, float *Y, int InputSize) {
    __shared__ float XY[SECTION_SIZE];
    __shared__ float AUS[BLOCK_DIM];
    //int i = blockIdx.x * blockDim.x + threadIdx.x;

    // Keep mind: Partition the input into blockDim.x subsections: i.e. for 8 threads --> 8 subsections

    // collaborative load in a coalesced manner
    for (int j = 0; j < SECTION_SIZE; j += blockDim.x) {
        XY[threadIdx.x + j] = X[threadIdx.x + j];
    }
    __syncthreads();


    // PHASE 1: scan inner own subsection
    // At the end of this phase the last element of each subsection contains the sum of all alements in own subsection
    for (int j = 1; j < SUBSECTION_SIZE; j++) {
        XY[threadIdx.x * (SUBSECTION_SIZE)+j] += XY[threadIdx.x * (SUBSECTION_SIZE)+j - 1];
    }
    __syncthreads();


    // PHASE 2: perform iterative kogge_stone_scan of the last elements of each subsections of XY loaded first in AUS
    AUS[threadIdx.x] = XY[threadIdx.x * (SUBSECTION_SIZE)+(SUBSECTION_SIZE)-1];
    float in;
    for (unsigned int stride = 1; stride < blockDim.x; stride *= 2) {
        __syncthreads();
        if (threadIdx.x >= stride) {
            in = AUS[threadIdx.x - stride];
        }
        __syncthreads();
        if (threadIdx.x >= stride) {
            AUS[threadIdx.x] += in;
        }
    }
    __syncthreads();


    // PHASE 3: each thread adds to its elements the new value of the last element of its predecessor's section
    if (threadIdx.x > 0) {
        for (unsigned int stride = 0; stride < (SUBSECTION_SIZE); stride++) {
            XY[threadIdx.x * (SUBSECTION_SIZE)+stride] += AUS[threadIdx.x - 1];  // <-- 
        }
    }
    __syncthreads();


    // store the result into output vector
    for (int j = 0; j < SECTION_SIZE; j += blockDim.x) {
        Y[threadIdx.x + j] = XY[threadIdx.x + j];
    }
}

如果我在块中使用一个线程和 8192 个元素,它可以完美运行,但如果我使用多个线程,则 XY[5793](或 X[5793] 完成并存储结果时)的结果是错误的. 它有 4096 个元素和一个或多个线程,最多 1024 个线程。 如果我使用 int 而不是浮点数,它甚至适用于具有一个或多个线程的 8192 个元素。

我也尝试在 MATLAB 中进行验证,这些是输出比较:

  • X[5973] = 16788115 ---- MATLAB
  • X[5973] = 16788114 ---- CPU
  • X[5973] = 16788116 ---- GPU

正如我们所看到的,CPU 结果也与 MATLAB 不同,所以在这些结果之后,我认为问题出在浮点加法上,但我告诉你,我用有序的“x.00”填充了输入数组浮点数(例如 {1.00, 2.00, 3.00, 4.00 ..... 8192.00})。

另一个问题是关于性能的,宿主代码总是比内核代码快,有这些配置参数和这些输入,正常吗?

如果你需要完整的源代码,你可以找到它here

【问题讨论】:

  • 不要垃圾标签! CUDA 不是 C!
  • 浮点不是关联的。不能保证您将不同其他浮点数的数字列表相加,结果将是相同的
  • 首先考虑使用使用整数的实现,而不必考虑浮点精度问题(看起来您的总和可以很好地适合 32 位整数)。另外 - 你似乎在那里使用了很多__syncthreads()。最后 - 为什么只有 1 个街区?你永远不可能只用一个块来获得好的性能。
  • 是的,你是对的@einpoklum 实际上,我尝试使用整数,结果没有问题。在分层算法如何在我可以使用多个块的情况下执行前缀和之后,该算法只是一个要理解的原语。现在我使用整数实现了分层算法,我看到从 65000 个元素开始,内核变得比主机更快。 __syncthreads() 是必需的,因为在共享向量中,多个线程可以同时在代码的不同部分读取和写入。

标签: cuda floating-point prefix-sum


【解决方案1】:

8192 是 2^13

sum(1..8192) 在 8192^2/2 附近:8192*8193/2,也就是比 2^25 多一点。

因此您需要 26 位来表示它(请参阅下面的注释)。

单精度 IEEE 754 浮点数只有 24 位有效位,因此,取决于求和的执行方式(以何种顺序),最终取决于舍入方向(通常是默认舍入到最近,并列到偶数),然后是结果可能会有所不同。

注意,严格来说,精确的和可以用浮点数表示而无需四舍五入,因为最后 12 位为零,因此有效数字仅跨越 14 位。但部分和就不是这样了。

【讨论】:

    【解决方案2】:

    第一次扫描可能有问题:

    XY[threadIdx.x * (SUBSECTION_SIZE)+j] += XY[threadIdx.x * (SUBSECTION_SIZE)+j - 1];
    

    这可能导致共享内存中元素的读取不一致。当您阅读前一个元素时,不能保证任何其他线程都没有更新该值。

    尝试通过将值存储在寄存器中来将此部分分成几部分。示例:

    int t =  XY[threadIdx.x * (SUBSECTION_SIZE)+j - 1];
     __syncthreads();
     XY[threadIdx.x * (SUBSECTION_SIZE)+j] += t; 
    

    【讨论】:

    • 每个线程都在自己的小节中工作,不会触及其他线程的小节,所以问题仍然存在,我试过了。
    猜你喜欢
    • 2018-09-18
    • 2016-04-14
    • 2012-02-23
    • 2016-06-19
    • 1970-01-01
    • 2023-03-17
    • 2017-12-12
    • 1970-01-01
    • 2013-10-29
    相关资源
    最近更新 更多