给定两种不同的BLAS实现,我们可以期望它们进行完全相同的浮点计算并返回相同的结果吗?还是会发生这种情况,例如,一个人将标量积计算为 ,一个人将其计算为 因此可能在IEEE浮点数中给出不同的结果算术?
给定两种不同的BLAS实现,我们可以期望它们进行完全相同的浮点计算并返回相同的结果吗?还是会发生这种情况,例如,一个人将标量积计算为 ,一个人将其计算为 因此可能在IEEE浮点数中给出不同的结果算术?
Answers:
不,不能保证。如果您使用的是没有任何优化的NETLIB BLAS,则几乎可以肯定结果是相同的。但是对于BLAS和LAPACK的任何实际使用,都使用高度优化的并行BLAS。即使仅在CPU的向量寄存器中并行运行,并行化也会导致单个项的求值顺序发生变化,并且求和的顺序也发生变化。现在,从IEEE标准中缺少的关联属性可以得出结论,结果是不一样的。因此,您提到的事情确实可以发生。
在NETLIB BLAS中,标量积只是for循环,展开系数为5:
DO I = MP1,N,5
DTEMP = DTEMP + DX(I)*DY(I) + DX(I+1)*DY(I+1) +
$ DX(I+2)*DY(I+2) + DX(I+3)*DY(I+3) + DX(I+4)*DY(I+4)
END DO
如果将每个乘法立即添加到DTEMP,或者首先将所有5个分量求和然后再添加到DTEMP,则由编译器决定。在OpenBLAS中,取决于体系结构的是更复杂的内核:
__asm__ __volatile__
(
"vxorpd %%ymm4, %%ymm4, %%ymm4 \n\t"
"vxorpd %%ymm5, %%ymm5, %%ymm5 \n\t"
"vxorpd %%ymm6, %%ymm6, %%ymm6 \n\t"
"vxorpd %%ymm7, %%ymm7, %%ymm7 \n\t"
".align 16 \n\t"
"1: \n\t"
"vmovups (%2,%0,8), %%ymm12 \n\t" // 2 * x
"vmovups 32(%2,%0,8), %%ymm13 \n\t" // 2 * x
"vmovups 64(%2,%0,8), %%ymm14 \n\t" // 2 * x
"vmovups 96(%2,%0,8), %%ymm15 \n\t" // 2 * x
"vmulpd (%3,%0,8), %%ymm12, %%ymm12 \n\t" // 2 * y
"vmulpd 32(%3,%0,8), %%ymm13, %%ymm13 \n\t" // 2 * y
"vmulpd 64(%3,%0,8), %%ymm14, %%ymm14 \n\t" // 2 * y
"vmulpd 96(%3,%0,8), %%ymm15, %%ymm15 \n\t" // 2 * y
"vaddpd %%ymm4 , %%ymm12, %%ymm4 \n\t" // 2 * y
"vaddpd %%ymm5 , %%ymm13, %%ymm5 \n\t" // 2 * y
"vaddpd %%ymm6 , %%ymm14, %%ymm6 \n\t" // 2 * y
"vaddpd %%ymm7 , %%ymm15, %%ymm7 \n\t" // 2 * y
"addq $16 , %0 \n\t"
"subq $16 , %1 \n\t"
"jnz 1b \n\t"
...
将标量乘积拆分为长度为4的小标量乘积并求和。
使用其他典型的BLAS实现(例如ATLAS,MKL,ESSL等),此问题仍然存在,因为每个BLAS实现都使用不同的优化来获取快速代码。但是据我所知,需要一个人为的例子来导致真正错误的结果。
如果需要BLAS库返回相同的结果(逐位相同),则必须使用可复制的BLAS库,例如:
简短答案
如果编写这两个BLAS实现以完全相同的顺序执行操作,并且使用相同的编译器标志和相同的编译器来编译库,那么它们将为您提供相同的结果。浮点算法不是随机的,因此两个相同的实现将给出相同的结果。
但是,出于性能考虑,有很多事情可以破坏此行为...
更长的答案
除了每个操作的行为方式,IEEE还指定了执行这些操作的顺序。但是,如果使用诸如“ -ffast-math”之类的选项编译BLAS实现,则编译器可执行的转换在精确算术中是正确的,但在IEEE浮点运算中不是“正确”的。如您所指出的,典型的例子是浮点加法的非关联性。使用更积极的优化设置,将假定关联性,并且处理器将通过对操作进行重新排序来尽可能多地并行执行这些操作。
其他违反标准的行为是通过使用FMA(融合乘加)指令来实现的。这些在矩阵乘法等运算中非常突出,并且有可能使例程的吞吐量增加一倍。但是,它们在单个操作中执行操作,并且仅会导致单个浮点取整步骤。这与IEEE标准不同,后者要求此操作有两个舍入步骤。这实际上使FMA结果比IEEE 结果更准确,但这在技术上是违反标准的行为。
if (x == 0) assert(x == 0)
可能会失败,从某种角度来看,这和随机性一样好。
if (x != 0) assert(x != 0)
由于扩展精度算法,应该为。