3

走进向量计算:制作 OpenBLAS Docker 预构建产物镜像

 2 years ago
source link: https://soulteary.com/2022/07/31/into-vector-computing-making-openblas-docker-prebuilt-product-images.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

走进向量计算:制作 OpenBLAS Docker 预构建产物镜像

2022年07月31日阅读Markdown格式12192字25分钟阅读

本篇文章,将聊聊如何在容器中完成支持多 CPU 架构(x86、ARM)、多种 CPU 类型(Intel、AMD)的 OpenBLAS 的 Docker 镜像,来让包括 Milvus 在内的,使用 OpenBLAS 的软件的 Docker 镜像构建可以“又稳又快”。

我会试着在接下来的内容中,持续分享如何让一个拥有着一万多颗“星星”的大型数据库项目的容器构建过程不断提速。

有阅读过以往内容的同学,应该会记得,我在之前的介绍“Milvus 源码编译安装”的文章中,有提到过“要聊聊如何构建一个可靠、可信赖的 Milvus 容器镜像”。

之前提到过,因为 Milvus 使用的核心搜索引擎 knowhere 有使用到 OpenBLAS。以及,相信有过本地构建经验、或者参考前面文章“走进向量计算:从源码编译 OpenBLAS”进行过构建的同学,对于 OpenBLAS 项目的构建时间漫长,应该是深有体会的。并且,在不同 CPU 架构(x86、ARM),不同类型 CPU(Intel、AMD)的环境下,OpenBLAS 编译产物也是不同的。

所以,如果我们想搞定支持多种 CPU 架构的 Milvus 容器镜像,自然要先解决多种架构和类型硬件的 OpenBLAS 在容器内的构建。

如果我们使用 GitHub Action (免费版),你会发现 CI 跑一天都构建不出来 OpenBLAS 这个基础依赖,即使我们采用 8 Cores 这类常见规格的云服务器,也需要“哼哧哼哧”的跑个一个钟头,这里如果我们使用 docker buildx 来模拟不同硬件的话,很有可能跑 4~5 个钟头不见得有结果(可以参考 CI 中大量跑了一天以上被自动取消的任务)。

而如果我们使用预构建的方式,临时采用“按需付费”的方式,找一台配置较高的机器,或者利用本地高配置的机器,花十几分钟到半个小时,提前做好预构建产物的编译。那么,之后的构建时间,通常就能够缩短到只需要“几秒钟”了,因为文件复制的计算量非常少。

所以,想要减少开发和构建 Milvus 所需要的时间,在确定的容器环境中,制作预构建产物来减少重复构建花费的大量时间,就变得十分必要的啦。

既然是容器环境的产物预构建,那么,我们自然需要先完成 Docker 运行环境的安装,如果你的构建环境中已经安装过了 Docker,那么可以跳过这个小节,阅读后面的内容。

如果你是桌面运行环境,可以访问官网下载安装文件,如果你使用的是服务端环境,可以参考这篇文章中的“更简单的 Docker 安装”,来完成 Docker 环境的准备。

当然,如果你没有运行 Linux 系统的设备,使用 macOS 或者在 Windows 中使用虚拟机,也是一样的。

当然,不同硬件架构能够使用的编译参数是有不同的,所以这里,我们需要根据未来计划运行 Milvus 或其他软件所需要的硬件环境,准备对应的硬件,来完成基础依赖的编译。即使Docker Buildx 只能模拟 CPU 架构来进行 Multi-ARCH 构建,但是却无法模拟 CPU 类型,无法满足不同指令集的硬件产物构建。

前文中,我们有提到需要构建不止一种产物镜像,简单来说:

  • 我们需要构建出最新和次新 Ubuntu LTS 环境下的软件,包含目前 Milvus 长期使用的 0.3.9 版本,以及未来考虑升级的最新的稳定的版本:0.3.20 。
  • 以及考虑到目前 Milvus 官方镜像还是基于 Ubuntu 18.04,所以构建这个环境下的产物,对于能够平滑替换,进行验证也是必要的。
  • 考虑到目前不仅是云环境,还是本地环境, ARM 芯片和 AMD Zen 架构的 CPU 都越来越多,所以,我们也需要考虑这两个环境下的产物构建。

当然,因为 OpenBLAS 不同版本,在ARM 芯片、AMD ZEN 架构 CPU 下存在一些兼容问题,即使我们能够得到构建产物,产物其实也并不能够通过软件的单元测试。所以,我们在构建的过程中,会忽略掉构建结果不能 100% 通过测试的“组合”。

好了,我们先来聊聊最常见的 Intel CPU 的产物构建吧。

基于 Intel x86 架构 CPU 的容器预构建

因为不同类型、架构的 CPU,能够构建不同 OpenBLAS 的可靠产物是不同的,所以我们先来明确要构建哪些版本。在 Intel x86 芯片环境下,我们需要满足下面的需求:

  • 最新 Ubuntu LTS 版本 22.04 环境下的两个版本的 OpenBLAS:0.3.9 和 0.3.20,满足未来 Milvus 升级到最新的 Ubuntu LTS 时使用。
  • 上一个 Ubuntu 稳定 LTS 版本 20.04 环境下的 OpenBLAS:0.3.9 和 0.3.20,满足未来 Milvus 能够升级到次新 LTS 版本时使用。
  • 目前 Milvus 官方镜像使用的 Ubuntu LTS 版本 18.04 环境下的 OpenBLAS:0.3.9 和 0.3.20,满足当前版本的 Milvus ,能够平滑切换 OpenBLAS 依赖安装,以及验证最新版本的 OpenBLAS 使用。

简单来说,就是我们需要分别构建 Ubuntu 18.04~22.04 环境下,0.3.9 和 0.3.20 两个版本的 OpenBLAS 镜像,来满足当前状况的 Milvus、过渡期的 Milvus,以及适合长远的 Milvus 使用的 OpenBLAS 预构建产物。

设计 Intel CPU 使用的通用 Dockerfile 镜像文件

即使我们需要根据排列组合做镜像,但可维护性依旧是我们最需要考虑的地方,因此,我们最好是能够使用一个 Dockerfile 文件来管理同 CPU 架构的镜像(按照 CPU 架构拆分镜像):

# 允许使用参数来动态改变我们所使用的基础镜像
ARG LTS=22.04
FROM ubuntu:${LTS} AS Base
# (示意) 安装必要的依赖
RUN apt-get update && \
    apt-get install ... && \
    apt-get remove --purge -y
# 设置工作目录
WORKDIR /src
# 允许使用参数来指定 OpenBLAS 的版本,从官方发布页面获取软件源码
ARG OPENBLAS_VERSION=0.3.9
ENV OPENBLAS_VERSION=${OPENBLAS_VERSION}
RUN wget "https://github.com/xianyi/OpenBLAS/archive/v${OPENBLAS_VERSION}.tar.gz" && \
    tar zxvf v${OPENBLAS_VERSION}.tar.gz && rm v${OPENBLAS_VERSION}.tar.gz
# 改变工作目录
WORKDIR /src/OpenBLAS-${OPENBLAS_VERSION}
# (示意) 使用适合 Intel 芯片的参数,进行编译和安装
RUN make && make install

# 将构建后的产物保存到一个干净的空镜像里,为后续使用做准备
FROM scratch
ARG OPENBLAS_VERSION=0.3.9
ENV OPENBLAS_VERSION=${OPENBLAS_VERSION}
COPY --from=Base /usr/lib/libopenblas-r${OPENBLAS_VERSION}.so /usr/lib/

上面是隐藏了一些细节(依赖安装、编译命令)的基础镜像设计,首先根据用户传递的构建参数,来确定要使用的基础 Linux 环境,和要构建的 OpenBLAS 产物版本。然后再将构建完毕的内容,复制到一个崭新的空白容器里,来简化容器复杂度,以及方便后续 Milvus 或其他软件的构建过程使用。

或许有小伙伴好奇,为什么一定要使用多阶段构建呢。如果我们没有进行多阶段构建,剥离环境和构建产物,那么我们得到的预构建镜像,大概会是下面这样的“壮观”体积。

soulteary/milvus-openblas     0.3.20-intel-x86-ubuntu-22.04     3.95GB
soulteary/milvus-openblas     0.3.20-intel-x86-ubuntu-20.04     9.03GB
soulteary/milvus-openblas     0.3.20-intel-x86-ubuntu-18.04     7.54GB
soulteary/milvus-openblas     0.3.9-intel-x86-ubuntu-22.04      3.35GB
soulteary/milvus-openblas     0.3.9-intel-x86-ubuntu-20.04      6.68GB
soulteary/milvus-openblas     0.3.9-intel-x86-ubuntu-18.04      5.55GB

当采用了多阶段构建之后,即使在不进行额外压缩的前提下,产物镜像尺寸也能够得到非常明显的变化:

soulteary/milvus-openblas     0.3.20-intel-x86-ubuntu-22.04     143MB
soulteary/milvus-openblas     0.3.20-intel-x86-ubuntu-20.04     318MB
soulteary/milvus-openblas     0.3.20-intel-x86-ubuntu-18.04     266MB
soulteary/milvus-openblas     0.3.9-intel-x86-ubuntu-22.04      122MB
soulteary/milvus-openblas     0.3.9-intel-x86-ubuntu-20.04      241MB
soulteary/milvus-openblas     0.3.9-intel-x86-ubuntu-18.04      201MB

完整文件,我上传到了 GitHub,方便有需要的同学自取:soulteary/docker-openblas/blob/main/intel/Dockerfile

我们将上面的内容保存为 Dockerfile,就能够正式进行 Intel x86 CPU 环境下的镜像构建了。

Ubuntu 和 Intel 环境下的 OpenBLAS 构建

先来处理 Ubuntu 22.04 系统环境下,0.3.9 版本 OpenBLAS 的构建:

docker build --build-arg=LTS=22.04 --build-arg=OPENBLAS_VERSION=0.3.9 -t soulteary/milvus-openblas:0.3.9-intel-22.04 .

执行命令,经过漫长的等待之后,将能够看到类似下面的输出,包含了一大堆“测试通过”的日志信息和容器“构建完毕”的提示:

...
OPENBLAS_NUM_THREADS=2 ./xscblat1
 Real CBLAS Test Program Results


 Test of subprogram number  1         CBLAS_SDOT     
                                    ----- PASS -----

 Test of subprogram number  2         CBLAS_SAXPY    
                                    ----- PASS -----
...
 cblas_zsyr2k PASSED THE TESTS OF ERROR-EXITS

 cblas_zsyr2k PASSED THE COLUMN-MAJOR COMPUTATIONAL TESTS (  1764 CALLS)
 cblas_zsyr2k PASSED THE ROW-MAJOR    COMPUTATIONAL TESTS (  1764 CALLS)

 END OF TESTS
...
make[1]: Leaving directory '/src/OpenBLAS-0.3.9/exports'

 OpenBLAS build complete. (BLAS CBLAS LAPACK)

  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".

Removing intermediate container 41f18b3da43e
 ---> c5545163375d
Step 10/10 : RUN make PREFIX=/usr NO_STATIC=1 install
 ---> Running in 80583523b475
make -j 8 -f Makefile.install install
make[1]: Entering directory '/src/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 '/src/OpenBLAS-0.3.9'
...
...
Removing intermediate container 80583523b475
 ---> 980e4e15139b
Successfully built 980e4e15139b
Successfully tagged soulteary/milvus-openblas:0.3.9-intel-20.04

如果你的构建环境获取 GitHub Release 中的源码包存在网络问题,可以考虑在构建参数中使用 --build-arg=https_proxy=YOUR_PROXY_ADDR ,强制内容获取走你的指定网络来解决问题。

同样的,我们可以执行下面的命令,来搞定 Ubuntu 22.04 环境下,OpenBLAS 0.3.20 版本的镜像构建:

docker build --build-arg=LTS=22.04 --build-arg=OPENBLAS_VERSION=0.3.20 -t soulteary/milvus-openblas:0.3.20-intel-22.04 .

同理,我们可以根据切换参数的内容,来完成 Ubuntu 20.04 和 Ubuntu 18.04 系统版本下 OpenBLAS 0.3.9 和 0.3.20 两个版本的镜像构建:

docker build \
    --build-arg=LTS=20.04 \
    --build-arg=OPENBLAS_VERSION=0.3.9 \
    -t soulteary/milvus-openblas:0.3.9-intel-x86-ubuntu-20.04 .
docker build \
    --build-arg=LTS=20.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-20.04 .
...

为了节约篇幅,完整内容,我已经上传到了 GitHub,有需要的同学可以自取:soulteary/docker-openblas/blob/main/intel/build.sh

基于 AMD Zen 架构 CPU 的容器预构建

和 Intel x86 小节中的最大不同是,在 AMD Zen 架构的 CPU 的容器构建中,由于比较老的版本的 OpenBLAS 在该架构上的兼容性存在问题,即使能构建出来产物,看着一堆堆的测试报错、警告,以及测试安装时的错误日志,也没有人能放心的使用它们,所以我们只构建 OpenBLAS 0.3.20 版本。

此外,在构建 Intel x86 架构 CPU 的时候,我们的构建参数使用的是 TARGET=CORE2,在构建 AMD Zen 架构镜像的时候,需要替换为 TARGET=ZEN。完整的镜像文件,我上传到了 GitHub:soulteary/docker-openblas/blob/main/amd-zen/Dockerfile,有需要可以自取。

构建命令,和构建 Intel x86 时类似,也是通过改变参数,来调整构建环境,和使用软件的版本:

docker build \
    --build-arg=LTS=22.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-22.04 .

docker build \
    --build-arg=LTS=20.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-20.04 .

docker build \
    --build-arg=LTS=18.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-18.04 .

命令执行完毕,将会得到类似下面的结果:

...
make[1]: Leaving directory '/src/OpenBLAS-0.3.20/exports'

 OpenBLAS build complete. (BLAS CBLAS LAPACK)

  OS               ... Linux             
  Architecture     ... x86_64               
  BINARY           ... 64bit                 
  C compiler       ... GCC  (cmd & version : gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0)
  Fortran compiler ... GFORTRAN  (cmd & version : GNU Fortran (Ubuntu 11.2.0-19ubuntu1) 11.2.0)
  Library Name     ... libopenblas-r0.3.20.a (Single-threading)  
  Supporting multiple x86_64 cpu models with minimum requirement for the common code being ZEN

To install the library, you can run "make PREFIX=/path/to/your/installation install".

Removing intermediate container f276420c3691
 ---> a094af160f34
Step 10/10 : RUN make PREFIX=/usr NO_STATIC=1 install
 ---> Running in aa4ce3961832
make -j 16 -f Makefile.install install
make[1]: Entering directory '/src/OpenBLAS-0.3.20'
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 '/src/OpenBLAS-0.3.20'
...
Removing intermediate container aa4ce3961832
 ---> 5aebe4bd2ed3
Successfully built 5aebe4bd2ed3
Successfully tagged soulteary/milvus-openblas:0.3.20-amd-zen-ubuntu-22.04

最终,将镜像都构建完毕之后,我们能够得到下面的结果:

soulteary/milvus-openblas     0.3.20-amd-zen-ubuntu-22.04     143MB
soulteary/milvus-openblas     0.3.20-amd-zen-ubuntu-18.04     266MB
soulteary/milvus-openblas     0.3.20-amd-zen-ubuntu-20.04     318MB

基于 ARMv64 架构 CPU 的容器预构建

依旧是先准备 Dockerfile,和 AMD Zen 架构 CPU 遇到的问题类似,0.3.9 版本和一些 Ubuntu 发行版中,我们会在构建过程和结果中遇到一些报错和警告,虽然能够得到构建产物,但是和上面的原因一样,我们需要的是稳定、可靠的产物,所以,可以排除掉 0.3.9 版本的 OpenBLAS 的所有“镜像组合”。

对于 ARMv64 设备的镜像构建,我们可以使用两种方式。一是使用在以往文章中提到过的 buildx 来进行构建,下面的命令将在构建完毕之后,自动将镜像推送到 DockerHub 中:

docker buildx build -t group/name:version -f ./Dockerfile.armv8 --push --platform=linux/arm64 .

此外,我们还可以选择和上文中构建 Intel / AMD x86 CPU 一样,将构建的事情,放在具有这个硬件架构的设备上完成构建。相比较前者,这样的构建效率有质的不同(快的多的多),恰好这几种 CPU 的设备我手头都有,所以我就选择第二种方案啦。

不过,和上文中 x86 CPU 的构建配置还是有一些不同,我们需要指定构建参数为 TARGET=ARMV8,完整 Dockerfile,我上传到了 GitHub soulteary/docker-openblas/blob/main/armv8/Dockerfile,有需要可以自取。

在准备好 Dockerfile 之后,我们使用下面的命令进行 ARMv8 环境下的镜像构建:

docker build \
    --build-arg=LTS=22.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-armv8-ubuntu-22.04 .

docker build \
    --build-arg=LTS=20.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-armv8-ubuntu-20.04 .

docker build \
    --build-arg=LTS=18.04 \
    --build-arg=OPENBLAS_VERSION=0.3.20 \
    -t soulteary/milvus-openblas:0.3.20-armv8-ubuntu-18.04 .

相比较 x86 环境下的构建,ARMv8 环境的产物镜像会构建的飞快,并且产物会小巧不少:

soulteary/milvus-openblas   0.3.20-armv8-ubuntu-22.04     88.8MB
soulteary/milvus-openblas   0.3.20-armv8-ubuntu-20.04     169MB
soulteary/milvus-openblas   0.3.20-armv8-ubuntu-18.04     147MB

好啦,到这里如何在容器中构建 OpenBLAS 就分享完毕啦。

镜像的使用本为两部分,第一部分是获取镜像,你既可以使用我制作好的镜像,也可以进行自行构建。第二部分,则是在容器中使用多阶段构建,完成“软件安装”(跨镜像文件 COPY)。

如果你不想花费时间重新构建这几类不同硬件环境的镜像,可以使用我提过的镜像文件,经过 DockerHub 的压缩,这些镜像的尺寸得以进一步变得苗条,最小的镜像不过 20MB,最大的也才 47 MB:https://hub.docker.com/repository/docker/soulteary/milvus-openblas/tags

如果你希望自行构建,根据自己的需求改变构建参数,使用起来心里更踏实,也可以参考我的项目 https://github.com/soulteary/docker-openblas,并结合上文进行构建参数调整,来进行本地构建。

聊完了镜像的获取,我们来看看镜像在容器中如何使用吧。

关于预构建镜像的使用,其实非常简单,就如同我们执行 make install 一样,将文件拷贝到正确的目录中,并按照“传统”用软链做好副本的重命名即可,比如这样:

FROM soulteary/milvus-openblas:0.3.20-intel-x86-ubuntu-20.04 AS OpenBLAS

FROM ubuntu:20.04
LABEL [email protected]

COPY --from=OpenBLAS /usr/lib/libopenblas-r0.3.20.so /usr/lib/
RUN ln -s /usr/lib/libopenblas-r0.3.20.so /usr/lib/libopenblas.so.0 && \
    ln -s /usr/lib/libopenblas.so.0 /usr/lib/libopenblas.so

在使用过程中,我们只需要保证基础镜像(Ubuntu)版本是对号的,CPU 运行环境是对号的,那么就都能够通过上面这种“跨镜像”复制的方式,来减少 Milvus 或者其他软件构建过程中的时间浪费。

既然本文主要聊 OpenBLAS 的容器构建,Milvus 相关的内容,我们还是在之后的文章中,再进行展开吧。

在接下来的向量数据库相关的内容中,我们将继续聊聊之前立过的各种 Flag。以及不断分享如何把一个原本需要非常长时间构建的软件,不断优化到一个喝杯水就能搞定的程度。

毕竟使用这类带有“魔法”的软件完成一些好玩的事情之外,让这些“魔法”能够生效的更快、能量更强,也是一件十分有趣的事情。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK