如何在Amazon EMR、EC2上为Breeze配置高性能的BLAS/LAPACK?

28

我正在尝试搭建一个支持集群上探索性数据分析的环境。根据对现有资源的初步调查,我的目标是使用Scala/Spark和Amazon EMR来配置集群。

目前,我只是想运行一些基本示例来验证我是否已正确配置了所有内容。但问题是,我没有看到在Amazon机器实例上预期的Atlas BLAS库的性能。

以下是我的简单基准测试的代码片段。它只是一个平方矩阵乘法,然后是一个短而胖的乘法和一个高而瘦的乘法,生成一个可以打印的小矩阵(我想确保Scala不会因为惰性评估而跳过任何计算步骤)。

我使用Breeze作为线性代数库,并使用netlib-java引入本地原生BLAS / LAPACK库。

import breeze.linalg.{DenseMatrix, DenseVector}
import org.apache.spark.annotation.DeveloperApi
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partition, SparkContext, TaskContext}
import org.apache.spark.SparkConf

import com.github.fommil.netlib.BLAS.{getInstance => blas}

import scala.reflect.ClassTag

object App {

  def NaiveMultiplication(n: Int) : Unit = {

    val vl = java.text.NumberFormat.getIntegerInstance.format(n)
    println(f"Naive Multipication with vector length " + vl)

    println(blas.getClass().getName())

    val sm: DenseMatrix[Double] = DenseMatrix.rand(n, n)
    val a: DenseMatrix[Double] = DenseMatrix.rand(2,n)
    val b: DenseMatrix[Double] = DenseMatrix.rand(n,3)

    val c: DenseMatrix[Double] = sm * sm
    val cNormal: DenseMatrix[Double] = (a *  c)  * b

    println(s"Dot product of a and b is \n$cNormal")
  }

基于网络调查的基准测试,我预计使用本地优化的BLAS库进行3000x3000矩阵乘法大约需要2-4秒。在我的MacBook Air上本地运行该基准测试仅需1.8秒。但在EMR上运行该基准测试需要约11秒(使用g2.2xlarge实例,在m3.xlarge实例上也获得了类似的结果)。为了进一步验证,我在相同的EC2实例类型g2.2xlarge上运行来自BIDMach项目的预构建EC2 AMI,获得了2.2秒的结果(请注意,同样计算的GPU基准测试仅需0.047秒)。

目前我怀疑是netlib-java没有正确加载库,但我卡在这里了。我已经多次阅读了netlib-java的README文件,看起来ATLAS库已按要求安装(见下文)。

[hadoop@ip-172-31-3-69 ~]$ ls /usr/lib64/atlas/
libatlas.a       libcblas.a       libclapack.so      libf77blas.so      liblapack.so      libptcblas.so      libptf77blas.so
libatlas.so      libcblas.so      libclapack.so.3    libf77blas.so.3    liblapack.so.3    libptcblas.so.3    libptf77blas.so.3
libatlas.so.3    libcblas.so.3    libclapack.so.3.0  libf77blas.so.3.0  liblapack.so.3.0  libptcblas.so.3.0  libptf77blas.so.3.0
libatlas.so.3.0  libcblas.so.3.0  libf77blas.a       liblapack.a        libptcblas.a      libptf77blas.a
[hadoop@ip-172-31-3-69 ~]$ cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
[hadoop@ip-172-31-3-69 ~]$ ls /etc/ld.so.conf.d
atlas-x86_64.conf  kernel-4.4.11-23.53.amzn1.x86_64.conf  kernel-4.4.8-20.46.amzn1.x86_64.conf  mysql55-x86_64.conf  R-x86_64.conf
[hadoop@ip-172-31-3-69 ~]$ cat /etc/ld.so.conf.d/atlas-x86_64.conf 
/usr/lib64/atlas

下面我展示了在 Amazon EMR 实例上运行基准测试的两个示例。第一个示例显示当原生系统BLAS正确加载时的情况。第二个示例显示当本机 BLAS 未加载并且软件包退回到参考实现时的情况。因此,根据消息和时间,似乎确实正在加载本地 BLAS。与在我的 Mac 上本地运行相比,无 BLAS 情况大约在相同时间内运行,但本地 BLAS 情况在我的 Mac 上运行1.8秒,而在下面的情况下需要15秒。除了特定的目录/文件名等之外,我的 Mac 和 EMR 的信息消息是相同的。

[hadoop@ip-172-31-3-69 ~]$ spark-submit --class "com.cyberatomics.simplespark.App" --conf "spark.driver.extraClassPath=/home/hadoop/simplespark-0.0.1-SNAPSHOT-jar-with-dependencies.jar"   --master local[4] simplespark-0.0.1-SNAPSHOT-jar-with-dependencies.jar  3000 naive
Naive Multipication with vector length 3,000
Jun 16, 2016 12:30:39 AM com.github.fommil.jni.JniLoader liberalLoad
INFO: successfully loaded /tmp/jniloader2856061049061057802netlib-native_system-linux-x86_64.so
com.github.fommil.netlib.NativeSystemBLAS
Dot product of a and b is 
1.677332076284315E9   1.6768329748988206E9  1.692150656424957E9   
1.6999000993276503E9  1.6993872020220244E9  1.7149145239563465E9  
Elapsed run time:  15.1s
[hadoop@ip-172-31-3-69 ~]$ 
[hadoop@ip-172-31-3-69 ~]$ spark-submit --class "com.cyberatomics.simplespark.App"  --master local[4] simplespark-0.0.1-SNAPSHOT-jar-with-dependencies.jar  3000 naive
Naive Multipication with vector length 3,000
Jun 16, 2016 12:31:32 AM com.github.fommil.netlib.BLAS <clinit>
WARNING: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS
Jun 16, 2016 12:31:32 AM com.github.fommil.netlib.BLAS <clinit>
WARNING: Failed to load implementation from: com.github.fommil.netlib.NativeRefBLAS
com.github.fommil.netlib.F2jBLAS
Dot product of a and b is 
1.6640545115052865E9  1.6814609592261212E9  1.7062846398842275E9  
1.64471099826913E9    1.6619129531594608E9  1.6864479674870768E9  
Elapsed run time:  28.7s

目前我的最佳猜测是它实际上正在加载本地库,但是它正在加载通用库。你有没有任何建议,我如何验证它在运行时选择了哪个共享库?我尝试使用'ldd',但似乎无法与spark-submit一起使用。或者也许我对Atlas的期望值是错误的,但是似乎难以相信如果它们不能以相当竞争的速度运行,AWS会预安装这些库。

如果您发现EMR上的库未正确链接,请指导我应该怎么做才能使netlib-java选择Atlas库。

谢谢 tim


1
你能把“跟进”转化为一个答案吗?它提供了有用的见解,如果没有其他答案,我想授予悬赏。提前致谢! - user6022341
我甚至无法重现您第一次拉取默认的EMR Atlas本地库的情况。您是否做了其他不在帖子中列出的不同操作,导致使用本地库而不是F2jBLAS?无论我尝试什么,似乎仍然得到F2J。 - Nathaniel Wendt
我已经很久没看过这个了。我认为netlib与Breeze集成的方式有所改变。但是据我回忆,解决您的问题的关键是包含包含本地库存根的.jar文件。当我首次发布上述内容时,必须在附加路径变量中显式包含该jar文件。它不会随我的应用程序一起打包在fat jar中。这是一个关于设置netlib使用BLAS的好文章:https://datasciencemadesimpler.wordpress.com/tag/blas/ - Tim Ryan
是的,我最终从上面和其他一些我找到的随机线程中弄清楚了这个问题。你的帖子和答案在我的过程中非常有帮助,感谢! - Nathaniel Wendt
2个回答

12

跟进:

我初步的结论是,在Amazon EMR实例上默认安装的Atlas库非常慢。它可能是一个通用构建,没有针对具体机器类型进行优化,或者它本质上比其他库更慢。使用这个 脚本 作为指南,我为运行基准测试的特定机器类型构建并安装了OpenBLAS(我还在这里找到了一些有用的信息)。一旦安装了OpenBLAS,我的3000x3000矩阵乘法基准测试完成时间为3.9秒(与使用默认的Atlas库时列出的15.1秒相比)。这仍然比在我的Mac上运行相同基准测试慢(差了2倍),但这种差异在硬件性能的基础下可以合理地解释。

这是我在Amazon的EMR Spark实例上安装OpenBLAS库时使用的完整命令列表:

sudo yum install git
git clone https://github.com/xianyi/OpenBlas.git
cd OpenBlas/
make clean
make -j4
sudo mkdir /usr/lib64/OpenBLAS
sudo chmod o+w,g+w /usr/lib64/OpenBLAS/
make PREFIX=/usr/lib64/OpenBLAS install
sudo rm /etc/ld.so.conf.d/atlas-x86_64.conf 
sudo ldconfig
sudo ln -sf /usr/lib64/OpenBLAS/lib/libopenblas.so /usr/lib64/libblas.so
sudo ln -sf /usr/lib64/OpenBLAS/lib/libopenblas.so /usr/lib64/libblas.so.3
sudo ln -sf /usr/lib64/OpenBLAS/lib/libopenblas.so /usr/lib64/libblas.so.3.5
sudo ln -sf /usr/lib64/OpenBLAS/lib/libopenblas.so /usr/lib64/liblapack.so
sudo ln -sf /usr/lib64/OpenBLAS/lib/libopenblas.so /usr/lib64/liblapack.so.3
sudo ln -sf /usr/lib64/OpenBLAS/lib/libopenblas.so /usr/lib64/liblapack.so.3.5

1
我在AWS上运行的步骤仍然声称我正在运行F2J。您采取了哪些其他步骤来将Breeze指向这个本地库?您是否包含了breeze-native依赖项?在您的问题中包含额外的classPath的意义是什么?在您的示例中,似乎这就是捕获本地库而不是F2J的方法。 - Nathaniel Wendt
@NathanielWendt :对于将来遇到这个问题的人,我列出了加载本地库的步骤。https://gist.github.com/dineshdharme/8bd39cdbc35a09033b9ba2cfd1bdf146 - undefined

0
根据下面的指南,我可以让本地的BLAS库正常工作。

https://spark.apache.org/docs/latest/ml-linalg-guide.html

https://github.com/luhenry/netlib

sbt 依赖项:
```scala "dev.ludovic.netlib" % "blas" % "3.0.3" ```
然后在你的 Scala 代码中,使用以下语句进行检查:
// import statement
import dev.ludovic.netlib.blas.NativeBLAS

// print this in your function
println(s"BLAS dev.ludovic.netlib.blas.NativeBLAS: ${NativeBLAS.getInstance()}")

应该打印出类似这样的内容:
BLAS dev.ludovic.netlib.blas.NativeBLAS: dev.ludovic.netlib.blas.JNIBLAS@68a4dcc6

编辑:实际上,仅仅按照上述步骤进行操作对我来说并不起作用。
我不得不单独编译这些JAR文件并让Spark使用它们。我已经在这个要点中详细说明了编译JAR文件的步骤。

https://gist.github.com/dineshdharme/8bd39cdbc35a09033b9ba2cfd1bdf146

如果你不想经历繁琐的过程,我建议直接跳到第三步,按照说明进行操作。

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