繁体   English   中英

为什么我的 CUDA warp shuffle sum 对一个 shuffle 步骤使用错误的偏移量?

[英]Why is my CUDA warp shuffle sum using the wrong offset for one shuffle step?

编辑:我已在https://developer.nvidia.com/nvidia_bug/3711214将此作为错误提交。

我正在编写一个数值模拟程序,它在发布模式下给出了略微不正确的结果,但在调试模式下看似正确的结果。 原始程序使用 curand 进行随机采样,但我已将其简化为更简单且更具确定性的 MVCE,它启动单个 kernel 的 1 个块 * 1 个经纱(32 个线程),其中每个线程:

  • 使用可能会变得扭曲发散的循环执行计算,尤其是在接近尾声时,因为一些线程在其他线程之前完成了它们的任务。
  • 将线程重新同步在一起。
  • 尝试使用经线中的其他线程对数据进行蝶式洗牌以获得单个总和。
  • [在 MVCE 中不需要] 线程 0 会将总和写回全局 memory 以便可以将其复制到主机
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>


__global__ void test_kernel()
{

    int cSteps = 0;
    int cIters = 0;
    float pos = 0;

    //curandState localState = state[threadIdx.x];

    while (true) {
        float rn = threadIdx.x * 0.01 + 0.001;
        pos += rn;
        cSteps++;
        if (pos > 1.0f) {
            pos = 0;
            cIters++;
            if (cSteps > 1024) {
                break;
            }
        }
    }

    printf(" 0: Th %d cI %d\n", threadIdx.x, cIters);
    __syncthreads();
    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 1, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 1, 32);

    printf(" 1: Th %2d cI %d\n", threadIdx.x, cIters);
    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 2, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 2, 32);

    printf(" 2: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 4, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 4, 32);

    printf(" 4: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 8, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 8, 32);

    printf(" 8: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 16, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 16, 32);

    printf("16: Th %2d cI %d\n", threadIdx.x, cIters);
}

int main()
{
    test_kernel <<<1, 32>>> ();
    return 0;
}

在调试模式下,随机播放按预期工作。 我看到每个线程都有自己的值:

 0: Th 0 cI 2
 0: Th 1 cI 12
 0: Th 2 cI 22
 0: Th 3 cI 32
 0: Th 4 cI 41
// ...

在第一次 shuffle xor 1 之后,每对线程都同意相同的数字:

 1: Th  0 cI 14
 1: Th  1 cI 14
 1: Th  2 cI 54
 1: Th  3 cI 54

在 shuffle xor 2 之后,每组四个线程都同意:

 2: Th  0 cI 68
 2: Th  1 cI 68
 2: Th  2 cI 68
 2: Th  3 cI 68
 2: Th  4 cI 223
 2: Th  5 cI 223
 2: Th  6 cI 223
 2: Th  7 cI 223

等等。 在最后一次 shuffle 之后,warp 中的所有线程都同意相同的值 (4673)。

一旦我启用发布模式,我得到的结果是微妙的垃圾。 进入 shuffle 的值是相同的,并且第一轮 shuffle 之后的值与 debug build 一致(并且在每对中都像以前一样一致)。 一旦我执行 shuffle xor 2,结果就会分崩离析:

 2: Th  0 cI 28
 2: Th  1 cI 28
 2: Th  2 cI 108
 2: Th  3 cI 108
 2: Th  4 cI 186
 2: Th  5 cI 186
 2: Th  6 cI 260
 2: Th  7 cI 260

事实上,如果将随机序列替换为这个特定的损坏序列,则调试构建(和手动检查)将产生的确切 output :

    printf(" 0: Th %d cI %d\n", threadIdx.x, cIters);
    __syncthreads();
    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 1, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 1, 32);

    printf(" 1: Th %2d cI %d\n", threadIdx.x, cIters);
    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 1, 32); // 2 changed to 1
    cIters += __shfl_xor_sync(0xffffffff, cIters, 1, 32); // 2 changed to 1

    printf(" 2: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 4, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 4, 32);

    printf(" 4: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 8, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 8, 32);

    printf(" 8: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 16, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 16, 32);

output 的完整差异在这里

软硬件环境如下:

  • GA103 3080Ti(移动),制造商推荐的时钟,16 G VRAM。 机器似乎没有与其他 Cuda 程序一起损坏(使用 primegrid-CUDA 测试并通过双重检查验证了任务)

  • CUDA 11.0

  • MVSC 主机编译器 14.29.30133

  • 完整的调试命令行如下:

     "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\bin\nvcc.exe" -gencode=arch=compute_52,code=\"sm_52,compute_52\" --use-local-env -ccbin "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\HostX86\x64" -x cu -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\include" -G --keep-dir x64\Debug -maxrregcount=0 --machine 64 --compile -cudart static -g -DWIN32 -DWIN64 -D_DEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /Od /Fdx64\Debug\vc142.pdb /FS /Zi /RTC1 /MDd " -o x64\Debug\kernel.cu.obj "C:\Users\[username]\source\repos\BugRepro\BugRepro\kernel.cu"
  • 完整的发布命令行如下:

     C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\bin\nvcc.exe" -gencode=arch=compute_52,code=\"sm_52,compute_52\" --use-local-env -ccbin "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\HostX86\x64" -x cu -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\include" -I"C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0\include" --keep-dir x64\Release -maxrregcount=0 --machine 64 --compile -cudart static -DWIN32 -DWIN64 -DNDEBUG -D_CONSOLE -D_MBCS -Xcompiler "/EHsc /W3 /nologo /O2 /Fdx64\Release\vc142.pdb /FS /Zi /MD " -o x64\Release\kernel.cu.obj "C:\Users\[username]\source\repos\BugRepro\BugRepro\kernel.cu"

我没有解决办法尝试的事情:

  • 添加/删除syncthreads调用(显示一个,以及在shuffle调用之间),即使它们不应该是必要的,因为每个shuffle都是同步的
  • 将计算能力更改为 8.0 以更好地匹配我的卡
  • 强制 GPU 上的基本时钟
  • 以相反的顺序洗牌 (16/8/4/2/1)
  • 使用 __shfl_down_sync 而不是 xor,具有相同的偏移模式。

让每个线程写入全局 memory 然后在主机 CPU 上求和确实会产生正确的结果。

用调用__shfl_sync和手动计算的通道 ID 替换所有 shuffle 是可行的。 仅用__shfl_sync替换损坏的 shuffle xor 2不会 __shfl_sync __shfl_sync第一个 shuffle xor 1 (正常工作)似乎可以解决它。 (这两种变通方法适用于我的 MVCE;我还没有机会评估它们是否适用于整个程序)

    // unexpectedly working
    int id = threadIdx.x;
    printf(" 0: Th %d cI %d\n", threadIdx.x, cIters);
    __syncthreads();
    cSteps += __shfl_sync(0xffffffff, cSteps, id ^ 1, 32);
    cIters += __shfl_sync(0xffffffff, cIters, id ^ 1, 32);

    printf(" 1: Th %2d cI %d\n", threadIdx.x, cIters);
    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 2, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 2, 32);

    printf(" 2: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 4, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 4, 32);

    printf(" 4: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 8, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 8, 32);

    printf(" 8: Th %2d cI %d\n", threadIdx.x, cIters);

    cSteps += __shfl_xor_sync(0xffffffff, cSteps, 16, 32);
    cIters += __shfl_xor_sync(0xffffffff, cIters, 16, 32);

    printf("16: Th %2d cI %d\n", threadIdx.x, cIters);

即使我有一个解决方法,我担心我仍然会在某个地方遇到未定义的行为,我的修复可能很脆弱。

任何人都可以阐明这一点吗? 我的程序中确实有UB吗? 这是一个已知的编译器错误吗?

据 CUDA 工程团队确认,这是一个编译器错误。 正如他们的通信所证实的那样,修复即将推出:

该修复针对的是 CUDA 11 之后的未来主要 CUDA 版本。在最新的 R515 在线之后,JIT 修复可能会在驱动程序分支中稍早一些。

他们还确认关闭优化可以解决问题; 他们没有评论任何在优化仍然有效的情况下可靠工作的解决方法。

以下解决方法在我的硬件和编译器上对我有用,但 YMMV:

  • 使用__shfl_sync代替shfl_add_syncshfl_xor_sync
  • __reduce_add_sync

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM