本篇文章接上一篇,继续聊聊向量数据库领域,知名的开源技术项目:Milvus,在不同 CPU 架构的 macOS 环境下的编译安装。

如果你在寻找如何从源码编译安装,如何在 x86 和 ARM 两种架构的 macOS 上折腾 Milvus,那么这篇文章应该是最为详细的教程向参考内容啦。

写在前面

关于 Milvus 这个项目的背景知识,以及项目目前的一些现存问题,我在上一篇文章《从零开始构建向量数据库:Milvus 的源码编译安装(一)》中有提到过。所以,就不再做展开了,如果你希望做一些了解,可以自行翻阅之前的内容。

目前,软件可以在 Linux、macOS、Windows 上完成编译,考虑到不少工程师都是使用(或被迫选择)macOS 作为图形界面系统,所以本篇文章仅介绍如何在 macOS 环境下从源码构建 Milvus。

接下来,我们还是先从编译入手,从零到一的开始了解这个项目。

前置准备

在正式折腾编译构建之前,还是先分别从操作系统、开发环境、源码获取等几个方面来展开聊聊要做哪些准备工作。

操作系统

本文选择的 macOS 版本是最新版 12.4,一般情况下,我们只要打开系统更新,然后让设备自己更新到最新版本,就完成了 80% 的系统环境相关的准备工作啦。

如果你希望在一个全新的环境中进行安装,避免所有的潜在干扰,可以参考苹果官方文档对系统进行重装(绝大多数情况下,不需要这样操作):

对于完成剩余 20% 的工作,我们需要借助 Homebrew。由于 Homebrew 使用了海外的 CDN 服务,所以我们在国内进行软件下载,可能会受到一些网络因素的影响,导致软件下载过程中断或失败。

如果你希望能够避免这类网络的影响,可以尝试使用国内的“清华源镜像”来尝试安装这些软件。

如果你能够直接完成依赖和工具的下载,那么就不需要配置“镜像”,因为镜像的同步机制,存在数据同步不完整的情况,在这个情况下,我们进行依赖安装,可能会出现部分依赖失败。解决这个问题也很简单,只需要在安装进行不下去的时候,“unset” 掉“镜像”,切换为官方源即可。

Golang 和 C++ 开发环境

在上一篇文章中,我们就提到过,Milvus 中绝大多数代码是由 Golang 编写的,但是核心的部分又包含了一大堆 C++ 的内容(internal/core/src),所以我们需要同时确保两种语言的运行环境。

为了让 Golang 环境的折腾能省点劲,我曾写过两篇文章介绍简单可行的方案:

经过实际测试,只需要按照文章中的步骤,一步一步 “CTRL+C / CTRL+V”,这个方案一定能够让你在十分钟内,快速的用上新鲜热乎的 Golang。

关于 C++ 环境的准备,我们会在下文中详细的展开,这里就先不赘述啦。

源码获取

关于如何快速获取源代码,以及避免因为代码仓库过大,容易受到一些国内经常会遇到的网络质量影响而“拉(pull)”不下来数据的问题,我在上一篇文章中的相同的小节“源码获取”小节中有提到过,你可以直接参考文中提到的解决方案。为了节约篇幅,这里就不再展开啦。

准备基础依赖库:OpenBLAS

OpenBLAS 作为泛 AI 领域中,经常能够看到的科学计算开源库,自然也是要安装的。(据说未来版本中,会变成可选项)

在之前的文章《走进向量计算:从源码编译 OpenBLAS》中,我详细的介绍了如何进行 OpenBLAS 的编译安装,你可以参考文中的内容,来获取 OpenBLAS 的编译产物,并自行完成编译产物的有效性验证。

如果你是使用 brew 安装的:

openblas is keg-only, which means it was not symlinked into /usr/local,
because macOS provides BLAS in Accelerate.framework.

For compilers to find openblas you may need to set:
  export LDFLAGS="-L/usr/local/opt/openblas/lib"
  export CPPFLAGS="-I/usr/local/opt/openblas/include"

For pkg-config to find openblas you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"

准备构建依赖:cmake

想要构建包含 C++ 组件的项目,CMake 必不可少。

Milvus 官方项目要求 CMake 的版本是大于等于 3.18,在上一篇文章中,我们有提到了“尽量和官方构建使用相同版本”的建议,不过因为下面的现实问题,完全保持一致的版本比较难,也没有意义:

  • Mac M1 芯片设备退出时间点较晚,Milvus 立项时间较早,这导致很多项目使用的软件版本不支持 M1 设备(ARMv64 设备)
  • HomeBrew 和 macOS 新版本所预构建的工具的版本号都比较新,安装旧版本非常折腾,且不保证能够通过测试
  • 我们很少会使用 macOS 作为生产环境,更多的时候,macOS 会被用来做开发环境,所以我们也并没有必要严格要求这个环境下的组件版本和 Linux 环境下完全一致

但是考虑到过程的严谨,期望构建产物少一些预料之外的问题。在这里,我们还是分别聊聊两种芯片架构的 macOS 设备。

Intel x86 芯片的 macOS 设备

虽然在 CMake 官方网站,我们没有找到预构建程序,但是在 CMake 的 GitHub 仓库发布页面中,我们能够找到和 Milvus 官方 CI 使用版本一致的软件包:3.18.6 版本的 CMake

将文件下载之后,放到可执行文件目录(/usr/local/bin/),或者类似我们上文中安装 gvm 的时候,把包含 cmake 的路径添加到环境变量中即可完成预构建的 CMake 的安装。

安装完毕之后,我们同样可以使用 cmake --version 命令,来简单验证安装是否正确:

cmake --version
cmake version 3.18.6

CMake suite maintained and supported by Kitware (kitware.com/cmake).

当然,这里也可以使用 brew install cmake 来直接安装更新版本的 CMake 程序,简化安装过程。

M1 ARMv64 芯片的 macOS

因为直到 3.19.3 以上版本的 cmake 才提供了 ARMv64 支持,所以我们和 macOS 环境中构建 OpenBLAS 小节中遇到了一样的问题:无法在 macOS 环境下,使用和 Milvus 官方仓库中一致版本号的 cmake

我们同样有两个选择,一个是参考前文中处理 Intel 芯片 macOS 环境中 CMake 的处理方式,只选择官方开始支持 ARMv64 的软件版本,完成预构建程序的安装。

另外一个方案,则是直接使用 brew install cmake 安装 cmake 最新的 “stable” 版本,因为两种方式本质都使用了较高的新版本 cmake,并没有本质差别,所以我更推荐使用后者,安装 “stable” 版本的软件:

brew install cmake
==> Downloading https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/cmake-3.23.2.arm64_monterey.bottle.tar.gz
######################################################################## 100.0%
==> Pouring cmake-3.23.2.arm64_monterey.bottle.tar.gz
==> Caveats
To install the CMake documentation, run:
  brew install cmake-docs

Emacs Lisp files have been installed to:
  /opt/homebrew/share/emacs/site-lisp/cmake
==> Summary
🍺  /opt/homebrew/Cellar/cmake/3.23.2: 3,043 files, 42.2MB
==> Running `brew cleanup cmake`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

程序安装完毕,我们使用 cmake --version 来进行一个简单的验证,输出程序的版本号:

cmake version 3.23.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).

准备构建依赖:clang-format、clang-tidy

在 Milvus 代码仓库里的构建工具镜像代码中,我们能够看到两个构建依赖叫做:clang-format-10clang-tidy-10,这两个工具被用于构建过程中,对代码进行格式化和检查

我们在 HomeBrew 官方软件包仓库中,可以找到一个名为 clang-format软件包,它包含了我们所需要的 clang-format 相关组件。但是和上面安装 OpenBLAScmake 一样,软件仓库中并没有提供我们所需要的版本(Milvus 官方仓库的版本)。

所以,我们需要参考上一篇文章中 Ubuntu 22.04 中“自己动手,丰衣足食”的精神,开启手动编译。

依旧是先使用 git clone 下载所需要的代码,和 Milvus 官方仓库保持一致:

 git clone -b ubuntu/focal https://git.launchpad.net/ubuntu/+source/llvm-toolchain-10
Cloning into 'llvm-toolchain-10'...
remote: Enumerating objects: 106493, done.
remote: Counting objects:  21% (22364/106493)
remote: Counting objects: 100% (106493/106493), done.
remote: Compressing objects: 100% (78521/78521), done.
remote: Total 106493 (delta 25377), reused 105666 (delta 24555)B/s
Receiving objects: 100% (106493/106493), 164.17 MiB | 268.00 KiB/s, done.
Resolving deltas: 100% (25377/25377), done.
Updating files: 100% (90282/90282), done.

在代码下载完毕之后,我们还是进入目录,并准备一个用于构建的新目录:

cd llvm-toolchain-10 && \
mkdir build && cd build

在 Ubuntu 环境中,我们想要使用 cmake 构建 llvm-toolchain 需要 ninja-build,在 macOS 中,我们所需要的这个包被叫做 “ninja”,我们可以使用 brew install ninja 来完成它的安装:

brew install ninja
==> Downloading https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/ninja-1.11.0.arm64_monterey.bottle.tar.gz
######################################################################## 100.0%
==> Pouring ninja-1.11.0.arm64_monterey.bottle.tar.gz
==> Caveats
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions

Emacs Lisp files have been installed to:
  /opt/homebrew/share/emacs/site-lisp/ninja
==> Summary
🍺  /opt/homebrew/Cellar/ninja/1.11.0: 10 files, 435.3KB
==> Running `brew cleanup ninja`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

在完成 ninja 安装之后,我们就可以使用相同的参数来生成构建所需要的配置文件了:

cmake -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=$(which python3) -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON ../llvm

在构建配置文件生成完毕之后,还是执行 cmake --build . 命令,就可以开始 clang 工具的编译工作了:

cmake --build .

这里默认编译将会使用一个核心,如果我们的设备有更多的核心数,建议添加参数来提升构建速度(比如用8个核心一起并行构建):

cmake --build . -j8

在漫长的编译构建结束后,我们将看到类似下面的日志:

...
[100%] Building CXX object utils/benchmark/src/CMakeFiles/benchmark.dir/timers.cc.o
[100%] Linking CXX static library ../../../lib/libbenchmark.a
[100%] Built target benchmark
[100%] Building CXX object utils/benchmark/src/CMakeFiles/benchmark_main.dir/benchmark_main.cc.o
[100%] Linking CXX static library ../../../lib/libbenchmark_main.a
[100%] Built target benchmark_main
[100%] Copying llvm-locstats into /Users/soulteary/app/llvm-toolchain-10/build/./bin
[100%] Built target llvm-locstats

和上一篇文章中对 Linux 编译产物的验证方式一样,我们分别使用 ./bin/clang-format --version./bin/clang-tidy --version 就能够完成对两个程序的分别验证了:

# ./bin/clang-format --version
clang-format version 10.0.0 (https://git.launchpad.net/ubuntu/+source/llvm-toolchain-10 6f319ce10f040c979f7bb074a7b7465b8c7bf9f7)

# ./bin/clang-tidy --version
LLVM (http://llvm.org/):
  LLVM version 10.0.0
  Optimized build.
  Default target: arm-apple-darwin21.5.0
  Host CPU: cyclone

在完成对程序可运行的验证之后,我们将两个程序分别复制到 homebrew 或者系统的可执行文件的目录中即可:

cp bin/clang-format /usr/local/bin/ && \
cp bin/clang-tidy /usr/local/bin/

其余 C++ 开发环境的依赖

上文中,我们其实已经进行了一些 C++ 环境的依赖和工具的准备,不过为了让 C++ 部分程序能够顺利编译和使用,我们还需要继续补完一些内容。

在 Milvus 官方仓库的依赖安装脚本中,我们能找到一个函数 install_mac_deps,里面定义了基本所有的官方认为需要的工具:

function install_mac_deps() {
  sudo xcode-select --install  > /dev/null 2>&1
  brew install boost libomp ninja tbb cmake llvm ccache
  brew uninstall grep
  brew install grep
  export PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"
  brew update && brew upgrade && brew cleanup

  if [[ $(arch) == 'arm64' ]]; then
    brew install openssl
    brew install librdkafka
    brew install pkg-config
    sudo mkdir /usr/local/include
    sudo mkdir /usr/local/opt
    sudo ln -s "$(brew --prefix llvm)" "/usr/local/opt/llvm"
    sudo ln -s "$(brew --prefix libomp)/include/omp.h" "/usr/local/include/omp.h"
    sudo ln -s "$(brew --prefix libomp)" "/usr/local/opt/libomp"
    sudo ln -s "$(brew --prefix boost)/include/boost" "/usr/local/include/boost"
    sudo ln -s "$(brew --prefix tbb)/include/tbb" "/usr/local/include/tbb"
    sudo ln -s "$(brew --prefix tbb)/include/oneapi" "/usr/local/include/oneapi"
  fi
}

将上面的内容全部复制,然后粘贴在终端中执行,接着输入 install_mac_deps ,敲击回车,会看到终端提醒我们需要输入密码,才能继续执行程序。

# install_mac_deps
Password:
==> Downloading https://ghcr.io/v2/homebrew/core/icu4c/manifests/70.1
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/icu4c/blobs/sha256:43cf787a35559b90597db8e1aaba95dbeedb84b1ee3d2e942be8938ae618724c
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:43cf787a35559b90597db8e1aaba95dbeedb84b1ee3d2e942be8938ae618724c?se=2022-07-06T14%3A35%3A00Z&sig=v38FExWrsokAKB1VwnoRNd%2FjGPYEhs
######################################################################## 100.0%
...

在输入密码之后,耐心等待程序运行完毕,macOS 所需要的 C++ 编译环境就完全就绪了。

Golang 开发环境调整

在文章的“前置准备”小节中,我们提到过 Golang 环境的快速安装配置。但是在 Ubuntu 和 macOS 环境中,两者其实还是有一些不同之处:在 macOS 12.4 版本中,如果我们使用和 Milvus 官方一致的 Golang 1.16.9 将会出现构建失败的问题,所以我们必须升版本到 1.17 及以上。

使用 gvm 命令,来将 Golang 切换到一个合适的版本:

gvm install go1.17 -B && \
gvm use go1.17 --default

从源码编译 Milvus 主程序

看到这里,距离完成 Milvus 项目的构建,我们已经完成了 90% 的工作。接下来,我们就来解决剩下的 10% 的工作。

这部分的在 macOS 环境下的 Milvus 编译,和 Ubuntu 环境大同小异。唯一的差别是,我们需要在执行编译之前,先声明一个环境变量,避免程序在编译过程中因为找不到 pkg-config 而无法继续进行:

export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@3/lib/pkgconfig"

在完成变量声明之后,我们同样先切换到早早准备好的 Milvus 代码目录中,接着执行 make milvus

cd milvus
make milvus

然后在一阵花花绿绿的终端日志滚动结束之后,我们执行 ./bin/milvus 命令,就能够看到符合预期的 Milvus 执行程序输出的帮助信息了:

# ./bin/milvus
Usage:

milvus run [server type] [flags]
	Start a Milvus Server.
	Tips: Only the server type is 'mixture', flags about starting server can be used.
[flags]
	-rootcoord 'true'
		Start the rootcoord server.
	-querycoord 'true'
		Start the querycoord server.
	-indexcoord 'true'
		Start the indexcoord server.
	-datacoord 'true'
		Start the datacoord server.
	-alias ''
		Set alias


milvus stop [server type] [flags]
	Stop a Milvus Server.
[flags]
	-alias ''
		Set alias


milvus mck run [flags]
	Milvus data consistency check.
	Tips: The flags are optional.
[flags]
	-etcdIp ''
		Ip to connect the ectd server.
	-etcdRootPath ''
		The root path of operating the etcd data.
	-minioAddress ''
		Address to connect the minio server.
	-minioUsername ''
		The username to login the minio server.
	-minioPassword ''
		The password to login the minio server.
	-minioUseSSL 'false'
		Whether to use the ssl to connect the minio server.
	-minioBucketName ''
		The bucket to operate the data in it

milvus mck cleanTrash [flags]
	Clean the back inconsistent data
	Tips: The flags is the same as its of the 'milvus mck [flags]'


[server type]
	standalone
	rootcoord
	proxy
	querycoord
	indexcoord
	embedded
	querynode
	indexnode
	datacoord
	datanode
	mixture

最后

聊到这里 ,Milvus 在两种不同 CPU 架构的 macOS 设备上的编译安装就讲完啦。虽然已经在之前的文章中,铺垫和介绍了一部分内容,但是目前文章字数依然破万字。所以,也就不继续展开更多细节啦。

在下一篇 Milvus 相关的内容中,我们会继续聊聊容器相关的构建和使用,如何构建一个“知根知底”,更加“透明可控”的容器镜像。以及如何优化这个容器镜像的性能极限,让它在开发和生产环境中的性价比更高。

如果你想了解如何构建一个更加可靠、可信赖的容器镜像,我们下一篇文章见。

–EOF