1
头图
编者按:本文详细介绍了 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 的数据模型。

  1. Database,现在 Milvus 还不支持多租户,所以只有一个 database
  2. Collection,因为是分布式系统,所以一个 collection 可以被加载到多个 node 上,每个 node 加载该 collection 的一个分片,每个分片称为 shard
  3. Partition,数据的逻辑分片,可加速查询
  4. Segment,是 Partition 中的数据块

在 Milvus 中查询的最小单位是 segment,对 collection 的查询操作最终会被分解为对该 collection 或若干 partition 中所有 segment 的查询。最后,对所有 segment 的查询结果进行归并,并得到最终结果。

如上图所示,为了支持流式数据插入,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)等方法。

上图右侧展示了其他几种索引类型。

  1. Faiss 原生索引有 2 个基类:FaissBaseIndex 是所有 Faiss 原生 FLOAT 索引的基类;FaissBaseBinaryIndex 是所有 Faiss 原生 BINARY 索引的基类。
  2. GPUIndex 是所有 Faiss 原生 GPU 索引的基类。
  3. 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。

具体步骤如下:

  1. IndexEnum 中添加新索引名称字符串;
  2. 在 [ConfAdapter.cpp] 中加入新索引参数的合法性检查(主要是在 train 和 query 时的参数检查);
  3. 为新索引新建单独文件,新索引的基类应该至少包含 VecIndex,实现 VecIndex 需要的虚接口;
  4. VecIndexFactory::CreateVecIndex() 中添加新索引的创建逻辑;
  5. 最后,在 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 基金会的毕业项目,能够管理大量非结构化数据集,在新药发现、推荐系统、聊天机器人等方面具有广泛的应用。


Zilliz
154 声望829 粉丝

Vector database for Enterprise-grade AI