缩小两台机器之间ddot的差异

我目前有两台机器,它们在两个向量上为np.dot的一个实例产生不同的输出。在不深入研究从NumPy到BLAS的抽象抽象层的情况下,我能够重现scipy.linalg.blas.ddot中的差异,因此我假设对BLAS中差异的解释也解释了NumPy中的差异。具体来说,请考虑以下示例:

import numpy as np
from scipy.linalg.blas import ddot

u = np.array([0.13463703107579461093,-0.07773272613450200874,-0.98784132994666418170])
v = np.array([-0.86246572448831815283,-0.03715105562531360872,-0.50475010960748223354])
a = np.dot(v,u)
b = v[0]*u[0] + v[1]*u[1] + v[2]*u[2]
c = ddot(v,u)
print(f'{a:.25f}')
print(f'{b:.25f}')
print(f'{c:.25f}')

这将产生以下输出:

                      Machine 1                   Machine 2
a   0.3853810478481685120044631 0.3853810478481685675156143
b   0.3853810478481685120044631 0.3853810478481685120044631
c   0.3853810478481685120044631 0.3853810478481685675156143

类似地,以下Cython引起了相同的差异:

cimport scipy.linalg.cython_blas
cimport numpy as np
import numpy as np

cdef np.float64_t run_test(np.double_t[:] a,np.double_t[:] b):
    cdef int ix,iy,n
    ix = iy = 1
    n = 3
    return scipy.linalg.cython_blas.ddot(&n,&a[0],&ix,&b[0],&iy)

a = np.array([0.13463703107579461093,-0.98784132994666418170])
b = np.array([-0.86246572448831815283,-0.50475010960748223354])
print(f'{run_test(a,b):.25f}')

所以,我试图了解可能导致这种情况的原因。

有问题的计算机分别运行Windows 10(Intel(R)Core(TM)i7-5600U)和Windows Server 2016(Intel(R)Xeon(R)Gold 6140)。

在这两种情况下,我都只设置了numpyscipycython及其相关性,而没有设置新的conda环境。我已经在环境上运行了校验和,以确保最终包含的二进制文件达成一致并验证np.__config__.show()的输出是否匹配。同样,我检查了mkl.get_version_string()的输出在两台计算机上是否一致。

这使我认为问题可能出在硬件差异上。我没有研究最终将执行什么指令(缺少在Windows / MSVC上调试Cython代码的直接方法),但是我checked这两台机器都支持AVX2 / fma,这似乎可能是一个来源的差异。

另一方面,我确实发现这两台机器支持不同的指令集。具体地

          Machine 1 (i7)       Machine 2 (Xeon)
AVX                    Y                      Y
AVX2                   Y                      Y
AVX512CD               N                      Y
AVX512ER               N                      N
AVX512F                N                      Y
AVX512PF               N                      N
fma                    Y                      Y

但是,我不知道一种确定它本身是否足以解释差异的好方法,或者它是否是红色鲱鱼(?)

所以我的问题变成:

  

从上面开始,有什么自然的步骤可以尝试找出导致差异的原因?是组装时间,还是还有其他更明显的东西?

tonggexiaowu 回答:缩小两台机器之间ddot的差异

鉴于对该问题的出色评论,似乎显而易见,受支持的指令集之间的差异最终是罪魁祸首,的确,我们可以在运行Cython脚本时使用ListDLLs来发现MKL基于这两种情况。

对于i7(机器1):

>listdlls64 python.exe | wsl grep mkl
0x00000000b9ff0000  0xe7e000   [...]\miniconda3\envs\npfloattest\Library\bin\mkl_rt.dll
0x00000000b80e0000  0x1f05000  [...]\miniconda3\envs\npfloattest\Library\bin\mkl_intel_thread.dll
0x00000000b3b40000  0x43ba000  [...]\miniconda3\envs\npfloattest\Library\bin\mkl_core.dll
0x00000000b0e50000  0x2ce5000  [...]\miniconda3\envs\npfloattest\Library\bin\mkl_avx2.dll
0x00000000b01f0000  0xc58000   [...]\miniconda3\envs\npfloattest\Library\bin\mkl_vml_avx2.dll
0x00000000f88c0000  0x7000     [...]\miniconda3\envs\npfloattest\lib\site-packages\mkl\_mklinit.cp37-win_amd64.pyd
0x00000000afce0000  0x22000    [...]\miniconda3\envs\npfloattest\lib\site-packages\mkl\_py_mkl_service.cp37-win_amd64.pyd

至强(机器2):

0x0000000057ec0000  0xe7e000   [...]\Miniconda3\envs\npfloattest\Library\bin\mkl_rt.dll
0x0000000055fb0000  0x1f05000  [...]\Miniconda3\envs\npfloattest\Library\bin\mkl_intel_thread.dll
0x0000000051bf0000  0x43ba000  [...]\Miniconda3\envs\npfloattest\Library\bin\mkl_core.dll
0x000000004e1a0000  0x3a4a000  [...]\Miniconda3\envs\npfloattest\Library\bin\mkl_avx512.dll
0x000000005c6c0000  0xc03000   [...]\Miniconda3\envs\npfloattest\Library\bin\mkl_vml_avx512.dll
0x0000000079a70000  0x7000     [...]\Miniconda3\envs\npfloattest\lib\site-packages\mkl\_mklinit.cp37-win_amd64.pyd
0x000000005e830000  0x22000    [...]\Miniconda3\envs\npfloattest\lib\site-packages\mkl\_py_mkl_service.cp37-win_amd64.pyd

这非常有力地表明,对AVX512CD / AVX512F的支持足以促使MKL使用不同的库,从而最终可能会使用不同的指令集。

现在,看看这是如何实现的将是非常有趣的:发出了什么指令,这在具体的数字示例上意味着什么,以及具体的哪些指令集是MKL基于其决定的(我应该密切注意4FMAPS / 4VNNIW,而不仅仅是CD / F / ER / PR?)

首先,让我们编写等效的VC ++程序,以了解最终将运行哪些指令:

typedef double (*func)(int,const double*,int,int);
int main()
{
    double a[3];
    double b[3];
    std::cin >> a[0];
    std::cin >> a[1];
    std::cin >> a[2];
    std::cin >> b[0];
    std::cin >> b[1];
    std::cin >> b[2];

    func cblas_ddot;
    HINSTANCE rt = LoadLibrary(TEXT("mkl_rt.dll"));
    cblas_ddot = (func)GetProcAddress(rt,"cblas_ddot");
    double res_rt = cblas_ddot(3,a,1,b,1);
    std::cout.precision(25);
    std::cout << res_rt;
}

目前,我缺乏在AVX512支持计算机上运行该方法的好方法,但请注意,在i7(计算机1)/ AVX2上使用了以下说明;这里,在每种情况下,我们都注意到指令修改的所有YMM寄存器;特别是YMM4YMM5分别用ab的值初始化,在vfmadd231pd之后,YMM3包含元素两个数组的乘积,在vaddsd之后,YMM5的下部包含结果:

00007FF88E1CFEA6  vmovdqu     ymm5,ymmword ptr [7FF890BC1BC0h]  
00007FF88E1CFEAE  vmaskmovpd  ymm4,ymm5,ymmword ptr [rbx]

YMM4 = 0000000000000000-BFEF9C656BB84218-BFB3E64ABC939CC1-3FC13BC946A68994 

00007FF88E1CFEB3  vmaskmovpd  ymm5,ymmword ptr [r9]

YMM5 = 0000000000000000-BFE026E9B3AD5464-BFA3057691D85EDE-BFEB9951B813250D 

00007FF88E1CFEB8  vfmadd231pd ymm3,ymm4

YMM3 = 0000000000000000-3FDFE946951928C9-3F67A8442F158742-BFBDBA0760DBBFEC

00007FF88E1CFEBD  vaddpd      ymm1,ymm3,ymm1  

YMM1 = 0000000000000000-3FDFE946951928C9-3F67A8442F158742-BFBDBA0760DBBFEC 

00007FF88E1CFEC1  vaddpd      ymm0,ymm2,ymm0  
00007FF88E1CFEC5  vaddpd      ymm2,ymm1,ymm0  

YMM2 = 0000000000000000-3FDFE946951928C9-3F67A8442F158742-BFBDBA0760DBBFEC 

00007FF88E1CFEC9  vhaddpd     ymm3,ymm2  
00007FF88E1CFECD  vperm2f128  ymm4,1  

YMM4 = BFBCFCC53F6313B2-BFBCFCC53F6313B2-3FDFE946951928C9-3FDFE946951928C9 

00007FF88E1CFED3  vaddsd      xmm5,xmm3,xmm4  

YMM5 = 0000000000000000-0000000000000000-BFBCFCC53F6313B2-3FD8AA15454063DC

00007FF88E1CFED7  vmovsd      qword ptr [rsp+90h],xmm5 
,
  1. 如果您需要按位相等的答案:您是否尝试过@ bg2b(https://software.intel.com/en-us/mkl-linux-developer-guide-instruction-set-specific-dispatching-on-intel-architectures)发布的链接中的MKL_ENABLE_INSTRUCTIONS变量?如果您导入MKL库,然后调用mkl.enable_instructions,则可能为时已晚。
  2. 在双精度(DP)世界中:相对差为-1.4404224470807435333001684021155e-16(绝对差为-5.55111512e-17),小于C ++和Python DP机器epsilon(https://en.wikipedia.org/wiki/Machine_epsilon)。因此从Python的正确性来看结果是相等的。

干杯, 弗拉基米尔

本文链接:https://www.f2er.com/2884777.html

大家都在问