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

np.einsum performance of 4 matrix multiplications

给定以下 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 函数,但我似乎无法弄清楚如何将它用于给定的计算。

看起来您偶然发现了 greedy 路径给出非最佳缩放比例的情况。

>>> 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="optimal") 应该会让您 运行 达到最佳性能。我可以看看这条边,看看贪婪能不能抓住它。

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