LLM(Large Language Models)과 RAG(Retrieval-Augmented Generation)의 등장으로 Milvus, Pinecone과 같은 벡터 데이터베이스가 주목을 받고 있습니다. 또한 기존 데이터베이스는 PostgreSQL용 pgVector 및 SQLite용 sqlite-vss와 같은 타사 확장을 통해 벡터 검색 지원을 따라잡고 있습니다.
이 기사에서는 제가 방금 첫 번째 베타 버전을 출시한 SQLite용으로 작성한 또 다른 빠르고 조정 가능한 벡터 검색 확장 프로그램인 벡터라이트를 소개합니다. 이제 pip를 사용하여 설치할 수 있습니다
pip install vectorlite-py
SQLite 확장은 런타임 시 SQLite에 의해 로드될 수 있는 동적 라이브러리입니다. 다음은 SQLite CLI 셸에서 벡터라이트를 사용하는 예입니다.
-- 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를 제공하지 않으며 한 번에 하나의 행을 삽입/업데이트/삭제하는 API만 노출합니다. 게다가 sqlite-vss는 단일 벡터 검색만 지원하는데, 이는 faiss가 좋지 않습니다. 결과적으로 sqlite-vss는 faiss의 성능을 완전히 활용하지 못합니다.
Vectorlite는 HNSW 알고리즘의 빠른 구현을 제공하고 증분 인덱스 구성 및 단일 벡터 쿼리에 최적화된 hnswlib를 사용하며 이는 SQLite의 가상 테이블 API와 잘 작동합니다.
내 벤치마크에서 sqlite-vss와 비교했을 때 벡터라이트는 벡터 삽입 속도가 10배 더 빠르고 검색 속도가 2배~40배 더 빠릅니다(속도-정확도 균형을 갖춘 HNSW 매개변수에 따라 다름). 이는 주로 벡터 검색 라이브러리 선택이 다르기 때문입니다. .
sqlite-vss가 놓친 또 다른 중요한 기능은 벡터 메타데이터 필터링입니다. 실제 시나리오에서 벡터는 항상 날짜, 장르, 카테고리, 이름, 유형, 내용 등을 포함한 일부 메타데이터로 태그가 지정됩니다. 벡터 검색을 수행하기 전에 태그를 기반으로 벡터를 필터링하는 것은 의심의 여지가 없습니다. 기능적 벡터 데이터베이스.
Vectorlite는 첫 번째 출시 이후 벡터 메타데이터 필터(sqlite 버전 >= 3.38 필요)를 지원합니다. 예시를 확인해주세요.
sqlite-vss와 vectorlite는 모두 벡터 검색에 ANN(Approximate Nearest Neighbors) 알고리즘을 활용합니다. 즉, 결과가 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.
위 내용은 벡터라이트 소개: SQLite를 위한 빠르고 조정 가능한 벡터 검색 확장의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!