1. 概述
mcFaiss是Faiss在MXMACA框架下的实现,MXMACA® 是曦云® 系列GPU的计算框架。mcFaiss支持Faiss的所有功能,包括:
提供多种向量搜索算法,如IVF、LSH、PQ、HNSW等
提供多种语言的接口,如Python、C++等
支持多种向量数据的应用,如图像检索、文本检索、语音检索等
曦云系列GPU提供了多种平台上的二进制发布包,能够帮助用户实现快速部署;同时也提供了源代码补丁,方便用户在MXMACA上编译Faiss。
本文档主要用于指导用户在使用曦云系列GPU时,使用mcFaiss进行向量搜索,并且完成相关的应用部署。
2. 快速入门
2.1. 软件包
2.1.1. 软件包信息
mcFaiss提供了 mxc500-mcFaiss-<VERSION>-linux-x86_64.tar.xz ,可以使用命令 tar -xJf 进行解压,解压后的二进制软件包信息说明参见下表。
软件包类型 |
文件名示例 |
说明 |
|---|---|---|
deb发布包 |
mcfaiss-x.y.z.n+bu.v.w.m-x86_64.deb |
mcFaiss发布包,适用于Ubuntu等使用deb包管理的系统。如果应用程序以动态库的方式依赖mcFaiss,则可以部署该二进制包作为依赖 |
deb开发包 |
mcfaiss-dev-x.y.z.n+bu.v.w.m-x86_64.deb |
mcFaiss开发包,适用于Ubuntu等使用deb包管理的系统,用于开发应用程序使用,头文件和静态库由此包提供 |
rpm发布包 |
mcfaiss-x.y.z.n+bu.v.w.m-x86_64.rpm |
mcFaiss发布包,适用于CentOS等使用rpm包管理的系统 |
rpm开发包 |
mcfaiss-devel-x.y.z.n+bu.v.w.m-x86_64.rpm |
mcFaiss开发包,适用于CentOS等使用rpm包管理的系统 |
Python wheel包 |
faiss-x.y.z.n+bu.v.w.m-py3-none-manylinux_2_27_x86_64.whl |
mcFaiss的Python二进制扩展包,适用于Python3.8及以上 |
备注
x.y.z.n表示对应包的软件发布版本,bu.v.w.m表示基于版本号为u.v.w.m的MXMACA进行编译的构建号。
x86_64表示CPU架构。
2.1.2. 软件包内容
安装mcFaiss的deb或rpm包会在/opt/mxmap路径下安装相关的二进制开发库和头文件。其中mcfaiss发布包会安装.so文件;mcfaiss-dev开发包会安装头文件、静态库等。
/opt/mxmap为起始路径,可以得到如下的文件内容:
├─include/
│ └─faiss/
│ ├─AutoTune.h
│ ├─Clustring.h
│ ├─......
│ └─index_io.h
├─lib/
│ ├─libfaiss.a
│ ├─libfaiss_avx2.a
│ ├─libfaiss_avx2.so
│ ├─libfaiss_gpu.a
│ └─libfaiss.so
└─share/
└─faiss/
├─......
└─faiss-va.b.c.patch
include/faiss/目录包含了Faiss所有的头文件
lib/libfaiss.a、lib/libfaiss_avx2.a文件是静态链接库
lib/libfaiss.so、lib/libfaiss_avx2.so文件是动态链接库
share/faiss/faiss-va.b.c.patch文件是基于a.b.c版本的Faiss源码补丁
2.2. 安装部署
2.2.1. 操作系统及依赖
mcFaiss二进制发布包(deb或rpm)支持的操作系统及相关依赖,参见下表。
操作系统 |
相关依赖 |
说明 |
|---|---|---|
Ubuntu 18.04 |
依赖版本的MXMACA Runtime |
|
libomp5 |
||
Python3.8及以上 |
||
Ubuntu 20.04 |
同Ubuntu 18.04 |
|
Ubuntu 22.04 |
同Ubuntu 18.04 |
|
CentOS 8 |
依赖版本的MXMACA Runtime |
|
libomp |
||
Python3.8及以上 |
2.2.2. mcFaiss安装
Ubuntu系统
假设mcFaiss的deb包已经保存在当前目录下,推荐使用 apt 安装:
$ sudo apt -y install ./mcfaiss-x.y.z.n+bu.v.w.m-x86_64.deb
或者也可以使用 dpkg 命令进行安装:
$ sudo dpkg -i ./mcfaiss-x.y.z.n+bu.v.w.m-x86_64.deb
安装成功后,可以在 /opt/mxmap 目录下找到相应的文件,详细信息参见 2.1.2 软件包内容。
Python二进制包
在支持的Linux系统中,可以使用pip进行安装。
$ python3.8 -m pip install ./faiss-x.y.z.n+bu.v.w.m-py3-none-manylinux_2_27_x86_64.whl
2.3. mcFaiss使用
mcFaiss提供C++和Python两种语言的接口,mcFaiss的C++/Python接口和Faiss提供的接口完全一致,用户可以参考Faiss的C++接口文档进行开发。 目前,mcFaiss支持的Faiss版本为1.8.0。
2.3.1. 代码示例(C++)
使用C++进行Flat索引完成向量添加、搜索,代码示例如下:
#include <iostream>
#include <algorithm>
#include <memory>
#include <random>
#include <vector>
#include <faiss/IndexFlat.h>
#include <faiss/MetaIndexes.h>
#include <faiss/gpu/GpuIndexFlat.h>
#include <faiss/gpu/StandardGpuResources.h>
int main() {
int d = 4; // dimension
int nb = 100; // database size
int nq = 10; // nb of queries
int topk = 3; // nb of probes
std::mt19937 rng;
std::uniform_real_distribution<float> distribution(0.0, 1.0);
std::vector<float> xb(d * nb);
std::vector<float> xq(d * nq);
std::vector<float> dis(d * nq * topk);
std::vector<int64_t> rids(d * nq * topk);
for (int i = 0; i < nb; i++) {
for (int j = 0; j < d; j++) {
xb[d * i + j] = distribution(rng);
}
}
std::copy(xb.begin(), xb.begin() + d * nq, xq.begin());
faiss::gpu::StandardGpuResources res;
faiss::gpu::GpuIndexFlatConfig config;
config.storeTransposed = false;
faiss::gpu::GpuIndexFlatL2 index(&res, d, config);
index.add(nb, xb.data());
index.search(nq, xq.data(), topk, dis.data(), rids.data());
for (int i = 0; i < nq; i++) {
std::cout << "query " << i << ": ";
for (int j = 0; j < topk; j++) {
std::cout << rids[i * topk + j] << " ";
}
std::cout << std::endl;
}
}
如果需要在曦云系列GPU上编译并运行上述示例,需要使用mxcc进行编译。
$ mxcc example.cpp -o example -I${MACA_PATH}/include \
-I${MACA_PATH}/tools/cu-bridge/include \
-I${MACA_PATH}/include \
-I${MACA_PATH}/include/mcblas \
-I${MACA_PATH}/include/common \
-I${MACA_PATH}/include/mcr \
-I/opt/mxmap/include \
-L/opt/mxmap/lib \
-lfaiss -lruntime_cu
编译成功后,运行example程序,可以得到如下的输出:
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/mxmap/lib ./example
query 0: 0 11 99
query 1: 1 50 78
query 2: 2 92 86
query 3: 3 93 63
query 4: 4 83 24
query 5: 5 60 17
query 6: 6 37 68
query 7: 7 36 14
query 8: 8 40 64
query 9: 9 83 24
注意事项
如果您的工程需要通过CMake的find_package使用mcFaiss,需要注意以下两点:
由于mcfaiss-dev安装包将文件安装到/opt/mxmap目录下,所以需要设置
CMAKE_PREFIX_PATH,如下:set(CMAKE_PREFIX_PATH /opt/mxmap) find_package(faiss CONFIG REQUIRED)
使用静态链接mcFaiss时,需要从源码编译安装0.3.x版本的OpenBLAS,编译命令参考如下:
curl -fL https://gitee.com/mirrors/OpenBLAS/repository/archive/v0.3.27.tar.gz | tar xz cd OpenBLAS* cmake -S . -B build -D CMAKE_BUILD_TYPE=Release -D NO_AVX512=1 -D NOFORTRAN=1 -D USE_OPENMP=1 -D USE_THREAD=0 cmake --build build -j sudo make -C build/ install
2.3.2. 代码示例(Python)
使用Python进行Flat索引完成向量添加、搜索,代码示例如下:
import faiss
import numpy as np
d = 4 # dimension
nb = 100 # database size
nq = 10 # nb of queries
topk = 3 # nb of probes
xb = np.random.random((nb, d)).astype('float32')
xq = xb[:nq]
index = faiss.IndexFlatL2(d)
index.add(xb)
dis, rids = index.search(xq, topk)
for i in range(nq):
print('query {}: {}'.format(i, rids[i]))
安装mcFaiss Python扩展包后,使用Python来运行上述脚本,可以得到如下的输出:
$ python3.8 example.py
query 0: [ 0 74 24]
query 1: [ 1 20 58]
query 2: [ 2 90 33]
query 3: [ 3 81 40]
query 4: [ 4 47 28]
query 5: [ 5 66 85]
query 6: [ 6 45 67]
query 7: [ 7 14 49]
query 8: [ 8 62 31]
query 9: [ 9 17 69]
2.4. 常见问题
2.4.1. OpenBLAS引起coredump
如果在CPU核数较多的环境中使用Faiss,可能会出现如下错误:
OpenBLAS warning: precompiled NUM_THREADS exceeded, adding auxiliary array for thread metadata.
OpenBLAS: Program is Terminated. Because you tried to allocate too many memory regions. This library was built to support a maximum of 50 threads - either rebuild OpenBLAS with a larger NUM_THREADS value or set the environment variable OPENBLAS_NUM_THREADS to a sufficiently small number. This error typically occurs when the software that relies on OpenBLAS calls BLAS functions from many threads in parallel, or when your computer has more cpu cores than what OpenBLAS was configured to handle.
如提示信息所示,运行时所启动的线程数量超过了OpenBLAS的最大值。可以通过设置环境变量限制使用的线程数量,比如,使用以下命令将线程数量限制到50:
OMP_NUM_THREADS=50 OPENBLAS_NUM_THREADS=50 ./TestGpuIndexIVFFlat
2.4.2. 环境变量设置
如果应用程序使用的GPU设备的数量少于实际数量,为了获得最佳性能,建议通过 MACA_VISIBLE_DEVICES 环境变量指定实际使用的GPU ID。
3. mcFaiss基本概念
3.1. 向量数据
人工智能算法可以将物理世界的人/物/场景所产生的各种非结构化数据(如语音、图片、视频、语言文字、行为等)进行抽象,变成多维度的向量。 这些向量如同数学空间中的坐标,标识了各个实体和实体关系。非结构化检索则是对这些生成的向量进行搜索,从而找到对应实体或者近似实体的过程。
图 3.1 向量索引
3.2. 向量索引
3.2.1. 支持GPU加速的索引
mcFaiss继承了Faiss所有向量索引在CPU上的实现,提供了多种向量索引算法,包括:IVF、LSH、PQ、HNSW等;提供了多种向量索引的实现,包括:IndexFlat、IndexIVFFlat、IndexLSH、IndexPQ等。 同时,mcFaiss也提供了Faiss向量GPU索引在MXMACA上的实现,包括:GpuIndexFlat、GpuIndexIVFFlat、GpuIndexPQ、GpuIndexSQ等。
3.2.2. 选择索引的建议
不同的索引适用于不同的场景,通常来说Flat的索引适用于数据量相对较小的场景,并且可以提供100%的召回。
IVFFlat的索引适用于数据量较大的场景,并且通过调整 nlist 和 nprobe 参数的设定在召回和搜索效率之间寻找平衡。在更大数据量的规模情况下,则可以考虑使用PQ/SQ量化来进一步提高搜索效率。
3.3. 向量间的距离
3.3.1. 浮点向量的距离
3.3.1.1. 欧氏距离
欧氏距离是最常见的距离度量方式,在向量索引中为了方便计算,通常使用以下计算公式:
实际数学意义上的欧氏距离还需要再计算一个平方根,但是在实际的向量搜索中,由于平方根是一个单调递增的函数,因此在搜索时可以省略平方根的计算,直接比较平方距离即可。
3.3.1.2. 内积距离
内积距离通常表达的是两个向量之间的相似度,内积距离的计算公式如下:
3.3.1.3. 余弦距离
余弦距离的计算公式如下:
而在Faiss等向量索引的计算中,并没有直接支持余弦距离,通常通过将A和B向量进行归一化,使得上述公式的分母为1,那么此时余弦距离等价于内积距离。
3.3.2. 二值向量的距离
mcFaiss也支持二值向量的距离计算和相关索引。二值向量的距离计算方式有:Hamming距离、Jaccard距离等。mcFaiss提供了多种二值向量的索引,包括:IndexBinaryFlat、IndexBinaryIVF等。
4. mcFaiss编程指南
4.1. mcFaiss索引接口
使用mcFaiss时,通常遵循的接口调用顺序为:创建索引 -> 训练索引 -> 添加向量数据 -> 搜索向量。
4.2. 索引的基本使用
本节介绍如何在曦云系列GPU上使用mcFaiss GPU索引。
4.2.1. 创建索引
创建一个索引类。
代码示例(C++)
使用C++ API时,首先需要创建一个Index类的实例:
int d = 4; // dimension
int nlist = 1024; // number of clusters
faiss::gpu::StandardGpuResources res;
faiss::gpu::GpuIndexIVFFlatConfig config;
faiss::gpu::GpuIndexIVFFlat index(&res, d, nlist, faiss::METRIC_L2, config);
示例中,创建了一个GPU索引类,为使用欧式距离(L2)的IVFFlat索引类,nlist为1024。关于nlist的具体含义,参见 5.2 GpuIndexIVFFlat。
代码示例(Python)
d = 4 # dimension
nlist = 1024 # number of clusters
config = faiss.GpuIndexIVFFlatConfig()
res = faiss.StandardGpuResources()
index = faiss.GpuIndexIVFFlat(res, d, nlist, faiss.METRIC_L2, config)
4.2.2. 训练索引
对于某些索引,必须对其进行训练。比如,对于IVF索引,进行训练的目的是为了生成索引的聚类中心,从而提高索引的搜索效率。
代码示例(C++)
int nb = 10000; // 训练数据的数量
std::vector<float> xb(d * nb);
// 填充训练的向量数据到xb
index.train(nb, xb.data());
代码示例(Python)
nb = 10000 # 训练数据的数量
# 填充训练的向量数据到xb,这里使用随机数做示例
xb = np.random.random((nb, d)).astype('float32')
index.train(xb)
在实际使用过程中,并不要求训练的数据和4.2.3添加向量数据中添加的数据是一致的,只要保证这两者的维度是一致的即可。 但是为了提高索引的搜索效率,建议训练的数据和添加的数据一致,或者训练的数据是添加数据的一个子集。
对于Flat索引,不需要进行训练,因为Flat索引不需要聚类中心。
4.2.3. 添加向量数据
将向量数据添加到索引中。
代码示例(C++)
int nb = 10000; // 向量数据的数量
std::vector<float> xb(d * nb);
// 填充向量数据到xb
index.add(nb, xb.data());
代码示例(Python)
nb = 10000 # 向量数据的数量
# 填充向量数据到xb,这里使用随机数做示例
xb = np.random.random((nb, d)).astype('float32')
index.add(xb)
添加操作可以反复进行,但是每次添加都有可能触发设备内存的重新分配。因此,为了提高数据添加的效率,建议批量添加数据。
4.2.4. 搜索向量
向量搜索是索引的核心功能,可以通过以下方式进行搜索。
代码示例(C++)
int nq = 10; // 查询向量的数量
int topk = 3; // 搜索的topk
std::vector<float> xq(d * nq);
// 填充查询向量数据到xq
std::vector<float> dis(d * nq * topk);
std::vector<int64_t> rids(d * nq * topk);
index.search(nq, xq.data(), topk, dis.data(), rids.data());
搜索结束后,dis和rids中保存了搜索结果,dis中保存了搜索结果的距离,rids中保存了搜索结果的ID。
代码示例(Python)
nq = 10 # 查询向量的数量
topk = 3 # 搜索的topk
# 填充查询向量数据到xq,这里使用随机数做示例
xq = np.random.random((nq, d)).astype('float32')
dis, rids = index.search(xq, topk)
通常情况下,GPU上的搜索效率会比CPU上的更高,但是在搜索之前需要将查询向量数据拷贝到GPU上,因此在实际使用的过程中,为了提高搜索的效率,建议进行批量搜索。
5. mcFaiss GPU索引的支持
本章描述GPU相关索引在mcFaiss上的一些参数和设定。默认使用Python接口作为示例介绍。
5.1. GpuIndexFlat
GpuIndexFlat 是Flat索引在GPU上的实现。Flat索引是最简单、最常用的索引,其搜索效率较低,但是可以提供100%的召回;其构建速度较快,适用于数据量较小的场景。
创建 GpuIndexFlat 时,可以通过 GpuIndexFlatConfig 来设定一些参数,示例如下:
config = faiss.GpuIndexFlatConfig()
config.device = 0
config.useFloat16 = False
config.storeTransposed = False
参数说明参见下表。
设置项 |
含义 |
说明 |
|---|---|---|
device |
使用的显卡设备索引 |
默认为0。即,使用第一个GPU设备 |
useFloat16 |
是否使用FP16。如果使用,则在内部计算时,会使用FP16进行一些相关计算,这在精度要求不高的场景下,可以提高索引训练和搜索的效率。 |
默认不使用 |
storeTransposed |
是否保存转置后的矩阵数据。对于向量数据来说,输入的向量数据是行排序的;但是对于GPU的运算来说,列排序的数据往往可以减少不必要的计算和转置,从而提高搜索的性能。但是保存一份额外的转置数据,需要额外的内存开销。 |
默认不保存 |
5.2. GpuIndexIVFFlat
GpuIndexIVFFlat 是IVFFlat索引在GPU上的实现。IVFFlat索引是一种基于聚类的索引,其搜索效率较高,但是通常无法提供100%的召回。
创建 GpuIndexIVFFlat 时,可以通过 GpuIndexIVFFlatConfig 来设定一些参数。参数和 GpuIndexFlatConfig 提供的基本一致,参见 5.1 GpuIndexFlat。
创建 GpuIndexIVFFlat 时,需要指定聚类的数量,示例如下:
nlist = 1024
index = faiss.GpuIndexIVFFlat(res, d, nlist, faiss.METRIC_L2, config)
在IVF类索引中, nlist 和 nprobe 是两个重要的参数:
nlist是聚类的数量,nlist的数量越大,索引的搜索效率越高,但是索引的构建时间也越长。nprobe是搜索时的探测数量,nprobe的数量越大,索引的搜索召回率越高,但是索引的搜索性能越低。
在实际使用的过程中,需要根据实际的场景来调整 nlist 和 nprobe 的数量,以达到搜索效率和搜索召回的平衡。
5.3. GpuIndexIVFPQ
GpuIndexIVFPQ 是IVFPQ索引在GPU上的实现。IVFPQ索引是一种基于聚类和量化的索引。
创建 GpuIndexIVFPQ 时,可以通过 GpuIndexIVFPQConfig 来设定一些参数。参数和 GpuIndexFlatConfig 提供的基本一致,参见 5.1 GpuIndexFlat。
另外还可以额外设置一些选项,参见下表。
设置项 |
含义 |
说明 |
|---|---|---|
useMMCodeDistance |
指定是否使用MMCode距离。MMCode距离是一种针对PQ量化的距离计算方式,可以提高搜索的效率。但是MMCode距离的计算方式会导致搜索结果的召回率下降,因此在实际使用的过程中,建议不要使用MMCode距离。 |
默认不使用 |
usePrecomputedTables |
指定是否使用预计算的量化表。预计算的量化表可以提高搜索的效率,但是会占用更多的内存。在实际使用的过程中,可以考虑使用预计算的量化表。 |
默认不使用 |
创建 GpuIndexIVFPQ,示例如下:
nlist = 1024
d = 256
m = 16
nbits = 8
index = faiss.GpuIndexIVFPQ(res, d, nlist, m, nbits, faiss.METRIC_L2, config)
除了指定聚类的数量 nlist, m 是PQ量化的子空间的维度, nbits 是PQ量化的子空间的bit数。
m 和 nbits 的数量越大,索引的搜索效率越高,但是索引的构建时间也越长。在实际使用的过程中,需要根据实际的场景来调整 m 和 nbits 的数量,以达到搜索效率和搜索召回的平衡。
备注
要求向量的维度d能被m整除。nbits通常在GPU取8。
5.4. GpuIndexIVFScalarQuantizer
GpuIndexIVFScalarQuantizer 是IVFSQ索引在GPU上的实现。IVFSQ索引是一种基于聚类和标量量化的索引。
创建 GpuIndexIVFScalarQuantizer 时,可以通过 GpuIndexIVFScalarQuantizerConfig 设定一些参数,参数和 GpuIndexFlatConfig 提供的基本一致,参见 5.1 GpuIndexFlat。
创建 GpuIndexIVFScalarQuantizer,示例如下:
nlist = 1024
sq = faiss.ScalarQuantizer.QT_8bit
encode_residual = True
index = faiss.GpuIndexIVFScalarQuantizer(res, d, nlist, sq, faiss.METRIC_L2, encode_residual, config)
除了指定聚类的数量 nlist, sq 是标量量化的类型, encode_residual 是指定是否对残差进行编码。
通常,建议对残差进行编码以提高搜索的效率(默认选项);同时对于GPU,推荐使用8bit的标量量化。
6. 附录
6.1. 术语/缩略语
术语/缩略语 |
全称 |
说明 |
|---|---|---|
HNSW |
Hierarchical Navigable Small World |
分层可导航小世界,是基于图的近似最近邻(ANN)算法 |
LSH |
Locality-Sensitive Hashing |
局部敏感哈希算法,广泛应用于近似最近邻(ANN)搜索 |
SQ |
Scalar Quantization |
标量量化,一种对标量数值进行降维的量化手段 |
IVF |
Inverted File |
倒排文件,在搜索引擎中常用的一种索引技术,在向量索引中也有十分广泛的应用 |
PQ |
Product Quantization |
乘积量化,在向量索引中往往用来进行向量降低维度、加速搜索的一种量化手段 |