Python np.einsum 4矩阵乘法的性能

Python np.einsum 4矩阵乘法的性能,python,numpy,numpy-einsum,Python,Numpy,Numpy Einsum,给定以下3个矩阵: M = np.arange(35 * 37 * 59).reshape([35, 37, 59]) A = np.arange(35 * 51 * 59).reshape([35, 51, 59]) B = np.arange(37 * 51 * 51 * 59).reshape([37, 51, 51, 59]) C = np.arange(59 * 27).reshape([59, 27]) 我正在使用einsum计算: D1 = np.einsum('xyf,xtf,

给定以下3个矩阵:

M = np.arange(35 * 37 * 59).reshape([35, 37, 59])
A = np.arange(35 * 51 * 59).reshape([35, 51, 59])
B = np.arange(37 * 51 * 51 * 59).reshape([37, 51, 51, 59])
C = np.arange(59 * 27).reshape([59, 27])
我正在使用einsum计算:

D1 = np.einsum('xyf,xtf,ytpf,fr->tpr', M, A, B, C, optimize=True);
但当时我发现它的性能要差得多:

tmp = np.einsum('xyf,xtf->tfy', A, M, optimize=True)
tmp = np.einsum('ytpf,yft->ftp', B, tmp, optimize=True)
D2 = np.einsum('fr,ftp->tpr', C, tmp, optimize=True)
我不明白为什么。
总的来说,我正在尽可能地优化这段代码。我读过关于np.tensordot函数的书,但我似乎不知道如何在给定的计算中使用它。

看起来你偶然发现了贪婪路径给出非最佳缩放的情况

>>> path, desc = np.einsum_path('xyf,xtf,ytpf,fr->tpr', M, A, B, C, optimize="greedy");
>>> print(desc)
  Complete contraction:  xyf,xtf,ytpf,fr->tpr
         Naive scaling:  6
     Optimized scaling:  5
      Naive FLOP count:  3.219e+10
  Optimized FLOP count:  4.165e+08
   Theoretical speedup:  77.299
  Largest intermediate:  5.371e+06 elements
--------------------------------------------------------------------------
scaling                  current                                remaining
--------------------------------------------------------------------------
   5              ytpf,xyf->xptf                         xtf,fr,xptf->tpr
   4               xptf,xtf->ptf                              fr,ptf->tpr
   4                 ptf,fr->tpr                                 tpr->tpr

>>> path, desc = np.einsum_path('xyf,xtf,ytpf,fr->tpr', M, A, B, C, optimize="optimal");
>>> print(desc)
  Complete contraction:  xyf,xtf,ytpf,fr->tpr
         Naive scaling:  6
     Optimized scaling:  4
      Naive FLOP count:  3.219e+10
  Optimized FLOP count:  2.744e+07
   Theoretical speedup:  1173.425
  Largest intermediate:  1.535e+05 elements
--------------------------------------------------------------------------
scaling                  current                                remaining
--------------------------------------------------------------------------
   4                xtf,xyf->ytf                         ytpf,fr,ytf->tpr
   4               ytf,ytpf->ptf                              fr,ptf->tpr
   4                 ptf,fr->tpr                                 tpr->tpr

使用np.einsum'xyf,xtf,ytpf,fr->tpr',M,A,B,C,optimize=optimize应该可以让您以最高性能运行。我可以查看这条边,看看贪婪是否能抓住它。

看起来你无意中发现贪婪路径给出了非最佳缩放

>>> path, desc = np.einsum_path('xyf,xtf,ytpf,fr->tpr', M, A, B, C, optimize="greedy");
>>> print(desc)
  Complete contraction:  xyf,xtf,ytpf,fr->tpr
         Naive scaling:  6
     Optimized scaling:  5
      Naive FLOP count:  3.219e+10
  Optimized FLOP count:  4.165e+08
   Theoretical speedup:  77.299
  Largest intermediate:  5.371e+06 elements
--------------------------------------------------------------------------
scaling                  current                                remaining
--------------------------------------------------------------------------
   5              ytpf,xyf->xptf                         xtf,fr,xptf->tpr
   4               xptf,xtf->ptf                              fr,ptf->tpr
   4                 ptf,fr->tpr                                 tpr->tpr

>>> path, desc = np.einsum_path('xyf,xtf,ytpf,fr->tpr', M, A, B, C, optimize="optimal");
>>> print(desc)
  Complete contraction:  xyf,xtf,ytpf,fr->tpr
         Naive scaling:  6
     Optimized scaling:  4
      Naive FLOP count:  3.219e+10
  Optimized FLOP count:  2.744e+07
   Theoretical speedup:  1173.425
  Largest intermediate:  1.535e+05 elements
--------------------------------------------------------------------------
scaling                  current                                remaining
--------------------------------------------------------------------------
   4                xtf,xyf->ytf                         ytpf,fr,ytf->tpr
   4               ytf,ytpf->ptf                              fr,ptf->tpr
   4                 ptf,fr->tpr                                 tpr->tpr

使用np.einsum'xyf,xtf,ytpf,fr->tpr',M,A,B,C,optimize=optimize应该可以让您以最高性能运行。我可以查看这条边,看看贪婪是否能抓住它。

虽然确实有几个贪婪算法在这种情况下可能无法找到最佳顺序,但这与这里的谜题无关。当你进行D2进近时,你已经确定了操作顺序,在这种情况下是A,M,B,C或等效的M,A,B,C。这恰好是最佳路径。3 optimize=True语句是不需要的,并且被忽略,因为当有2个因素时,没有使用优化。D1方法的速度减慢是因为需要找到4数组操作的最佳顺序。如果您首先找到了路径,然后使用Optimize=path将它与4个数组一起传递给einsum,我猜这两种方法本质上是等效的。因此,减速是由于D1的优化步骤。虽然我不确定如何找到最佳排序,但根据我所做的未发表的工作,此任务通常会有O3^n最坏情况行为,其中n是数组的数量。

尽管确实有几个贪婪算法可能在这种情况下找不到最佳排序,这与这里的谜题无关。当你进行D2进近时,你已经确定了操作顺序,在这种情况下是A,M,B,C或等效的M,A,B,C。这恰好是最佳路径。3 optimize=True语句是不需要的,并且被忽略,因为当有2个因素时,没有使用优化。D1方法的速度减慢是因为需要找到4数组操作的最佳顺序。如果您首先找到了路径,然后使用Optimize=path将它与4个数组一起传递给einsum,我猜这两种方法本质上是等效的。因此,减速是由于D1的优化步骤。虽然我不确定如何找到最佳排序,但根据我所做的未发表的工作,此任务通常会有O3^n最坏情况行为,其中n是数组数。

看起来已经很好了。您可以在最后一步使用tensordot进行一些改进,但这不是瓶颈,因此不会对计时进行太多更改。但是为什么将einsum拆分为3会产生更好的结果?那么瓶颈是什么呢?瓶颈似乎是第二种方法D2的前两个步骤。但为什么将einsum拆分为3会产生更好的结果?我不知道原因,但我在使用einsum时观察到,当使用更多输入时,einsum会变慢,特别是使用张量。如果我不得不猜测的话,我会认为它的内存阻塞,就像这里我们在D1方法中有6个循环。@Divakar optimize=True将收缩拆分为多个较小的收缩,以降低整体缩放。这看起来已经很好了。您可以在最后一步使用tensordot进行一些改进,但这不是瓶颈,因此不会对计时进行太多更改。但是为什么将einsum拆分为3会产生更好的结果?那么瓶颈是什么呢?瓶颈似乎是第二种方法D2的前两个步骤。但为什么将einsum拆分为3会产生更好的结果?我不知道原因,但我在使用einsum时观察到,当使用更多输入时,einsum会变慢,特别是使用张量。如果我不得不猜测的话,我会认为它的内存阻塞,就像这里我们在D1方法中有6个循环。@Divakar optimize=True将收缩拆分为多个较小的收缩,以降低整体缩放。