1

在调试一些 CUDA 代码时,我使用printf语句与等效的 CPU 代码进行比较,并注意到在某些情况下我的结果不同;它们在任何一个平台上都不一定是错误的,因为它们在浮点舍入误差之内,但我仍然有兴趣知道是什么导致了这种差异。

我能够将问题追溯到不同的点积结果。在 CUDA 和主机代码中,我都有 a 和 b 类型的向量float4。然后,在每个平台上,我使用以下代码计算点积并打印结果:

printf("a: %.24f\t%.24f\t%.24f\t%.24f\n",a.x,a.y,a.z,a.w);
printf("b: %.24f\t%.24f\t%.24f\t%.24f\n",b.x,b.y,b.z,b.w);
float dot_product = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
printf("a dot b: %.24f\n",dot_product);

CPU 的打印输出为:

a: 0.999629139900207519531250   -0.024383276700973510742188 -0.012127066962420940399170 0.013238593004643917083740
b: -0.001840781536884605884552  0.033134069293737411499023  0.988499701023101806640625  1.000000000000000000000000
a dot b: -0.001397025771439075469971

对于 CUDA 内核:

a: 0.999629139900207519531250   -0.024383276700973510742188 -0.012127066962420940399170 0.013238593004643917083740
b: -0.001840781536884605884552  0.033134069293737411499023  0.988499701023101806640625  1.000000000000000000000000
a dot b: -0.001397024840116500854492

如您所见,a 和 b 的值在两个平台上似乎是按位等效的,但是完全相同的代码的结果却略有不同。据我了解,浮点乘法根据 IEEE 754 标准进行了明确定义,并且与硬件无关。但是,对于为什么我没有看到相同的结果,我确实有两个假设:

  1. 编译器优化正在重新排序乘法,它们在 GPU/CPU 上以不同的顺序发生,从而产生不同的结果。
  2. CUDA 内核使用融合乘加 (FMA) 运算符,如http://developer.download.nvidia.com/assets/cuda/files/NVIDIA-CUDA-Floating-Point.pdf中所述。在这种情况下,CUDA 结果实际上应该更准确一些。
4

1 回答 1

4

除了将 FMUL 和 FADD 合并到 FMA 中(可以通过 nvcc 命令行开关关闭-fmad=false),CUDA 编译器遵循 C/C++ 规定的评估顺序。根据您的 CPU 代码的编译方式,它可能使用比单精度更宽的精度来累积点积,从而产生不同的结果。

对于 GPU 代码,将 FMUL/FADD 合并到 FMA 是很常见的,由此产生的数值差异也是如此。出于性能原因,CUDA 编译器执行激进的 FMA 合并。使用 FMA 通常也会产生更准确的结果,因为舍入步骤的数量减少了,并且由于 FMA 在内部保持全宽乘积,因此可以防止减法抵消。我建议阅读以下白皮书及其引用的参考资料:

https://developer.nvidia.com/sites/default/files/akamai/cuda/files/NVIDIA-CUDA-Floating-Point.pdf

为了使 CPU 和 GPU 结果与完整性检查相匹配,您需要在 GPU 代码中使用 关闭 FMA 合并-fmad=false,并在 CPU 上强制每个中间结果以单精度存储:

   volatile float p0,p1,p2,p3,dot_product; 
   p0=a.x*b.x; 
   p1=a.y*b.y; 
   p2=a.z*b.z; 
   p3=a.w*b.w; 
   dot_product=p0; 
   dot_product+=p1; 
   dot_product+=p2; 
   dot_product+=p3;
于 2013-09-20T22:22:35.580 回答