Python/Numpy中的多线程BLAS

50

我正试图在Python中实现大量的矩阵-矩阵乘法。最初,我认为NumPy会自动使用我的线程化BLAS库,因为我已经构建了它并针对这些库进行了优化。但是,当我查看top或其他一些东西时,似乎代码根本没有使用线程。

有什么想法是出了什么问题,或者我可以做些什么来轻松地使用BLAS性能呢?


你能更具体一些吗?比如说,“大数”到底有多大?你的矩阵形状是什么样子的?你目前的时间表是怎样的?你的硬件特征是什么?你期望(或希望)获得什么样的性能提升?谢谢。 - eat
@eat:矩阵大约为1600x1600(双精度)。由于我正在解决一个非常大的耦合ODE系统,所以代码执行了大量的矩阵乘法。使用Fortran中的BLAS而不是通过循环天真地进行矩阵乘法可以显著加快速度。在我的系统上进行线程处理可能会产生相同的效果。我希望能够实现10倍的加速。 - Lucas
1
能否以一种易于理解的方式展示您代码的相关部分,以便任何人都可以在自己的平台上使用它?(顺便问一下,您的矩阵是否接近完整秩?如果它们恰好是低秩矩阵,则存在替代方法来加速计算)。谢谢。 - eat
@Lucas,我已经从.bash_profile中删除了那个变量,并且我也在Mac OS X上使用EPD。我的问题还没有解决。Numpy.dot仍然只使用一个核心。你还做了其他什么吗? - Nino
@Nino:你是在使用numpy.dot进行矩阵乘法吗? - Lucas
显示剩余2条评论
4个回答

116

我已经在另一个帖子中发布了这个问题,但我认为它更适合在这个帖子中:

更新(2014年7月30日):

我在我们的新HPC上重新运行了基准测试。硬件和软件堆栈都与原始答案中的设置不同。

我将结果放在google spreadsheet中(还包括原始答案的结果)。

硬件

我们的HPC有两个不同的节点,一个使用Intel Sandy Bridge CPU,另一个使用更新的Ivy Bridge CPU:

Sandy(MKL,OpenBLAS,ATLAS):

  • CPU:2 x 16 Intel(R) Xeon(R) E2560 Sandy Bridge @ 2.00GHz(16核心)
  • RAM:64 GB

Ivy(MKL,OpenBLAS,ATLAS):

  • CPU:2 x 20 Intel(R) Xeon(R) E2680 V2 Ivy Bridge @ 2.80GHz(20核心,HT = 40核心)
  • RAM:256 GB

软件

这个软件栈对于两个节点都是相同的。使用的不是GotoBLAS2,而是OpenBLAS,还有一个设置为8线程(硬编码)的多线程ATLAS BLAS。

  • 操作系统:Suse
  • 英特尔编译器:ictce-5.3.0
  • Numpy:1.8.0
  • OpenBLAS:0.2.6
  • ATLAS:3.8.4

点积基准测试

基准代码与下面相同。但是对于新机器,我还运行了矩阵大小为50008000的基准测试。
下表包括原始答案的基准测试结果(重命名为:MKL --> Nehalem MKL,Netlib Blas --> Nehalem Netlib BLAS等)

Matrix multiplication (sizes=[1000,2000,3000,5000,8000])

单线程性能: 单线程性能

多线程性能(8个线程): 多线程(8个线程)性能

线程数 vs 矩阵大小(Ivy Bridge MKL)矩阵大小 vs 线程数

基准测试套件

benchmark suite

单线程性能: enter image description here

多线程(8个线程)性能: enter image description here

结论

新的基准测试结果与原答案中的结果相似。 OpenBLASMKL 的表现相当,除了特征值测试。 在单线程模式下,OpenBLAS特征值测试表现只是合理的。 在多线程模式下,性能更差。

"矩阵大小 vs 线程图表" 也显示,尽管 MKL 和 OpenBLAS 通常随着核心/线程数的增加而扩展得很好,但这取决于矩阵的大小。对于小矩阵,添加更多核心不会显著提高性能。

Sandy BridgeIvy Bridge还有大约30%的性能提升,这可能是由于更高的时钟速率(+0.8 GHz)和/或更好的架构。


原始回答(2011年4月10日):

不久前,我不得不优化一些使用numpy和BLAS编写的线性代数计算/算法的python代码,因此我对不同的numpy/BLAS配置进行了基准测试。

具体而言,我测试了:

  • Numpy与ATLAS
  • Numpy与GotoBlas2(1.13)
  • Numpy与MKL(11.1 / 073)
  • Numpy与Accelerate Framework(Mac OS X)

我运行了两个不同的基准测试:

  1. 具有不同大小矩阵的简单点积
  2. 可以在这里找到的基准测试套件。

以下是我的结果:

机器

Linux(MKL、ATLAS、No-MKL、GotoBlas2):

  • 操作系统: Ubuntu Lucid 10.4 64位。
  • CPU: 2 x 4 Intel(R) Xeon(R) E5504 @ 2.00GHz (8核)
  • 内存: 24 GB
  • Intel编译器: 11.1/073
  • Scipy: 0.8
  • Numpy: 1.5

Mac Book Pro (Accelerate Framework):

  • 操作系统: Mac OS X Snow Leopard (10.6)
  • CPU: 1 Intel Core 2 Duo 2.93 Ghz (2核)
  • 内存: 4 GB
  • Scipy: 0.7
  • Numpy: 1.3

Mac Server (Accelerate Framework):

  • 操作系统: Mac OS X Snow Leopard Server (10.6)
  • CPU: 4 X Intel(R) Xeon(R) E5520 @ 2.26 Ghz (8核)
  • 内存: 4 GB
  • Scipy: 0.8
  • Numpy: 1.5.1

点积基准测试

代码:

import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)

结果:

    系统            |  大小 = 1000  | 大小 = 2000 | 大小 = 3000 |
netlib BLAS       |  1350 毫秒    |   10900 毫秒 |  39200 毫秒  |    
ATLAS (1 CPU)     |   314 毫秒     |    2560 毫秒 |   8700 毫秒  |     
MKL (1 CPUs)      |   268 毫秒     |    2110 毫秒 |   7120 毫秒  |
MKL (2 CPUs)      |    -           |       -      |   3660 毫秒  |
MKL (8 CPUs)      |    39 毫秒     |     319 毫秒 |   1000 毫秒  |
GotoBlas2 (1 CPU) |   266 毫秒     |    2100 毫秒 |   7280 毫秒  |
GotoBlas2 (2 CPUs)|   139 毫秒     |    1009 毫秒 |   3690 毫秒  |
GotoBlas2 (8 CPUs)|    54 毫秒     |     389 毫秒 |   1250 毫秒  |
Mac OS X (1 CPU)  |   143 毫秒     |    1060 毫秒 |   3605 毫秒  |
Mac Server (1 CPU)|    92 毫秒     |     714 毫秒 |   2130 毫秒  |

Dot product benchmark - chart

基准测试套件

代码:
有关基准测试套件的更多信息,请参见此处

结果:

    系统         | 特征值分解时间 | SVD时间 | 行列式时间 | 逆矩阵时间 | 点乘时间 |
netlib BLAS       |  1688毫秒     | 13102毫秒 | 438毫秒 | 2155毫秒 | 3522毫秒 |
ATLAS (1 CPU)     |   1210毫秒     |  5897毫秒 | 170毫秒 |  560毫秒 |  893毫秒 |
MKL (1 CPUs)      |   691毫秒      |  4475毫秒 | 141毫秒 |  450毫秒 |  736毫秒 |
MKL (2 CPUs)      |   552毫秒      |  2718毫秒 |  96毫秒 |  267毫秒 |  423毫秒 |
MKL (8 CPUs)      |   525毫秒      |  1679毫秒 |  60毫秒 |  137毫秒 |  197毫秒 |  
GotoBlas2 (1 CPU) |  2124毫秒      |  4636毫秒 | 147毫秒 |  456毫秒 |  743毫秒 |
GotoBlas2 (2 CPUs)|  1560毫秒      |  3278毫秒 | 116毫秒 |  295毫秒 |  460毫秒 |
GotoBlas2 (8 CPUs)|   741毫秒      |  2914毫秒 |  82毫秒 |  262毫秒 |  192毫秒 |
Mac OS X (1 CPU)  |   948毫秒      |  4339毫秒 | 151毫秒 |  318毫秒 |  566毫秒 |
Mac Server (1 CPU)|  1033毫秒      |  3645毫秒 |  99毫秒 |  232毫秒 |  342毫秒 |

Benchmark suite - chart

安装

安装MKL需要安装完整的Intel编译器套件,这相当简单。但是由于一些bug/问题,配置和编译支持MKL的numpy有点麻烦。

GotoBlas2是一个小型软件包,可以轻松地编译为共享库。然而,由于一个bug,您必须在构建后重新创建共享库才能将其与numpy一起使用。
此外,为多个目标平台构建它出了一些问题。因此,我不得不为每个我想要拥有优化的libgoto2.so文件的平台创建一个.so文件。

如果您从Ubuntu存储库中安装numpy,则会自动安装和配置numpy以使用ATLAS。从源代码安装ATLAS可能需要一些时间,并且需要一些额外的步骤(fortran等)。

如果您在带有FinkMac Ports的Mac OS X机器上安装numpy,则它将配置numpy以使用ATLASApple的Accelerate Framework之一。 您可以通过运行ldd命令查看numpy.core._dotblas文件或调用numpy.show_config()来检查。

结论

MKL表现最佳,紧随其后的是GotoBlas2
特征值测试中,GotoBlas2的表现比预期的要差得多。不确定为什么会出现这种情况。
苹果的加速框架Accelerate Framework表现非常好,尤其是在单线程模式下(与其他BLAS实现相比)。

GotoBlas2MKL都能很好地随着线程数量的增加而扩展。因此,如果您需要处理大型矩阵,则在多个线程上运行它将有很大帮助。

无论如何,不要使用默认的netlib blas实现,因为它对于任何严肃的计算工作来说都太慢了。

在我们的集群上,我还安装了AMD的ACML,性能与MKLGotoBlas2相似。但我没有任何数字数据。

我个人建议使用GotoBlas2,因为它更容易安装,而且是免费的。

如果您想使用C++/C进行编码,还应该查看Eigen3,它在某些cases中表现优于MKL/GotoBlas2,而且使用起来也非常简单。


谢谢分享。您知道苹果的Accelerate框架是否利用了多核或超线程吗?我猜不会,因为据我所知,您的“Mac服务器”有4个核心,但您能确认一下吗?此外,对于ATLAS,您是在暗示它只能使用1个核心(我只看到这种情况的结果)吗? - Eric O. Lebigot
1
加速框架默认只使用一个核心。老实说,我不知道是否可以将其设置为使用多个核心。开发者页面上没有关于此的任何信息:http://developer.apple.com/library/mac/#featuredarticles/AccelerateFrameworkData/_index.html关于ATLAS:默认的ATLAS安装是单线程的。但是也有一个多线程的ATLAS版本(AT93或类似版本)。请参见此处:http://cran.r-project.org/web/packages/gcbd/vignettes/gcbd.pdf - Ümit
@Nino:这取决于您的numpy安装在集群上配置了哪种多线程blas库。在我们的情况下(使用MKL),您可以通过MKL_NUM_THREADS环境变量设置要使用的核心数。 关于检查numpy函数使用的库,我通常会在_dotblas.so文件上执行ldd命令。它将显示该so文件链接到哪些库(在MKL的情况下是mkl so等)。 - Ümit
@Nino: 看起来你的numpy安装没有使用任何自定义BLAS库。例如,如果你使用MKL配置numpy,它应该链接dotblas.so到mkl库,或者在ATLAS的情况下(即libatlas.so等)。 - Ümit
1
近五年过去了,这个答案仍然是史诗级的。这仍然是规范的Numpy加速基准测试。只有一个补充可能会提高史诗的赌注:一个与BLIS链接的Numpy基准测试。但是乞丐不能选择。我选择你,Ümit。 - Cecil Curry
显示剩余5条评论

18

不是所有的NumPy都使用BLAS,只有一些函数 -- 具体来说是dot()vdot()innerproduct()numpy.linalg模块中的几个函数。此外,请注意,在处理大数组时,许多NumPy操作受到存储器带宽的限制,因此优化实现不太可能带来任何改进。如果您受到存储器带宽的限制,则多线程是否可以提供更好的性能严重取决于您的硬件。


这听起来不太好。我本来希望能在Python中以某种方式解决这个问题。你认为使用类似于weave的东西在C或Fortran中进行矩阵乘法会有所收获吗?假设我想使用numpy中的特定函数,该函数然后调用硬编码的矩阵乘法子程序。 - Lucas
2
@Lucas:在NumPy中进行矩阵乘法应该使用numpy.dot(),这也是内部实现的方式。但是如果不知道你具体在做什么,就很难给出进一步的建议。也许你想开一个新问题来询问。 - Sven Marnach

3

由于矩阵乘法受到内存限制,因此在相同的内存层次结构上添加额外的核心并不能带来太多好处。当然,如果您在切换到Fortran实现时看到了实质性的加速,则我可能是错误的。

我理解的是,对于这些问题,适当的缓存远比计算能力重要。BLAS可能会为您完成这项工作。

对于简单的测试,您可以尝试安装Enthought's python发行版进行比较。它们链接到英特尔的Math Kernel Library,我相信该库可以利用多个可用的核心。


1

你听说过MAGMA吗? GPU和多核架构上的矩阵代数 http://icl.cs.utk.edu/magma/

MAGMA项目旨在开发一种类似于LAPACK的密集线性代数库,用于异构/混合体系结构,从当前的“多核+GPU”系统开始。


MCVE-类似的文化也要求定量化——说明了什么过程/花费了多少时间来完成/在什么特定情况下。技术营销往往忽略这些定量可验证的事实,所以不要犹豫要求它们,或者自己生成它们,或者干脆不要再重复那些受公关驱动的文字。不要忘记,多核心、更多GPU引擎由于其专注于数字计算,会很快遇到(内部)延迟掩盖架构和I/O带宽限制。真正的并行设计能够体验到这一点。 - user3666197

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接