是否可以保证BLAS实现提供完全相同的结果?


17

给定两种不同的BLAS实现,我们可以期望它们进行完全相同的浮点计算并返回相同的结果吗?还是会发生这种情况,例如,一个人将标量积计算为 ,一个人将其计算为 因此可能在IEEE浮点数中给出不同的结果算术?

X1个ÿ1个+X2ÿ2+X3ÿ3+X4ÿ4
X1个ÿ1个+X2ÿ2+X3ÿ3+X4ÿ4

1
此线程中存在一些有关BLAS质量的投诉,请在页面中搜索CBLAS。这将表明,它们不仅不能给出相同的结果,而且并非所有人都足够准确地完成任何任务……
Szabolcs

Answers:


15

不,不能保证。如果您使用的是没有任何优化的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库,例如:


8

简短答案

如果编写这两个BLAS实现以完全相同的顺序执行操作,并且使用相同的编译器标志和相同的编译器来编译库,那么它们将为您提供相同的结果。浮点算法不是随机的,因此两个相同的实现将给出相同的结果。

但是,出于性能考虑,有很多事情可以破坏此行为...

更长的答案

除了每个操作的行为方式,IEEE还指定了执行这些操作的顺序。但是,如果使用诸如“ -ffast-math”之类的选项编译BLAS实现,则编译器可执行的转换在精确算术中是正确的,但在IEEE浮点运算中不是“正确”的。如您所指出的,典型的例子是浮点加法的非关联性。使用更积极的优化设置,将假定关联性,并且处理器将通过对操作进行重新排序来尽可能多地并行执行这些操作。

其他违反标准的行为是通过使用FMA(融合乘加)指令来实现的。这些在矩阵乘法等运算中非常突出,并且有可能使例程的吞吐量增加一倍。但是,它们在单个操作中执行操作,并且仅会导致单个浮点取整步骤。这与IEEE标准不同,后者要求此操作有两个舍入步骤。这实际上使FMA结果比IEEE 结果准确,但这在技术上是违反标准的行为。一种+bC


3
“浮点算术不是随机的”。令人遗憾的是必须明确指出这一点,但是似乎太多人认为这是……
pipe

好吧,不可预测和随机看起来很相似,许多入门编程类都说“绝不比较浮点数是否相等”,这给人的印象是,不能以与整数相同的方式依赖确切的值。
泰勒·奥尔森

@TylerOlsen这与问题无关,这也不是为什么这些类这么说的原因,但是IIRC曾经是一类无法依靠相等性的编译器错误。例如,有时if (x == 0) assert(x == 0) 可能会失败,从某种角度来看,这和随机性一样好。
基里尔

@Kirill抱歉,我只是想指出很多人从来没有真正学习过浮点的工作原理。至于历史的观点,这有点可怕,但是在我进入游戏之前必须已经解决了。
泰勒·奥尔森

@TylerOlsen糟糕,我的示例是错误的。if (x != 0) assert(x != 0)由于扩展精度算法,应该为。
基里尔

4

一般来说,没有。撇开关联性,选择编译器标志(例如,启用SIMD指令,使用融合乘法加法等)或硬件(例如,是否使用扩展精度)可能会产生不同的结果。

为了获得可重现的BLAS实现,我们付出了一些努力。有关更多信息,请参见ReproBLASExBLAS


1
另请参阅最新版本的英特尔MKL BLAS中的条件数值可重复性(CNR)功能。意识到达到这种可重复性水平通常会减慢您的计算速度,并且可能会使它们大大减慢速度!
Brian Borchers
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.