编者按:本文具体介绍了 Milvus 2.0 零碎的外围计算引擎 Knowhere,包含代码概览、如何增加索引,及对 Faiss 所做的优化。
Knowhere 概述
如果把 Milvus 比喻为一辆跑车,Knowhere 就是这辆跑车的引擎。Knowhere 的定义领域分为广义和狭义两种。广义上的 Knowhere 是上层向量查问库(如 Faiss、HNSW、Annoy)和下层服务调度之间的操作接口。同时,异构计算也由 Knowhere 这一层来管制,用于治理索引的构建和查问操作在何种硬件上执行,如 CPU 或 GPU,将来还能够反对 DPU/TPU/……这也是 Knowhere 这一命名的源起 —— know where。狭义上的 Knowhere 还包含 Faiss 及其它所有第三方索引库。因而,能够将 Knowhere 了解为 Milvus 的外围运算引擎。
从上述定义能够得悉,Knowhere 只负责解决数据运算相干的工作,其余零碎层面的工作如数据分片、负载平衡、灾备等,都不在它的性能领域中。另外,从 Milvus 2.0.1 开始,狭义的 Knowhere 已从 Milvus 我的项目中剥离进去,成为了一个独自的我的项目。
作为一个 AI 数据库,Milvus 的运算能够分成标量运算和向量运算。Knowhere 只负责解决向量运算。
上图是 Knowhere 模块在 Milvus 我的项目中的架构图。从下往上顺次是零碎硬件、第三方向量查问库、Knowhere,再往上通过 CGO 和 index_node 和 query_node 交互。橙色所示局部是广义上的 Knowhere,蓝色框所示局部是狭义上的 Knowhere。本文介绍的是狭义上的 Knowhere。
Knowhere 代码概览
对 Knowhere 的代码构造有大抵理解,用户在后续看代码或是奉献代码时都能更加便当。
Milvus 数据模型
首先介绍 Milvus 的数据模型。
- Database,当初 Milvus 还不反对多租户,所以只有一个 database
- Collection,因为是分布式系统,所以一个 collection 能够散布在 4 个 node 上,那每个 node 只有这张表的 1/4,这 1/4 叫做 shard
- Partition,数据的逻辑分片,可减速查问
- Segment,是 Partition 中的数据块
在 Milvus 中查问的最小单位是 segment,对 collection 的查问操作最终会合成为对该 collection 或 partition 中所有 segment 的查问。最终,会将所有 segement 的查问后果进行归并,并失去最终后果。
如上图所示,为了反对流式数据插入,segment 分为 growing segment 和 sealed segment。growing segment 是能够持续增加 data 的动静 segment,然而它没有索引,只能用“暴搜”来查问;达到 size 或工夫阈值后,growing segment 会变为 sealed segment。每个 segment 蕴含多个 field,其中,Primary Key 和 Timestamp 是零碎默认自带的 field,其余 field 由用户建表时指定。
当初一个 collection 只反对一列 vector field;Knowhere 只解决 vector field;建索引和查问也都只是针对 segment 中的 vector field。
Index 索引建设
索引是独立于原始向量数据的一种数据结构。绝大部分的索引构建须要经验 4 个步骤,创立 (create) – 数据插入 (insert) – 训练 (train) – 构建 (build)。
对于有的 AI 利用,其训练数据集和查问数据集是离开的,先用训练数据集做训练,再基于该训练后果插入查问数据。如公开数据集 sift1M / sift1B,其中就分专门的训练数据和测试数据。但对于 Knowhere,不辨别训练数据和查问数据。对于每一个 segment,Knowhere 都是用该 segment 的全量数据做训练,再基于该训练后果插入全量数据构建索引。
Knowhere 代码架构
Knowhere 里所有的操作都是针对 index 的操作。
下图最左侧的 DataObj 是 Knowhere 中所有数据结构的基类,只有一种虚办法 Size();Index 类继承了 DataObj,并有一个 field 名为 size_,有 Serialize() 和 Load() 两种虚办法。由 Index 派生出的 VecIndex 是所有向量索引的纯虚基类。从图中可见,它提供了训练(Train)、查问(Query)、统计信息(GetStatistics、ClearStatistics)等办法。
上图右侧展现了其余几种索引类型。
- Faiss 原生索引有 2 个基类:FaissBaseIndex 是所有 Faiss 原生 FLOAT 索引的基类;FaissBaseBinaryIndex 是所有 Faiss 原生 BINARY 索引的基类。
- GPUIndex 是所有 Faiss 原生 GPU 索引的基类。
- OffsetBaseIndex 是自研的索引基类,在索引里只存向量 ID,对于 128 纬向量,索引文件能减小 2 个数量级。因而,该索引在查问时须要配合原始向量一起应用。
IDMAP 是一种不是索引的索引,俗称“暴搜”。原始向量插入后,不须要训练和构建,间接用暴搜对原始向量数据进行查问。然而为了和其余索引统一,IDMAP 也继承自 VecIndex,并且实现了它所有的虚接口,因而它和其余索引的应用办法雷同。
上图是 IVF 系列,也是目前应用最多的索引接口类型。由 VecIndex 和 FaissBaseIndex 派生出了 IVF,由 IVF 再派生出 IVFSQ 和 IVFPQ;由 GPUIndex 和 IVF 派生出了 GPUIVF,由 GPUIVF 再派生出 GPUIVFSQ 和 GPUIVFPQ。
IVFSQHybrid 是自研的混合索引,coarse quantizer 在 GPU 上执行,桶内查问在 CPU 上执行,它利用了 GPU 运算能力强的特点,又缩小了 CPU 和 GPU 之间的内存拷贝,所以查问召回率和 GPUIVFSQ 一样,但查问性能更高。
另外 Binary 类型索引的根本类架构比较简单,由 FaissBaseBinaryIndex 和 VecIndex 派生出 BinaryIDMAP 和 BinaryIVF,不再开展介绍。
目前除了 Faiss 系列的索引,其它的第三方索引只反对两种,一种是基于树的索引 Annoy,一种是基于图的索引 HNSW。这两种索引是当初应用最多的,且都是间接从 VecIndex 派生进去。
Knowhere 如何增加索引
想在 Knowhere 中增加新索引,举荐参考现有索引。若增加基于矢量量化的索引,举荐参考 IVF_FLAT;若增加基于图的索引,举荐参考 HNSW;若增加基于树的索引,举荐参考 Annoy。
具体步骤如下:
- 在
IndexEnum
中增加新索引名称字符串; - 在 [ConfAdapter.cpp] 中退出新索引参数的合法性检查(次要是在 train 和 query 时的参数查看);
- 为新索引新建独自文件,新索引的基类应该至多蕴含 VecIndex,实现 VecIndex 须要的虚接口;
- 在
VecIndexFactory::CreateVecIndex()
中增加新索引的创立逻辑; - 最初,在
unittest
目录下增加单元测试。
Knowhere 对 Faiss 的优化
Knowhere 对 Faiss 做了很多性能上的扩大和性能上的优化。
1、反对 BitsetView
最开始引入 Bitset 是为了反对“soft delete”,Bitset 里的每个 bit 对应 index 中的一行向量,若 bit 位为 1,表明该行向量已被删除,该行向量在查问时不参加运算。
起初 Bitset 的利用有了扩大,不再局限于反对 delete,但 Bitset 的根本语义不变,只有 bit 为 1 就表明其对应的向量不参加查问。
Knowhere 所有对外裸露的 Faiss 索引查问接口都增加了 Bitset 参数,包含 CPU 索引和 GPU 索引。
Bitset 的具体阐明可参考 干货分享|Bitset 利用详解
2、反对更多的 Binary Index 间隔计算形式: Jaccard, Tanimoto, Superstructure, Substructure
Jaccard 间隔和 Tanimoto 间隔可用于计算样本间的类似度;SuperStructure 和 Substructure 可用于计算化学分子式之间的类似度。
3、反对 AVX512 指令集
FAISS 原生反对的指令集包含 AARCH64 / SSE42 / AVX2,咱们在 AVX2 的根底上增加了对于指令集 AVX512 的反对。相比于 AVX2,AVX512 在构建索引和查问时能晋升性能 20% – 30%。
可参考文章 Milvus 在 AVX-512 与 AVX2 的性能比照
4、反对指令集动静加载
原生 Faiss 反对哪种指令集须要在编译时通过参数宏指定,如果采纳这种形式,Milvus 在 release 时就须要为每种指令集编译特定的 Milvus 镜像,用户在应用时也必须依据硬件环境抉择特定的 Milvus 镜像。这就给 Milvus 发行和用户应用带来了不便。
为了解决这一问题,Knowhere 定义了不同指令集须要实现的对立函数接口,并把不同指令集的函数实现别离放到不同的文件中,而后用不同的编译参数同时编译所有的文件。
在运行时,Knowhere 也提供了接口,容许用户手动抉择运行的指令集函数;或者 Knowhere 会先查看以后运行环境的 CPU 所能反对的最高指令集,而后挂载该指令集对应的函数。
5、其它性能优化
可参考咱们在 SIGMOD 发表的论文 Milvus: A Purpose-Built Vector Data Management System
完整版视频解说请戳:
Deep dive# Milvus 2.0 Knowhere 概述_哔哩哔哩_bilibili
如果你在应用的过程中,对 Milvus 有任何改良或倡议,欢送在 GitHub 或者各种官网渠道和咱们保持联系~
Zilliz 以从新定义数据迷信为愿景,致力于打造一家寰球当先的开源技术创新公司,并通过开源和云原生解决方案为企业解锁非结构化数据的暗藏价值。
Zilliz 构建了 Milvus 向量数据库,以放慢下一代数据平台的倒退。Milvus 数据库是 LF AI & Data 基金会的毕业我的项目,可能治理大量非结构化数据集,在新药发现、举荐零碎、聊天机器人等方面具备宽泛的利用。