milvus 簡介
milvus是幹什麼的?通俗的講,milvus可以讓你在海量向量庫中快速檢索到和目標向量最相似的若干個向量,這裏相似度量標準可以是內積或者歐式距離等。借用官方的話說就是:
Milvus 是一款開源的、針對海量特徵向量的相似性搜索引擎。基於異構衆覈計算框架設計,成本更低,性能更好。 在有限的計算資源下,十億向量搜索僅毫秒響應。
說白了就是速度快,先不說十億向量,自己寫代碼去完成對100萬300維向量的餘弦相似度計算並排序就需要不小的響應時間吧,就本人測試而言,即便使用scipy庫計算速度依然要比milvus慢很多。
milvus和Faiss的對比
其實在milvus開源之前,也存在高性能向量相似性搜索引擎(庫),這個引擎就是Facebook的Faiss,它的功能和milvus是一樣的,所以就不再做過多介紹,具體可以參考官網
就我個人而言,我是推薦使用milvus的,主要是在我個人看來,milvus有如下幾個好處:
- 多平臺通用,mac,windows和linux都是支持的,因爲milvus可以通過docker部署,因此平臺通用性好了不少。
- 支持編程語言多,Java,c,c++和python都支持,要知道Faiss是不支持java的,這一點簡直讓人抓狂,github上好幾個項目就是關於把Faiss轉成java的,因爲我Java和python都是要使用的,我把github上關於faiss轉java的項目都試了個遍,結論就是非常難安裝,只要Faiss版本更新了,必須要重來一遍,即便最後java可以用了,也不敢保證其穩定性。所以想在Java上用Faiss還是放棄吧。
- 在速度方面,就我自己測試而言,milvus不輸Faiss,但是我沒有使用GPU測試,有興趣的小夥伴可以試一下。
當然milvus也有難用的地方,我自己發現了兩點,如果是我自己使用不當造成的,還請各位朋友在評論指出:
- milvus的向量格式不支持numpy,要用列表的形式存儲向量,而列表佔用內存要遠遠大於ndarray的,這對於小內存主機簡直是個天災,我16GB內存情況下,把500000*768的矩陣轉成list,電腦直接卡死。
- milvus在第一次search時速度會慢,之後機會快起來了,這應該是個小bug,有人在github提出了,應該很快就可以修復,所以你們在測試milvus的速度時千萬別算第一次的時間。
milvus 安裝及常見問題
milvus 一共有兩種安裝方式:自己編譯安裝和使用docker安裝。這裏推薦大家使用docker安裝,docker安裝方便快捷,可在Windows上使用。自己編譯安裝,由於每個人環境不同,很容易出問題。本文只介紹基於docker的安裝,另外因爲我比較窮,所以只介紹cpu版本的安裝,不過gpu安裝也是大同小異。
基於docker安裝milvus CPU版本
簡言之安裝比較簡單,畢竟大佬們已經把milvus部署在了docker裏,我們只要啓動起來就行了
。
安裝docker
首先就是要安裝docker,還不瞭解docker可以瞭解一波,非常好用的虛擬機工具,直接去官網下載對應平臺的安裝文件即可。
下載相應版本鏡像
安裝好docker後,要去pull對應的鏡像(image),首先進到dockerhub官網中,然後搜索milvus,第一個結果就是。因爲我們安裝的是CPU版本,所以在tags裏找cpu-latest,然後pull下來就可以了,即在你的命令行窗口輸入docker pull milvusdb/milvus:cpu-latest
。注意:隨着版本迭代更新,這一條命令在未來可能會失效,建議先去dockerhub搜索一下,去看一下應該用什麼tag。
設置工作目錄和配置文件
在創建啓動容器之前,我們要先設置好工作目錄和配置文件。
一共要設置三個目錄,分別是數據庫存儲目錄,日誌存儲目錄和配置文件目錄。其中配置文件目錄就存放着我說的配置文件。配置文件一共有兩個,分別是服務器設置文件和日誌設置文件。
所以我們要想好這三個文件夾放在哪裏,比如我們可以在當前用戶目錄下建立一個milvus文件夾,然後在這裏面存儲上述三個目錄。下面我們需要設置兩個配置文件,記得要把服務器配置文件名改爲server_config.yaml,把日誌配置文件改爲log_config.conf。
兩個配置文件的內容:服務器配置文件 日誌配置文件。配置文件也可以到官網下載。
下面是我的文件目錄結構,共大家參考:
milvus
│
├─conf //配置文件目錄
│ log_config.conf //服務器配置文件
│ server_config.yaml //日誌配置文件
│
├─db //數據庫存儲目錄
│
└─logs //日誌存儲目錄
│
啓動docker服務
設置好工作目錄後,就可以使用鏡像創建容器了,我的工作目錄是C:\Users\Zhdun\milvus
,所以我的創建命令是:
docker run -td --name mymilvus -e "TZ=Asia/Shanghai" -p 19530:19530 -p 8080:8080 -v C:\Users\Zhdun\milvus\db:/var/lib/milvus/db -v C:\Users\Zhdun\milvus\conf:/var/lib/milvus/conf -v C:\Users\Zhdun\milvus\logs:/var/lib/milvus/logs milvusdb/milvus:cpu-latest
命令看起來有點長, 我稍微解釋下,-td是後臺運行,--name是給自己的容器起個名字,-p是端口映射,不想用默認的話,可以去服務器配置文件裏改,-v就是爲了映射三個工作目錄。具體可以參考docker的run命令。
執行完命令後,運行docker ps -a,如果發現自己創建的容器excited的了,那就docker logs一下,看出了什麼問題。如果發現容器在運行了,就代表基本沒問題了。
接下來我會說一下常見的安裝問題,以及如何去使用milvus。
安裝時的常見問題及解決
Config check fail: Invalid config version: . Expected config version: 0.1 遇到這種問題就在服務器的配置文件第一行加上version: 0.1
。
Config check fail: Invalid cpu cache capacity: 1. Possible reason: sum of cache_config.cpu_cache_capacity and db_config.insert_buffer_size exceeds system memory.
這種問題就說明內存超出了限制,首先檢查服務器配置裏的 cpu_cache_capacity 和 insert_buffer_size 是不是過大了。
然後再檢查給定docker設定的內存是多少,可以通過docker info來檢查。
milvus 基本使用
安裝完成後,終於可以開始使用milvus了,milvus支持python,java和c++。在這裏我只介紹python的使用。
首先安裝 pymilvus庫:pip install pymilvus
,然後就可以使用這個庫來寫代碼了,接下來我會直接把自己寫的範例代碼貼上去,其中每一步的具體含義以及可能的擴展我會直接在註釋裏告訴大家,如有錯誤還請各位指出。
# -*- coding: utf-8 -*-
#導入相應的包
import numpy as np
from milvus import Milvus, IndexType, MetricType
# 初始化一個Milvus類,以後所有的操作都是通過milvus來的
milvus = Milvus()
# 連接到服務器,注意端口映射,要和啓動docker時設置的端口一致
milvus.connect(host='localhost', port='19530')
# 向量個數
num_vec = 5000
# 向量維度
vec_dim = 768
# 創建表
# 參數含義
# table_name: 表名
# dimension: 向量維度
# metric_type: 向量相似度度量標準, MetricType.IP是向量內積; MetricType.L2是歐式距離
table_param = {'table_name': 'mytable', 'dimension':vec_dim, 'index_file_size':1024, 'metric_type':MetricType.IP}
milvus.create_table(table_param)
# 隨機生成一批向量數據
vectors_array = np.random.rand(num_vec,vec_dim)
vectors_list = vectors_array.tolist()
# 官方建議在插入向量之前,建議先使用 milvus.create_index 以便系統自動增量創建索引
# 索引類型有:FLAT / IVFLAT / IVF_SQ8 / IVF_SQ8H,其中FLAT是精確索引,速度慢,但是有100%的召回率
index_param = {'index_type': IndexType.FLAT, 'nlist': 128}
milvus.create_index('mytable', index_param)
# 把向量添加到剛纔建立的表格中
# ids可以爲None,使用自動生成的id
status, ids = milvus.add_vectors(table_name="mytable",records=vectors_list,ids=None) # 返回這一組向量的ID
# 官方建議 向量插入結束後,相同的索引需要手動再創建一次
milvus.create_index('mytable', index_param)
# 輸出一些統計信息
status, tables = milvus.show_tables()
print("所有的表格:",tables)
print("表格的數據量(行):{}".format((milvus.count_table('mytable')[1])))
print("mytable表格是否存在:",milvus.has_table("mytable")[1])
# 加載表格到內存
milvus.preload_table('mytable')
# 創建查詢向量
query_vec_array = np.random.rand(1,vec_dim)
query_vec_list = query_vec_array.tolist()
# 進行查詢, 注意這裏的參數nprobe和建立索引時的參數nlist 會因爲索引類型不同而影響到查詢性能和查詢準確率
# 對於 FLAT類型索引,兩個參數對結果和速度沒有影響
status, results = milvus.search(table_name='mytable', query_records=query_vec_list, top_k=4, nprobe=16)
print(status)
print(results)
# 刪除表格和索引, 不刪除的話,下一次還可以繼續使用
milvus.drop_index(table_name="mytable")
milvus.delete_table(table_name="mytable")
# 斷開連接
milvus.disconnect()
milvus 多進程使用
寫這一章的主要目的是爲了進行併發測試,以及多進程能否節省時間,官方說明在使用多進程時需要滿足下面兩個條件:
- 程序執行時主進程中沒有創建 client
- 每個子進程分別創建 client 進行操作
下面是我的測試代碼:
# -*- coding: utf-8 -*-
import time
from multiprocessing import Pool
import numpy as np
import random
from milvus import Milvus, IndexType, MetricType
def create_data(host,port,num_vec,vec_dim):
""" 創建一些表格和索引用來做多進程測試 """
milvus = Milvus()
milvus.connect(host=host, port=port)
# 創建2個表
table_param = {'table_name': 'table1', 'dimension':vec_dim, 'index_file_size':1024, 'metric_type':MetricType.IP}
milvus.create_table(table_param)
table_param = {'table_name': 'table2', 'dimension':vec_dim, 'index_file_size':1024, 'metric_type':MetricType.L2}
milvus.create_table(table_param)
# 隨機生成一批向量數據
vectors_array = np.random.rand(num_vec,vec_dim)
vectors_list = vectors_array.tolist()
# 創建索引
index_param = {'index_type': IndexType.FLAT, 'nlist': 128}
milvus.create_index('table1', index_param)
milvus.create_index('table2', index_param)
# 添加數據
milvus.add_vectors(table_name="table1",records=vectors_list,ids=None)
milvus.add_vectors(table_name="table2",records=vectors_list,ids=None)
# 創建索引
milvus.create_index('table1', index_param)
milvus.create_index('table2', index_param)
print(milvus.show_tables())
# 斷開連接
milvus.disconnect()
def clear_table(host,port):
""" 清空表格和索引 """
milvus = Milvus()
milvus.connect(host=host, port=port)
for table_name in milvus.show_tables()[1]:
milvus.drop_index(table_name=table_name)
milvus.delete_table(table_name=table_name)
milvus.disconnect()
def milvus_search(host,port,table_name,query_vec,search_time=10):
""" 測試查詢, 返回查詢的秒數"""
milvus = Milvus()
milvus.connect(host=host, port=port)
# 因爲bug的原因,要先搜索一次
milvus.search(table_name,4,8,query_vec)
# 開始測試
for _ in range(search_time):
query_vec[0][0] = random.random() # 稍微隨機化一下
milvus.search(table_name, 4, 8, query_vec)
if __name__ == "__main__":
host = "localhost"
port = "19530"
num_vec = 100000
vec_dim = 768
num_proc = 3 # 進程數
search_time = 2000 # 搜索次數
####### Step1 先創建用於測試的數據 運行一次就行了
# create_data(host=host,port=port,num_vec=num_vec,vec_dim=vec_dim)
# clear_table(host,port)
# exit(0)
####### Step2 測試依次執行的時間
start_time = time.time()
for _ in range(num_proc):
query_vec = np.random.rand(1,vec_dim).tolist()
milvus_search(host,port,"table1",query_vec,search_time)
end_time = time.time()
print("順序執行milvus_search的時間總和是:",end_time-start_time)
####### Step3 測試多進程時間
pool = Pool(num_proc)
start_time = time.time()
for _ in range(num_proc):
query_vec = np.random.rand(1,vec_dim).tolist()
pool.apply_async(milvus_search,args=(host,port,"table1",query_vec,search_time))
pool.close()
pool.join()
end_time = time.time()
print("並行執行milvus_search的時間總和是:",end_time-start_time)
結論就是對於search操作,依次search100次,和10個進程同時開,每個進程search10次,開多進程速度是會變快的。
感謝閱讀,如有問題,煩請指出。
文章可以隨意轉載,但請務必註明出處:
事實是什麼? 這些事實所證實的真理是什麼? 永遠不要讓自己被自己所更願意相信所影響, 只是單單地去審視, 什麼纔是事實.