LLM (Large Language Model) や RAG (Retrieval-Augmented Generation) の台頭により、Milvus や Pinecone などのベクトル データベースが大きな注目を集めています。従来のデータベースも、PostgreSQL の pgvector や SQLite の sqlite-vss など、サードパーティの拡張機能を介したベクトル検索のサポートに追いつきつつあります。
この記事では、vectorlite を紹介します。これは、私が SQLite 用に作成したもう 1 つの高速で調整可能なベクトル検索拡張機能で、最初のベータ版がリリースされたばかりです。 pip
を使用してインストールできるようになりました。
pip install vectorlite-py
SQLite 拡張機能は、実行時に SQLite によってロードできる動的ライブラリです。以下は、SQLite CLI シェルでの Vectorlite の使用例です。
-- Load vectorlite .load path/to/vectorlite.[so|dll|dylib] -- shows vectorlite version and build info. select vectorlite_info(); -- Calculate vector l2(squared) distance select vector_distance(vector_from_json('[1,2,3]'), vector_from_json('[3,4,5]'), 'l2'); -- Create a virtual table named my_table with one vector column my_embedding with a dimention of 3 using default HNSW parameters. create virtual table my_table using vectorlite(my_embedding float32[3], hnsw(max_elements=100)); -- Insert vectors into my_table. rowid can be used to relate to a vector's metadata stored elsewhere, e.g. another table. insert into my_table(rowid, my_embedding) values (0, vector_from_json('[1,2,3]')); insert into my_table(rowid, my_embedding) values (1, vector_from_json('[2,3,4]')); insert into my_table(rowid, my_embedding) values (2, vector_from_json('[7,7,7]')); -- Find 2 approximate nearest neighbors of vector [3,4,5] with distances select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[3,4,5]'), 2)); -- Find the nearest neighbor of vector [3,4,5] among vectors with rowid 0 and 1. (requires sqlite_version>=3.38) -- It is called metadata filter in vectorlite, because you could get rowid set beforehand based on vectors' metadata and then perform vector search. -- Metadata filter is pushed down to the underlying index when traversing the HNSW graph. select rowid, distance from my_table where knn_search(my_embedding, knn_param(vector_from_json('[3,4,5]'), 1)) and rowid in (0, 1) ;
完全な API リファレンスについては、ここを確認してください。
まず第一に、SQLite は軽量でデータをローカルに保存し、データをより安全にするため、LLM を利用したアプリにとって貴重なツールです。
SQLite のベクトル検索を可能にする sqlite-vss はすでに存在しますが、その技術的な決定の一部には議論の価値があるため、vectorlite を作成します。 sqlite-vss の作者は別のベクトル検索拡張プロジェクトに移り、彼の観点から sqlite-vss の問題点を説明する記事を書きました。私のものを共有します。
Sqlite-vss は faiss を使用してベクトル検索を行います。これは Meta(facebook) によってオープンソース化された優れたライブラリであり、ベクトル検索用の幅広いアルゴリズムを提供します。ただし、大規模なデータセットに対するバッチ操作用に最適化されているため、単一のベクトル クエリや CPU での増分インデックス作成が遅くなります。ただし、SQLite の拡張モデル (仮想テーブルと呼ばれる) はバッチ操作用の API を提供しておらず、一度に 1 行を挿入/更新/削除するための API のみを公開しています。さらに、sqlite-vss は単一ベクトル検索のみをサポートしますが、faiss はこれが苦手です。結果として、sqlite-vss は faiss のパフォーマンスを最大限に活用できません。
Vectorlite は、HNSW アルゴリズムの高速実装を提供する hnswlib を使用し、増分インデックス構築と単一ベクトル クエリ用に最適化されており、SQLite の仮想テーブル API とうまく連携します。
私のベンチマークでは、sqlite-vss と比較して、vectorlite はベクターの挿入が 10 倍、検索が 2 倍から 40 倍高速です (速度と精度のトレードオフのある HNSW パラメーターに応じて異なります)。これは主に、ベクター検索ライブラリの選択の違いによるものです。 .
sqlite-vss に欠けているもう 1 つの重要な機能は、ベクトル メタデータ フィルタリングです。実際のシナリオでは、ベクトルには常に、日付、ジャンル、カテゴリ、名前、タイプ、コンテンツなどを含むメタデータがタグ付けされています。ベクトル検索を実行する前に、タグに基づいてベクトルをフィルタリングすることが必須の機能であることは間違いありません。機能ベクトルデータベース。
Vectorlite は、最初のリリース以来、ベクター メタデータ フィルターをサポートしています (SQLite バージョン >= 3.38 が必要)。この例を確認してください。
sqlite-vss と Vectorlite はどちらも、ベクトル検索に ANN (近似最近傍法) アルゴリズムを利用しています。つまり、結果が 100% 正しいとは保証されませんが、検索は比較的高速です。本番環境では、ワークロード、埋め込み、特定のニーズに基づいて、ANN アルゴリズムのパフォーマンスをベンチマークし、調整する必要があります。たとえば、ある企業はリアルタイム セマンティック検索アプリを構築しているため、低い再現率を犠牲にして高速なベクトル クエリが必要な場合があります。一方、別の企業は、オフライン分析を行っているため、高い再現率が必要だが、検索速度は気にしない可能性があります。
In a typical recommendation system, its vector index is often built and tuned offline using libraries like faiss and hnswlib for acceptable speed and accuracy, and then served in production.
With sqlite-vss, you have to build vector index using it, making performance tuning very difficult. One cannot build the vector index offline with faiss and then serve the index with sqlite-vss. Though you do get to pass faiss strings to it to tune the index, non-default indexes are so slow that they barely work (probably due to the nature of the algorithm or improper use of faiss I mentioned above).
With vectorlite, you have full control over the HNSW paramters to tune the search speed and recall rate. Please check this example and the result.
┏━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┓ ┃ distance ┃ vector ┃ ef ┃ ┃ ef ┃ insert_time ┃ search_time ┃ recall ┃ ┃ type ┃ dimension ┃ construction ┃ M ┃ search ┃ per vector ┃ per query ┃ rate ┃ ┡━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━┩ │ l2 │ 256 │ 200 │ 32 │ 10 │ 291.13 us │ 35.70 us │ 31.60% │ │ l2 │ 256 │ 200 │ 32 │ 50 │ 291.13 us │ 99.50 us │ 72.30% │ │ l2 │ 256 │ 200 │ 32 │ 100 │ 291.13 us │ 168.80 us │ 88.60% │ │ l2 │ 256 │ 200 │ 32 │ 150 │ 291.13 us │ 310.53 us │ 95.50% │ │ l2 │ 256 │ 200 │ 48 │ 10 │ 286.92 us │ 37.79 us │ 37.30% │ │ l2 │ 256 │ 200 │ 48 │ 50 │ 286.92 us │ 117.73 us │ 80.30% │ │ l2 │ 256 │ 200 │ 48 │ 100 │ 286.92 us │ 196.01 us │ 93.80% │ │ l2 │ 256 │ 200 │ 48 │ 150 │ 286.92 us │ 259.88 us │ 98.20% │ │ l2 │ 256 │ 200 │ 64 │ 10 │ 285.82 us │ 50.26 us │ 42.60% │ │ l2 │ 256 │ 200 │ 64 │ 50 │ 285.82 us │ 138.83 us │ 84.00% │ │ l2 │ 256 │ 200 │ 64 │ 100 │ 285.82 us │ 253.18 us │ 95.40% │ │ l2 │ 256 │ 200 │ 64 │ 150 │ 285.82 us │ 316.45 us │ 98.70% │ │ l2 │ 1024 │ 200 │ 32 │ 10 │ 1395.02 us │ 158.75 us │ 23.50% │ │ l2 │ 1024 │ 200 │ 32 │ 50 │ 1395.02 us │ 564.27 us │ 60.30% │ │ l2 │ 1024 │ 200 │ 32 │ 100 │ 1395.02 us │ 919.26 us │ 79.30% │ │ l2 │ 1024 │ 200 │ 32 │ 150 │ 1395.02 us │ 1232.40 us │ 88.20% │ │ l2 │ 1024 │ 200 │ 48 │ 10 │ 1489.91 us │ 252.91 us │ 28.50% │ │ l2 │ 1024 │ 200 │ 48 │ 50 │ 1489.91 us │ 848.13 us │ 69.40% │ │ l2 │ 1024 │ 200 │ 48 │ 100 │ 1489.91 us │ 1294.02 us │ 86.80% │ │ l2 │ 1024 │ 200 │ 48 │ 150 │ 1489.91 us │ 1680.97 us │ 94.20% │ │ l2 │ 1024 │ 200 │ 64 │ 10 │ 1412.03 us │ 273.36 us │ 33.30% │ │ l2 │ 1024 │ 200 │ 64 │ 50 │ 1412.03 us │ 899.13 us │ 75.50% │ │ l2 │ 1024 │ 200 │ 64 │ 100 │ 1412.03 us │ 1419.61 us │ 90.10% │ │ l2 │ 1024 │ 200 │ 64 │ 150 │ 1412.03 us │ 1821.85 us │ 96.00% │ │ cosine │ 256 │ 200 │ 32 │ 10 │ 255.22 us │ 28.66 us │ 38.60% │ │ cosine │ 256 │ 200 │ 32 │ 50 │ 255.22 us │ 85.39 us │ 75.90% │ │ cosine │ 256 │ 200 │ 32 │ 100 │ 255.22 us │ 137.31 us │ 91.10% │ │ cosine │ 256 │ 200 │ 32 │ 150 │ 255.22 us │ 190.87 us │ 95.30% │ │ cosine │ 256 │ 200 │ 48 │ 10 │ 259.62 us │ 57.31 us │ 46.60% │ │ cosine │ 256 │ 200 │ 48 │ 50 │ 259.62 us │ 170.54 us │ 84.80% │ │ cosine │ 256 │ 200 │ 48 │ 100 │ 259.62 us │ 221.11 us │ 94.80% │ │ cosine │ 256 │ 200 │ 48 │ 150 │ 259.62 us │ 239.90 us │ 97.90% │ │ cosine │ 256 │ 200 │ 64 │ 10 │ 273.21 us │ 49.34 us │ 48.10% │ │ cosine │ 256 │ 200 │ 64 │ 50 │ 273.21 us │ 139.07 us │ 88.00% │ │ cosine │ 256 │ 200 │ 64 │ 100 │ 273.21 us │ 242.51 us │ 96.30% │ │ cosine │ 256 │ 200 │ 64 │ 150 │ 273.21 us │ 296.21 us │ 98.40% │ │ cosine │ 1024 │ 200 │ 32 │ 10 │ 1192.27 us │ 146.86 us │ 27.40% │ │ cosine │ 1024 │ 200 │ 32 │ 50 │ 1192.27 us │ 451.61 us │ 66.10% │ │ cosine │ 1024 │ 200 │ 32 │ 100 │ 1192.27 us │ 826.40 us │ 83.30% │ │ cosine │ 1024 │ 200 │ 32 │ 150 │ 1192.27 us │ 1199.33 us │ 90.00% │ │ cosine │ 1024 │ 200 │ 48 │ 10 │ 1337.96 us │ 200.14 us │ 33.10% │ │ cosine │ 1024 │ 200 │ 48 │ 50 │ 1337.96 us │ 654.35 us │ 72.60% │ │ cosine │ 1024 │ 200 │ 48 │ 100 │ 1337.96 us │ 1091.57 us │ 88.90% │ │ cosine │ 1024 │ 200 │ 48 │ 150 │ 1337.96 us │ 1429.51 us │ 94.50% │ │ cosine │ 1024 │ 200 │ 64 │ 10 │ 1287.88 us │ 257.67 us │ 38.20% │ │ cosine │ 1024 │ 200 │ 64 │ 50 │ 1287.88 us │ 767.61 us │ 77.00% │ │ cosine │ 1024 │ 200 │ 64 │ 100 │ 1287.88 us │ 1250.36 us │ 92.10% │ │ cosine │ 1024 │ 200 │ 64 │ 150 │ 1287.88 us │ 1699.57 us │ 96.50% │ └──────────┴───────────┴──────────────┴────┴────────┴─────────────┴─────────────┴────────┘
The same benchmark is also run for sqlite-vss using its default index:
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓ ┃ vector dimension ┃ insert_time(per vector) ┃ search_time(per query) ┃ recall_rate ┃ ┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩ │ 256 │ 3644.42 us │ 1483.18 us │ 55.00% │ │ 1024 │ 18466.91 us │ 3412.92 us │ 52.20% │ └──────────────────┴─────────────────────────┴────────────────────────┴─────────────┘
Vectorlite is not only much faster, but also offers much better recall rate if properly tuned. Besides, you can also build the index using hnswlib directly and serve the index using vectorlite as vectorlite can be considered a thin wrapper around hnswlib with a SQL API.
The quickest way to get started is to install vectorlite using python.
# Note: vectorlite-py not vectorlite. vectorlite is another project. pip install vectorlite-py apsw numpy
Vectorlite's metadata filter feature requires sqlite>=3.38. Python's builtin sqlite module is usually built with old sqlite versions. So apsw is used here as the SQLite driver, as it provides bindings to newer SQLite. Vectorlite still works with old SQLite versions if metadata filter support is not required.
Below is a minimal example of using vectorlite.
import vectorlite_py import apsw import numpy as np """ Quick start of using vectorlite extension. """ conn = apsw.Connection(':memory:') conn.enable_load_extension(True) # enable extension loading conn.load_extension(vectorlite_py.vectorlite_path()) # load vectorlite cursor = conn.cursor() # check if vectorlite is loaded print(cursor.execute('select vectorlite_info()').fetchall()) # Vector distance calculation for distance_type in ['l2', 'cosine', 'ip']: v1 = "[1, 2, 3]" v2 = "[4, 5, 6]" # Note vector_from_json can be used to convert a JSON string to a vector distance = cursor.execute(f'select vector_distance(vector_from_json(?), vector_from_json(?), "{distance_type}")', (v1, v2)).fetchone() print(f'{distance_type} distance between {v1} and {v2} is {distance[0]}') # generate some test data DIM = 32 # dimension of the vectors NUM_ELEMENTS = 10000 # number of vectors data = np.float32(np.random.random((NUM_ELEMENTS, DIM))) # Only float32 vectors are supported by vectorlite for now # Create a virtual table using vectorlite using l2 distance (default distance type) and default HNSW parameters cursor.execute(f'create virtual table my_table using vectorlite(my_embedding float32[{DIM}], hnsw(max_elements={NUM_ELEMENTS}))') # Vector distance type can be explicitly set to cosine using: # cursor.execute(f'create virtual table my_table using vectorlite(my_embedding float32[{DIM}] cosine, hnsw(max_elements={NUM_ELEMENTS}))') # Insert the test data into the virtual table. Note that the rowid MUST be explicitly set when inserting vectors and cannot be auto-generated. # The rowid is used to uniquely identify a vector and serve as a "foreign key" to relate to the vector's metadata. # Vectorlite takes vectors in raw bytes, so a numpy vector need to be converted to bytes before inserting into the table. cursor.executemany('insert into my_table(rowid, my_embedding) values (?, ?)', [(i, data[i].tobytes()) for i in range(NUM_ELEMENTS)]) # Query the virtual table to get the vector at rowid 12345. Note the vector needs to be converted back to json using vector_to_json() to be human-readable. result = cursor.execute('select vector_to_json(my_embedding) from my_table where rowid = 1234').fetchone() print(f'vector at rowid 1234: {result[0]}') # Find 10 approximate nearest neighbors of data[0] and there distances from data[0]. # knn_search() is used to tell vectorlite to do a vector search. # knn_param(V, K, ef) is used to pass the query vector V, the number of nearest neighbors K to find and an optional ef parameter to tune the performance of the search. # If ef is not specified, ef defaults to 10. For more info on ef, please check https://github.com/nmslib/hnswlib/blob/v0.8.0/ALGO_PARAMS.md result = cursor.execute('select rowid, distance from my_table where knn_search(my_embedding, knn_param(?, 10))', [data[0].tobytes()]).fetchall() print(f'10 nearest neighbors of row 0 is {result}') # Find 10 approximate nearest neighbors of the first embedding in vectors with rowid within [1000, 2000) using metadata(rowid) filtering. rowids = ','.join([str(rowid) for rowid in range(1000, 2000)]) result = cursor.execute(f'select rowid, distance from my_table where knn_search(my_embedding, knn_param(?, 10)) and rowid in ({rowids})', [data[0].tobytes()]).fetchall() print(f'10 nearest neighbors of row 0 in vectors with rowid within [1000, 2000) is {result}') conn.close()
More examples can be found in examples and integration_test folder.
Vectorlite is created with the hope that it could be the go-to vector search solution for SQLite like pgvector for PostgreSQL.
It is still in early stage. Any suggestions are welcome and appreciated.
以上がVectorlite の紹介: SQLite 用の高速で調整可能なベクトル検索拡張機能の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。