本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2022年07月08日 统计字数: 9946字 阅读时间: 20分钟阅读 本文链接: https://soulteary.com/2022/07/08/into-vector-computing-compiling-openblas-from-source.html ----- # 走进向量计算:从源码编译 OpenBLAS 不论是折腾深度学习、高性能计算,还是折腾向量数据库、相似性检索领域,在折腾的过程中,我们都可能会遇到需要 “OpenBLAS” 这个开源矩阵计算库的场景。 这是因为泛 AI 领域离不开矩阵计算,而 OpenBLAS 是全球前三的开源矩阵计算库。本篇文章,我们就来聊聊 OpenBLAS 在 Linux 和 macOS 环境中的编译和构建。 ## 写在前面 ![OpenBLAS 在 GitHub 上的项目概况](https://attachment.soulteary.com/2022/07/08/openblas.jpg) OpenBLAS 官方项目从 [v0.3.6 版本](https://github.com/xianyi/OpenBLAS/releases/tag/v0.3.6)开始,提供了 **有限的** “预构建”二进制版本,可以帮助我们节约一些时间。 但是在一些场景下,我们可能需要从源码进行编译: - 比如,我们使用了官方未提供二进制版本的 OpenBLAS - 或者,我们需要在某些 CPU 平台 / 架构中来使用 “native” 的库,不能直接使用官方提供的现成的二进制文件 虽然,有一些 Linux 发行版的软件包仓库中提供了 OpenBLAS 的软件包,但是并不是每个发行版中都能够简单快速的安装到比较新的软件版本,比如 Ubuntu 18.04 里最新可用的版本是 0.2.20,Ubuntu 20.04 中最新的版本 0.3.8,只有在 Ubuntu 22.04 中,我们才能够看到 0.3.20 版,流行的用于制作容器的 Linux 发行版 Alpine 里的 OpenBLAS 也是如此。 但是,我们的实际生产环境和业务场景中,遇到老的操作系统,还是蛮常见的,使用固定的非最新版的软件的需求,也是蛮常见的。所以,为了解决这个问题,我们就需要了解如何使用源码编译它。 ## Ubuntu 环境下编译 OpenBLAS 在 2022 年,我们已经没有理由再使用 Ubuntu 20.04 / 22.04 之外的 LTS 作为生产环境。想在这两个环境中,使用上 “stable” 版本的 OpenBLAS,还是有一些细节上的不同的。 我们先从 Ubuntu 20.04 这个 LTS 版本聊起。 ### Ubuntu 20.04 环境的 OpenBLAS 在 [Ubuntu 20.04 的软件包仓库](https://packages.ubuntu.com/source/focal/openblas)中,我们能够找到的最新的 OpenBLAS 版本是 0.3.8,假如我们需要使用的 OpenBLAS 版本是 0.3.9 ,那么我们的问题解决方案,就**只有**一种:从源码编译。 先执行下面的命令,安装必要的依赖工具: ```bash sudo apt-get update && \ # common utils for download sources tarball/zipball sudo apt-get install -y --no-install-recommends curl wget ca-certificates gnupg2 && \ # openblas deps sudo apt-get install -y --no-install-recommends g++ gcc gfortran git make && \ # cleanup sudo apt-get remove --purge -y ``` 接着,下载我们所需要版本(0.3.9)的 OpenBLAS 源代码压缩包: ```bash OPENBLAS_VERSION=0.3.9 && \ wget "https://github.com/xianyi/OpenBLAS/archive/v${OPENBLAS_VERSION}.tar.gz" ``` 在下载完毕之后,将压缩包进行解压缩,并切换工作目录到代码目录中: ```bash OPENBLAS_VERSION=0.3.9 && \ tar zxvf v${OPENBLAS_VERSION}.tar.gz && cd OpenBLAS-${OPENBLAS_VERSION} ``` 然后,根据自己的需求,对 OpenBLAS 进行编译操作: ```bash sudo make TARGET=CORE2 DYNAMIC_ARCH=1 DYNAMIC_OLDER=1 USE_THREAD=0 USE_OPENMP=0 FC=gfortran CC=gcc COMMON_OPT="-O3 -g -fPIC" FCOMMON_OPT="-O3 -g -fPIC -frecursive" NMAX="NUM_THREADS=128" LIBPREFIX="libopenblas" LAPACKE="NO_LAPACKE=1" INTERFACE64=0 NO_STATIC=1 ``` 编译时间,会根据你的机器的配置而不同。8c16t 的笔记本十分钟不到就能够编译完毕,一台 4c8g 的云主机(虚拟机)可能要构建个把小时。所以需要一些耐心,以及根据自己的资源,适当使用 `tmux` 和 `screen` 来保持任务运行也是必要的。如果你的机器核心数比较少,可能时间会略漫长,可以起身活动活动,喝杯饮料,瞭望下远方。 当我们看到类似下面的内容的时候,OpenBLAS 的编译就完成了: ```bash ... make[1]: Leaving directory '/app/OpenBLAS-0.3.9/exports' OpenBLAS build complete. (BLAS CBLAS LAPACK LAPACKE) OS ... Linux Architecture ... x86_64 BINARY ... 64bit C compiler ... GCC (command line : gcc) Fortran compiler ... GFORTRAN (command line : gfortran) Library Name ... libopenblas-r0.3.9.a (Single threaded) To install the library, you can run "make PREFIX=/path/to/your/installation install". ``` 然后,我们来进行 OpenBLAS 的安装: ```bash sudo make -j4 PREFIX=/usr NO_STATIC=1 install ``` 不出意外的话,你将看到类似下面的日志输出,包含了 “Install OK”: ```bash ... make[1]: Entering directory '/app/milvus/OpenBLAS-0.3.9' Generating openblas_config.h in /usr/include Generating f77blas.h in /usr/include Generating cblas.h in /usr/include Copying LAPACKE header files to /usr/include Copying the shared library to /usr/lib Generating openblas.pc in /usr/lib/pkgconfig Generating OpenBLASConfig.cmake in /usr/lib/cmake/openblas Generating OpenBLASConfigVersion.cmake in /usr/lib/cmake/openblas Install OK! make[1]: Leaving directory '/app/milvus/OpenBLAS-0.3.9' ``` 当然,别忘记做收尾工作,清理不必要的“过程产物”: ```bash OPENBLAS_VERSION=0.3.9 && \ cd .. && \ rm -rf OpenBLAS-${OPENBLAS_VERSION} && \ rm v${OPENBLAS_VERSION}.tar.gz ``` 如果你需要构建其他版本的 OpenBLAS,可以将命令中的 `OPENBLAS_VERSION=0.3.9` 中的版本号,修改为你需要的版本。 如果我们能够接受,使用更新一些版本的 OpenBLAS 来替代指定的版本。也可以考虑直接使用 GitHub 中,OpenBLAS 官方提供的最新版本的[预构建二进制](https://github.com/xianyi/OpenBLAS/releases/tag/v0.3.20)文件,不少版本都包含了 `x86_64` 架构的二进制文件。 当然,这样做的前提是,你经过了充分的性能测试验证,确保计算效率能够符合你自己预期。 ### Ubuntu 22.04 环境的 OpenBLAS 在 Ubuntu 22.04 的环境中,相比较 Ubuntu 20.04,在不追求指定版本的情况下,除了从源码编译构建之外,我们的选择 **可以多一种**。 如果我们能够接受,使用更新一些版本的 OpenBLAS。除了和上文一样,从官方 GitHub 发布页面下载预构建好的二进制文件之外,还能使用 `apt install` 来下载安装 [Ubuntu 软件包仓库中的新版本 OpenBLAS 的软件包](https://packages.ubuntu.com/source/jammy/openblas)。 ```bash sudo apt-get update && \ sudo apt-get install libjulia-openblas64 ``` 不过,需要注意的是,这个软件包是由 Ubuntu 社区团队维护的(Ubuntu MOTU Developers),而非 Ubuntu 官方团队,软件包名称也并非 `lib-openblas`,而是 `libjulia-openblas64`,所以在长期使用的场景中,软件包可靠性还需要根据自己的场景来评估和衡量。 ## macOS 环境的 OpenBLAS 在 OpenBLAS 的官方仓库中,我们可以看到官方直到 2021 年 3 月才[开始完善 Apple M1 的支持](https://github.com/xianyi/OpenBLAS/commit/38dcf3454bf4d3a4b5b470791277904c025d7369)(毕竟搭载 M1 的设备 2020 年末才发布)。所以,在 macOS 环境中,如果是 M1 设备(ARMv64),**势必需要妥协** OpenBLAS 不能使用某些比较老的版本,需要升级到比较新的版本上来。 加之最近几年 Apple 已经下掉了所有的能够用于生产开发的 x86 Mac 产品,所以像 在 Ubuntu 低版本中折腾 OpenBLAS 编译构建那样,在 macOS 环境中折腾 OpenBLAS 的构建,意义真的不大。 多数场景中,我们直接使用 `brew install openblas` 命令,就能完成 `openblas` 的安装了(新版本的预构建二进制): ```bash brew install openblas ==> Downloading https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/openblas-0.3.20.arm64_monterey.bottle.tar.gz ######################################################################## 100.0% ==> Pouring openblas-0.3.20.arm64_monterey.bottle.tar.gz ==> Caveats openblas is keg-only, which means it was not symlinked into /opt/homebrew, because macOS provides BLAS in Accelerate.framework. For compilers to find openblas you may need to set: export LDFLAGS="-L/opt/homebrew/opt/openblas/lib" export CPPFLAGS="-I/opt/homebrew/opt/openblas/include" ==> Summary 🍺 /opt/homebrew/Cellar/openblas/0.3.20: 23 files, 56.1MB ==> Running `brew cleanup openblas`... Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP. Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`). ``` 如果你实在想玩 macOS 下的编译的话,可以先使用 `brew` 安装必要的组件(`gfortran`),然后参考 Ubuntu 20.04 中的方式来完成组件的构建。不过记得调整一些参数: ```bash # 添加新的环境变量 export MACOSX_DEPLOYMENT_TARGET=11.0 # 修改默认配置中的参数 TARGET=ARMV8 CC=cc ``` 官方项目对于构建相关的文档细节,有记录在这里,[有需要自取](https://github.com/xianyi/OpenBLAS/wiki/How-to-build-OpenBLAS-for-macOS-M1---arm64)。 当然,如果你就是需要某个老版本的 OpenBLAS,并愿意折腾的话,也可以将目前 0.3.9 \~ 0.3.20 版本中的修改调整,反向合并到 0.3.9 版本中,构建一个特别的 M1 设备适用的版本,不过因为这个操作属于没有意义瞎折腾,所以我们就不展开描述啦。 ## 验证 OpenBLAS 是否可用 想要验证 OpenBLAS 的编译、安装结果是否可用,其实非常简单。我们可以参考官方示例,先创建一个简单的程序: ```c #include #include void main() { int i=0; double A[6] = {1.0,2.0,1.0,-3.0,4.0,-1.0}; double B[6] = {1.0,2.0,1.0,-3.0,4.0,-1.0}; double C[9] = {.5,.5,.5,.5,.5,.5,.5,.5,.5}; cblas_dgemm(CblasColMajor, CblasNoTrans, CblasTrans,3,3,2,1,A, 3, B, 3,2,C,3); for(i=0; i<9; i++) printf("%lf ", C[i]); printf("\n"); } ``` 将上面的内容保存为 `test_cblas_dgemm.c` 之后,我们执行下面的命令,尝试将其编译为可执行程序: ```c cc -static -o test_cblas_open test_cblas_dgemm.c -I /usr/include/ -L/usr/lib -lopenblas -lpthread -lgfortran ``` 如果你不确定在上面编译命令中,该使用什么样的路径参数,可以使用 `find / | grep openblas` 来搜索系统里所有和 `OpenBLAS` 相关的路径,寻找其中包含 `/usr/lib/libopenblas.so.0` 和 `/usr/include/openblas_config.h` 文件的路径即可。 如果你是 macOS 环境,并使用 `brew` 安装的 OpenBLAS,那么需要将下面两个环境变量声明在你的 SHELL 的 rc 文件中: ```bash export LDFLAGS="-L/opt/homebrew/opt/openblas/lib" export CPPFLAGS="-I/opt/homebrew/opt/openblas/include" ``` 当程序编译完毕,我们将能够得到一个名为 `test_cblas_open` 的程序,直接执行它,将能够看到类似下面的输出结果: ```bash # ./test_cblas_open 11.000000 -9.000000 5.000000 -9.000000 21.000000 -1.000000 5.000000 -1.000000 3.000000 ``` 不过使用官方的例子,只能证明我们的程序能够调用 OpenBLAS 进行计算,OpenBLAS 编译结果是可执行的,但是还不能够确认编译结果是正确、可靠的。 为了验证 OpenBLAS 编译后是可靠的,我们可以参考 [HomeBrew 的测试程序](https://github.com/Homebrew/homebrew-core/blob/master/Formula/openblas.rb#L53-L72),对上面官方示例代码做一些小小的调整,对 OpenBLAS 计算结果进行简单的“断言”,确保编译结果在计算方面是相对可靠的: ```c #include #include #include #include "cblas.h" int main(void) { int i; double A[6] = {1.0, 2.0, 1.0, -3.0, 4.0, -1.0}; double B[6] = {1.0, 2.0, 1.0, -3.0, 4.0, -1.0}; double C[9] = {.5, .5, .5, .5, .5, .5, .5, .5, .5}; cblas_dgemm(CblasColMajor, CblasNoTrans, CblasTrans, 3, 3, 2, 1, A, 3, B, 3, 2, C, 3); for (i = 0; i < 9; i++) printf("%lf ", C[i]); printf("\\n"); if (fabs(C[0]-11) > 1.e-5) abort(); if (fabs(C[4]-21) > 1.e-5) abort(); return 0; } ``` 我们将上面的内容保存为 `test-openblas.c`,然后执行命令,编译测试程序: ```bash cc -static -o test-openblas test-openblas.c -I /usr/include/ -L/usr/lib -lopenblas -lpthread -lgfortran ``` 命令执行完毕之后,我们得到 `./test-openblas` 这个可执行文件,然后执行程序,并采集程序 `exit code` 来完成对 OpenBLAS 的验证: ```bash ./test-openblas; test $? -eq 0 && echo "a happy ending" ``` 如果编译的程序没有问题,那么将得到包含 “a happy ending” 的输出结果: ```bash 11.000000 -9.000000 5.000000 -9.000000 21.000000 -1.000000 5.000000 -1.000000 3.000000 \na happy ending ``` ## 聊聊 OpenBLAS 这个项目 我接触和使用到这个“并不算年轻”的项目,是因为最近在折腾[向量数据库专栏](https://www.zhihu.com/column/c_1527733792156049408),计划在写的 [Milvus 相关](https://github.com/milvus-io/milvus)的内容,有一部分组件的编译安装,有使用到这个项目。 在 2011 年,来自中科院的[张先轶](http://xianyi.github.io/)博士,创造了 OpenBLAS 这个开源矩阵计算库的第一个版本:0.1 alpha1。随后这个库经历了长达十年的马拉松式的迭代,在 GitHub 平台上,各种版本和平台的 “OpenBLAS” 也陆续出现,甚至 OpenBLAS **一度是** Facebook 推出的 [faiss](https://github.com/facebookresearch/faiss) 开源高性能相似性搜索、向量聚类库的组件之一(长时间作为 [CPU 计算时的必须组件](https://github.com/facebookresearch/faiss/blob/v1.6.3/example_makefiles/makefile.inc.Linux),以及在[GPU 镜像中使用](https://github.com/facebookresearch/faiss/blob/79e74fe3075e494abcc909b4988ef3c3cb059f72/.circleci/Dockerfile.faiss_gpu#L5))。 而这个名为 faiss 的基础库,有多么重要呢?国内外有数不清的开源闭源的产品都依赖它做向量计算,在阿里达摩院对于自研产品 [Proxima 的首次公开 PR 里](https://developer.aliyun.com/article/782391)(2021年 3 月),就有曾提到:“目前,业内普遍使用的向量检索库是 Facebook AI 团队开源的 Faiss (Facebook AI Similarity Search) 引擎...” 直到 2019 年,在一次[关键提交中](https://github.com/facebookresearch/faiss/pull/769),Facebook 的 Research 团队,使用 [Intel MKL BLAS](https://www.intel.com/content/www/us/en/develop/documentation/onemkl-developer-reference-c/top.html)(Math Kernel Library) 逐步替换了 OpenBLAS,并在两年后的版本中的[安装文档](https://github.com/facebookresearch/faiss/blob/b4eb51dae81084b29ca77834fd9b0537045853e5/INSTALL.md)中指出,使用 Intel MKL BLAS 相比 OpenBLAS 能够得到更高的性能,至此之后,除了能够在该项目的 CI 镜像中、老用户的[测试反馈中](https://github.com/facebookresearch/faiss/issues/2170)见到 OpenBLAS 的身影,更多的时候,在这个项目里,我们能够看到的越来越多的是 Intel MKL 的身影,而 Intel MKL 逐步发力的 2018~2019 年([完整历程](https://www.intel.com/content/www/us/en/developer/articles/guide/download-documentation-intel-system-studio-current-previous.html)),正是 OpenBLAS 团队创业求生,开始把重心放在[“Perf-V”开发板的时候](http://perfv.org/cn/posts.html)。 除了 faiss 之外,在全球两大框架 PyTorch 和 Tensorflow 的社区里,也有不少和 OpenBLAS 相关的内容,比如[这里](https://github.com/pytorch/pytorch/issues?q=is%3Aissue+openblas+)和[这里](https://github.com/tensorflow/tensorflow/issues?q=is%3Aissue+openblas),可惜的是,在缺少官方运营支持和维护的情况下,这些内容显然不能够得到很好的解决。当有着类似问题的开发者凭借“错误日志”、“报错关键词”找到相关社区帖子的时候,发现这个问题是“悬而未决的”,那么或许会转头去寻找看起来维护者更多的解决方案。(目前的 OpenBLAS 只有一位位于德国的小哥在积极维护中) 作为开源软件从业者,看到开源软件,尤其是由国人创建的开源软件在全球头部开源仓库中被逐步“汰换”,还是比较难不产生一股悲凉感。在十年的时间里,这款项目也只拿到了部分用户的 “一键三连”,只有 4 千来颗星星,在当前 GitHub 平台上实在不是一个很好看的数据,虽然衡量开源软件价值的从来都不是这些“仓库运营数据”,但是这些数字却实打实的影响着平台上到底有多少人能够看到这个项目。 所以,如果你有幸看到了这篇文章的这里,不妨从现在开始,对对你有帮助的内容,不论是文章还是项目,都慷慨的给出一个免费的、大大的赞吧,你的每一次投票,都能够让这个世界变的更好,虽然只有一点点。 ## 最后 好啦, OpenBLAS 的编译和项目的故事就聊到这里。 接下来更新的内容里,我会持续更新 “向量数据库” 行业中的产品,并进行实战分享。希望能够帮助到不同场景下,需要做技术选型的朋友,做出合适的、正确的选择,少走弯路,远离深坑。 --EOF