我正在尝试在 Python 中实现大量矩阵-矩阵乘法。最初,我假设 NumPy 会自动使用我的线程化 BLAS 库,因为我是针对这些库构建的。但是,当我查看顶部或其他内容时,代码似乎根本不使用线程。
任何想法有什么问题或我可以做些什么来轻松使用 BLAS 性能?
我正在尝试在 Python 中实现大量矩阵-矩阵乘法。最初,我假设 NumPy 会自动使用我的线程化 BLAS 库,因为我是针对这些库构建的。但是,当我查看顶部或其他内容时,代码似乎根本不使用线程。
任何想法有什么问题或我可以做些什么来轻松使用 BLAS 性能?
我已经在另一个线程中发布了这个,但我认为它更适合这个:
我在我们的新 HPC 上重新运行基准测试。硬件和软件堆栈都与原始答案中的设置不同。
我将结果放在谷歌电子表格中(还包含原始答案的结果)。
我们的 HPC 有两个不同的节点,一个使用 Intel Sandy Bridge CPU,一个使用更新的 Ivy Bridge CPU:
桑迪(MKL、OpenBLAS、ATLAS):
常春藤(MKL、OpenBLAS、ATLAS):
软件堆栈适用于 sam 的两个节点。代替GotoBLAS2,使用OpenBLAS并且还有一个设置为 8 个线程(硬编码)的多线程 ATLAS BLAS。
基准代码与以下相同。但是对于新机器,我还运行了矩阵大小5000和8000的基准测试。
下表包括原始答案的基准测试结果(重命名为:MKL --> Nehalem MKL、Netlib Blas --> Nehalem Netlib BLAS 等)
单线程性能:
多线程性能(8 线程):
线程与矩阵大小(Ivy Bridge MKL):
单线程性能:
多线程(8 线程)性能:
新的基准测试结果与原始答案中的结果相似。OpenBLAS和MKL在同一水平上执行,除了特征值测试。特征值测试仅在单线程模式下的OpenBLAS上执行得相当好。在多线程模式下,性能更差。
“矩阵大小与线程图表”还显示,尽管 MKL 和 OpenBLAS 通常可以很好地扩展内核/线程数,但这取决于矩阵的大小。对于小型矩阵,添加更多内核不会大大提高性能。
从Sandy Bridge到Ivy Bridge的性能也提高了大约 30% ,这可能是由于更高的时钟速率(+ 0.8 Ghz)和/或更好的架构。
前段时间我不得不优化一些使用 numpy 和 BLAS 用 python 编写的线性代数计算/算法,所以我对不同的 numpy/BLAS 配置进行了基准测试/测试。
具体来说,我测试了:
我确实运行了两个不同的基准测试:
这是我的结果:
Linux(MKL、ATLAS、No-MKL、GotoBlas2):
Mac Book Pro(加速框架):
Mac 服务器(加速框架):
代码:
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 个 CPU)| 268 毫秒 | 2110 毫秒 | 7120 毫秒 | MKL(2 个 CPU)| - | - | 3660 毫秒 | MKL(8 个 CPU)| 39 毫秒 | 319 毫秒 | 1000 毫秒 | GotoBlas2 (1 CPU) | 266 毫秒 | 2100 毫秒 | 7280 毫秒 | GotoBlas2(2 个 CPU)| 139 毫秒 | 1009 毫秒 | 3690 毫秒 | GotoBlas2(8 个 CPU)| 54 毫秒 | 389 毫秒 | 1250 毫秒 | Mac OS X (1 CPU) | 143 毫秒 | 1060 毫秒 | 3605 毫秒 | Mac 服务器(1 个 CPU)| 92 毫秒 | 714 毫秒 | 2130 毫秒 |
代码:
有关基准套件的更多信息,请参见此处。
结果:
系统 | 特征值 | svd | 检测 | 库存 | 点 | netlib BLAS | 1688 毫秒 | 13102 毫秒 | 438 毫秒 | 2155 毫秒 | 3522 毫秒 | ATLAS (1 CPU) | 1210 毫秒 | 5897 毫秒 | 170 毫秒 | 560 毫秒 | 893 毫秒 | MKL(1 个 CPU)| 691 毫秒 | 4475 毫秒 | 141 毫秒 | 450 毫秒 | 736 毫秒 | MKL(2 个 CPU)| 552 毫秒 | 2718 毫秒 | 96 毫秒 | 267 毫秒 | 423 毫秒 | MKL(8 个 CPU)| 525 毫秒 | 1679 毫秒 | 60 毫秒 | 137 毫秒 | 197 毫秒 | GotoBlas2 (1 CPU) | 2124 毫秒 | 4636 毫秒 | 147 毫秒 | 456 毫秒 | 743 毫秒 | GotoBlas2(2 个 CPU)| 1560 毫秒 | 3278 毫秒 | 116 毫秒 | 295 毫秒 | 460 毫秒 | GotoBlas2(8 个 CPU)| 741 毫秒 | 2914 毫秒 | 82 毫秒 | 262 毫秒 | 192 毫秒 | Mac OS X (1 CPU) | 948 毫秒 | 4339 毫秒 | 151 毫秒 | 318 毫秒 | 566 毫秒 | Mac 服务器(1 个 CPU)| 1033 毫秒 | 3645 毫秒 | 99 毫秒 | 232 毫秒 | 342 毫秒 |
MKL的安装包括安装完整的英特尔编译器套件,这非常简单。然而,由于一些错误/问题,使用 MKL 支持配置和编译 numpy 有点麻烦。
GotoBlas2是一个可以轻松编译为共享库的小包。但是,由于存在错误,您必须在构建共享库后重新创建它才能与 numpy 一起使用。
除了为多个目标平台构建它之外,由于某种原因,它无法正常工作。所以我必须为每个平台创建一个.so文件,我想为其优化libgoto2.so文件。
如果您从 Ubuntu 的存储库安装 numpy,它将自动安装和配置 numpy 以使用ATLAS。从源代码安装ATLAS可能需要一些时间,并且需要一些额外的步骤(fortran 等)。
如果您在带有Fink或Mac Ports的 Mac OS X 机器上安装 numpy,它将配置 numpy 以使用ATLAS或Apple 的 Accelerate Framework。您可以通过在 numpy.core._dotblas文件上运行 ldd 或调用numpy.show_config()来检查。
MKL表现最好,紧随其后的是GotoBlas2。
在特征值测试中,GotoBlas2 的表现出乎意料地差于预期。不知道为什么会这样。
Apple 的 Accelerate Framework性能非常好,尤其是在单线程模式下(与其他 BLAS 实现相比)。
GotoBlas2和MKL都可以很好地扩展线程数。因此,如果您必须处理在多个线程上运行它的大型矩阵,将会有很大帮助。
在任何情况下都不要使用默认的netlib blas实现,因为它对于任何严肃的计算工作来说都太慢了。
在我们的集群上,我还安装了AMD 的 ACML,性能类似于MKL和GotoBlas2。我没有任何数字很难。
我个人会推荐使用GotoBlas2,因为它更容易安装并且是免费的。
如果你想用 C++/C 编写代码,还可以查看Eigen3 ,它在某些情况下应该优于MKL/GotoBlas2 ,而且也很容易使用。
并非所有 NumPy 都使用 BLAS,只有一些函数——特别dot()
是 、vdot()
和模块中innerproduct()
的几个函数。numpy.linalg
另请注意,许多 NumPy 操作受到大型数组的内存带宽的限制,因此优化的实现不太可能带来任何改进。如果您受到内存带宽的限制,多线程是否可以提供更好的性能在很大程度上取决于您的硬件。
可能是因为 Matrix x Matrix 乘法是内存受限的,所以在相同的内存层次结构上添加额外的内核不会给你太多。当然,如果您在切换到 Fortran 实现时看到显着的加速,那么我可能是不正确的。
我的理解是,对于这类问题,适当的缓存比计算能力重要得多。大概 BLAS 会为您执行此操作。
对于一个简单的测试,您可以尝试安装Enthought 的python 发行版进行比较。它们链接到英特尔的数学内核库,我相信它可以利用多个内核(如果可用)。
你听说过岩浆吗?GPU 和多核架构上的矩阵代数 http://icl.cs.utk.edu/magma/
MAGMA 项目旨在开发一个类似于 LAPACK 的密集线性代数库,但适用于异构/混合架构,从当前的“多核+GPU”系统开始。