使用 SVS Vamana 和 Milvus 進行搜尋
SVS(可擴展向量搜尋)是英特爾(Intel)的一套高效能向量搜尋函式庫,實作 Vamana 圖演算法。 在 Milvus 中,您可以透過FAISS 索引穿透機制存取 SVS Vamana:將 `index_type ` 設定為 `FAISS `,並提供 `SVSVamana ` 索引建構字串,Milvus 便會透過 FAISS 的 SVS 整合功能來建立並搜尋索引。
Vamana 是DISKANN亦採用的圖結構,但 SVS Vamana 會將整個圖結構保存在記憶體中。它還能透過LeanVec或LVQ 壓縮向量,藉此減少記憶體使用量並加速搜尋,同時仍能達到極高的召回率。
先決條件
- Milvus 3.0.0 或更新版本,需從原始碼編譯且已啟用 SVS
- PyMilvus 3.0.0 或更新版本
- 若使用
LVQ*或LeanVec*儲存方案,則需 Intel CPU;FP32、FP16及SQ8儲存方案可在任何平台上運行 - 熟悉FAISS 索引工廠字串
SVS 未包含在 Milvus 的預設建置中。若要使用SVSVamana 工廠,請透過make USE_SVS=ON 從原始碼建置 Milvus。在未包含 SVS 的建置中,索引建置會失敗,因為 Faiss 無法識別該工廠字串。
LVQ 和 LeanVec 壓縮僅能在 Intel CPU 上執行,且在具備 AVX-512 的 CPU 上執行速度更快。在其他 CPU 上,使用LVQ* 和LeanVec* 工廠字串時,索引建置會失敗。
SVS Vamana 的運作原理
Vamana 圖
SVS Vamana 會建構一個單層鄰近圖。每個向量皆為一個節點,而邊則連接可能為近鄰的向量。為了建構此圖,SVS 會針對每個節點x 執行以下步驟:
貪婪搜尋:SVS 會搜尋目前已建構的圖,並維持一個大小為
construction_window_size的候選清單。鄰居修剪:已造訪的節點將成為x 的候選鄰居。SVS 會依照這些節點與x 之間距離由小到大的順序進行考量。 若已保留的更近鄰節點
n滿足alpha * distance(n, c) <= distance(x, c),則跳過候選節點c。此機制可保留長程邊並刪除冗餘的短程邊,使搜尋能跨越圖結構,而非逐個最近鄰節點地移動。當圖的度數達到上限時,修剪過程即告終止。反向邊:對於x 的每個新鄰居,SVS 會新增一條指向x的反向邊,並在該鄰居的邊清單超過圖的度數時進行修剪。
修剪過程分為兩輪:首先使用alpha = 1 ,接著使用設定的alpha 。較大的圖度與construction_window_size 會產生更密集、品質更高的圖,但會消耗更多記憶體且建構時間較長。
在搜尋階段,SVS 會貪婪地遍歷圖,並保留迄今為止觀察到的、距離查詢最近的候選節點(search_window_size )。它總是先探索距離最近且尚未造訪的候選節點。當當前節點的所有鄰居與查詢節點的距離,均比視窗內最差候選節點與查詢節點的距離更遠時,搜尋即告結束。較大的search_window_size 會探索更多圖的區域,這雖能提高召回率,但也會增加延遲。
LeanVec 壓縮
LeanVec同時減少了每個向量的維數以及每維的位元數。LeanVec 索引會為每個向量保存兩種表示形式:
主要表示(降維):將向量線性投影至較低維度的表示。圖搜尋在這個主要資料集上執行,因此大部分的距離計算都在低維空間中進行。這同時降低了運算和記憶體頻寬的需求。預設情況下,降維後的維度為原始維度的一半(
dim / 2)。次要表示(全維度):向量在原始維度下的表示。SVS 會根據此次要資料集,對主要圖搜尋所返回的候選項進行重新排序,以彌補因維度縮減而損失的準確度。
這兩種表示形式均採用LVQ(局部自適應向量量化)進行量化。工廠字串中的儲存組件負責選取編碼方式。例如,LeanVec4x8 對主要資料集採用每維度 4 位元,對次要資料集則採用每維度 8 位元。即使是重新排序步驟,也絕不會讀取全精度向量。
預設情況下,LeanVec 會分別為索引向量和查詢學習獨立的投影,以支援來自與索引向量不同分佈的查詢(即「分佈外」或 OOD)。 此機制對於跨模態工作負載(例如文字到影像檢索)特別有效,且當查詢來自與索引向量相同的分佈時,同樣表現良好。Milvus 目前尚不支援將查詢傳遞以計算投影,因此現階段僅使用基礎向量。OOD 訓練會增加建置時間。 若希望以犧牲部分查詢效能為代價來加快建置速度,請將 `ood_training ` 設定為 `False`。
LVQ 壓縮
對於維度少於約 512 維的資料集,跳過維度縮減並僅使用 LVQ 量化進行壓縮,效果可能更佳。 與 LeanVec 類似,LVQ 索引會保留一個用於快速搜尋的主表示,以及一個用於重新排序的殘差表示。例如,LVQ4x4 會使用 4 位元作為主表示,並使用 4 位元作為殘差表示。LVQ4x0 和LVQ8x0 會跳過重新排序步驟,若其召回率足以滿足您的工作負載需求,此舉可加快查詢速度。
選擇工廠字串
SVS Vamana 的工廠字串格式為SVSVamana<degree>[,<storage>] ,其中<degree> 代表圖中每個節點的最大邊數。
| 預設字串 | 儲存 | 備註 |
|---|---|---|
SVSVamana64 | FP32 | 未壓縮向量。 |
SVSVamana64,FP16 | 16 位元浮點數 | 僅需FP32 一半的向量記憶體,且精度損失極小。 |
SVSVamana64,SQ8 | 8 位元標量量化 | 僅需FP32 四分之一的向量記憶體。 |
SVSVamana64,LVQ4x0,LVQ8x0 | LVQ 為主,無殘差 | 僅限 Intel CPU。不進行重新排序。 |
SVSVamana64,LVQ4x4,LVQ4x8 | LVQ 主要演算法與殘差 | 僅限 Intel CPU。 |
SVSVamana64,LeanVec4x4,LeanVec4x8,LeanVec8x8 | LeanVec 主要與次要 | 僅限 Intel CPU。請添加_<dim> 後綴以設定降維維度。例如,LeanVec4x8_128 會將主要資料集降維至 128 維。若未添加後綴,降維維度將為dim / 2 。 |
SVSVamana64,LeanVec4x8 這是一個不錯的起點。在此基礎上:
若要在犧牲部分記憶體空間的前提下提升召回率,請使用
_<dim>後綴來增加 LeanVec 的維度。若需在犧牲部分召回率的前提下縮小記憶體佔用,請降低 LeanVec 的維度。
對於低維向量,請嘗試使用
LVQ8。在非 Intel CPU 上,請使用
FP16或SQ8。
建立索引
以下範例建立一個圖度為 64 且採用 LeanVec 壓縮的 SVS Vamana 索引。請展開設定區塊並在建立索引前執行該區塊。
import random
from pymilvus import DataType, MilvusClient
client = MilvusClient(uri="http://localhost:19530")
collection_name = "svs_vamana_example"
if client.has_collection(collection_name):
client.drop_collection(collection_name)
rng = random.Random(42)
vectors = [[rng.random() for _ in range(256)] for _ in range(3000)]
schema = client.create_schema(auto_id=False, enable_dynamic_field=False)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("vector", DataType.FLOAT_VECTOR, dim=256)
client.create_collection(collection_name=collection_name, schema=schema)
client.insert(
collection_name=collection_name,
data=[{"id": i, "vector": vector} for i, vector in enumerate(vectors)],
)
client.flush(collection_name=collection_name)
將 `index_type ` 設定為 `FAISS`,並使用 `faiss_index_name ` 選取 SVS Vamana 配方。
index_params = client.prepare_index_params()
index_params.add_index(
field_name="vector",
index_name="svs_vamana_index",
index_type="FAISS",
metric_type="L2",
params={
# SVSVamana64 -> Vamana graph degree 64
# LeanVec4x8 -> 4-bit primary, 8-bit secondary
# _128 -> LeanVec dim 128 (omit the suffix for dim / 2)
"faiss_index_name": "SVSVamana64,LeanVec4x8_128",
"construction_window_size": 200, # build-time search width
"ood_training": True, # OOD on (default)
},
)
client.create_index(collection_name=collection_name, index_params=index_params)
client.load_collection(collection_name=collection_name)
搜尋索引
search_window_size 此參數控制召回率與延遲之間的權衡,如同HNSW 中的 `ef ` 所做的那樣。請將 `search_buffer_capacity ` 設定為至少與該值相同。兩者的預設值皆為 10,且若 `search_window_size ` 大於其 `search_buffer_capacity`,SVS 將拒絕該搜尋請求。
search_params = {
"params": {
"search_window_size": 100, # wider = higher recall, lower QPS
"search_buffer_capacity": 100, # must be >= search_window_size
}
}
res = client.search(
collection_name=collection_name,
data=[vectors[0]],
anns_field="vector",
limit=10,
search_params=search_params,
)
若limit 大於search_window_size ,SVS 會將該次搜尋的視窗大小調高至limit 。
SVS Vamana 參數
以下建置參數應置於params 中的add_index() 內,位於faiss_index_name 旁邊。索引建置完成後,您將無法變更這些參數。
| 參數 | 預設值 | 說明 |
|---|---|---|
construction_window_size | 40 | 在圖構造過程中,用於查找各節點鄰居的候選清單大小。較大的數值可提升圖的品質與召回率,但建構時間會較長。請將其設定為至少等於圖的度數。200 是常見的選擇。 |
alpha | 1.2 針對L2 ,請參閱0.95 ;針對IP ,請參閱COSINE | 修剪強度。數值越遠離 1,修剪越激進,傾向保留長程邊而非冗餘的短程邊。針對L2 ,請使用大於 1 的數值;針對IP 和COSINE ,則使用小於 1 的數值。除非您已針對自身資料測量出更佳的數值,否則請維持預設值。 |
graph_max_degree | 工廠字串中的度數,或32 | 每個節點的最大邊數。此設定會覆寫工廠字串中的度數。數值範圍 [32, 128] 適用於大多數工作負載。較高的數值可提高召回率,但會消耗更多記憶體且建構時間較長。 |
prune_to | 圖的度數減去 4 | 當節點超過圖的度數時,經過一次修剪後保留的邊數。 |
max_candidate_pool_size | 200 | 修剪節點鄰居時所考慮的候選節點最大數量。此數值應大於construction_window_size 。 |
use_full_search_history | True | 修剪時是否會考慮建構搜尋過程中造訪過的每個節點,而不僅限於最終候選節點。 |
leanvec_d | _<dim> 預設字串後綴或dim / 2 | 僅限 LeanVec。主要資料集的降維係數。此參數會覆寫預設字串後綴。較小的數值可減少記憶體使用量並加快圖搜尋速度,但會降低召回率。 |
ood_training | True 適用於 LeanVec | 僅限 LeanVec。是否為索引向量與查詢分別學習投影。若要加快建構速度,請將 `False ` 設為 `true`。若將 `True ` 設定為 LeanVec 以外的任何預設值,設定將失敗。 |
以下搜尋參數應設定於 `search_params.params` 中。您亦可將其設為建置參數,此舉將變更索引在未設定這些參數的搜尋中使用的預設值。
| 參數 | 預設值 | 說明 |
|---|---|---|
search_window_size | 10 | 遍歷圖時保留的最接近候選項數量。較大的數值會提高召回率並增加延遲。 |
search_buffer_capacity | 10 | 搜尋緩衝區的容量。必須大於或等於search_window_size 。在大多數情況下,請將其設定為search_window_size 。對於 LeanVec,請嘗試設定為search_window_size 的 1.5 倍。 |
進一步了解
- 請參閱FAISS 索引直通參考文件,了解一般工廠字串合約、限制及錯誤處理。
- 與DISKANN 中的磁碟上 Vamana 實作進行比較。
- 請參閱SVS 儲存庫,了解建置細節、效能調校以及底層的 C++ API。