两个单精度浮点向量的点积在 CUDA 内核中产生的结果与在主机上的结果不同
Dot product of two single-precision floating point vectors yields different results in CUDA kernel than on the host
在调试一些 CUDA 代码时,我使用 printf
语句与等效的 CPU 代码进行比较,并注意到在某些情况下我的结果不同; 它们在任何一个平台上都不一定是错误的,因为它们在浮点舍入误差内,但我仍然有兴趣知道是什么导致了这种差异。
我能够将问题追踪到不同的点积结果。在 CUDA 和主机代码中,我都有类型 float4
的向量 a 和 b。然后,在每个平台上,我使用以下代码计算点积并打印结果:
printf("a: %.24ft%.24ft%.24ft%.24fn",a.x,a.y,a.z,a.w);
printf("b: %.24ft%.24ft%.24ft%.24fn",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: %.24fn",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标准明确定义的,并且与硬件无关。但是,关于为什么我没有看到相同的结果,我确实有两个假设:
- 编译器优化是对乘法进行重新排序,它们在 GPU/CPU 上以不同的顺序发生,从而产生不同的结果。
- CUDA 内核正在使用融合的乘加 (FMA) 运算符,如 http://developer.download.nvidia.com/assets/cuda/files/NVIDIA-CUDA-Floating-Point.pdf 中所述。在这种情况下,CUDA 结果实际上应该更准确一些。
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;
- 为什么"do while"循环不断退出,即使条件计算结果为 false?
- valgrind-hellgrind与泄漏检查的结果不同
- 用C++20 fmt限制结果的总大小
- 如何返回一个类的两个对象相加的结果
- 使用QProcess执行命令,并将结果存储在QStringList中
- 如果我std::dynamic_pointer_cast并且底层dynamic_cast的结果为null,那么返回的sh
- 在没有定义返回类型的函数中返回布尔值,并将结果保存在无错误的char编译中-为什么
- 序列化,没有库的整数,得到奇怪的结果
- 如何在内核C++中使用1920x1080x16M图形或类似的16M颜色?(VGA)
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- 带有大结构变量的 CUDA 内核函数给出了错误的结果
- OpenCL - 内核方法返回意外结果
- 执行 CUDA 内核时黑屏C++输出正确的结果
- 带有大型矢量大小的CUDA内核的点产品返回错误的结果
- 如何存储CUDA内核函数的Bool结果
- OpenCL -- 不同的设备上有不同的内核"printf()"结果?
- 两个单精度浮点向量的点积在 CUDA 内核中产生的结果与在主机上的结果不同
- 奇怪的CUDA内核在旧的显示驱动程序上的结果
- CUDA C:内核输出不良结果
- OpenCL内核浮点除法给出不同的结果