編者按:本文詳細介紹了 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 可以被載入到多個 node 上,每個 node 載入該 collection 的一個分片,每個分片稱為 shard
- Partition,資料的邏輯分片,可加速查詢
- 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)等方法。
上圖右側展示了其他幾種索引型別。
- 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 基金會的畢業專案,能夠管理大量非結構化資料集,在新藥發現、推薦系統、聊天機器人等方面具有廣泛的應用。