Mit dem Aufkommen von LLMs (Large Language Models) und RAG (Retrieval-Augmented Generation) erhalten Vektordatenbanken wie Milvus und Pinecone große Aufmerksamkeit. Herkömmliche Datenbanken holen auch bei der Unterstützung der Vektorsuche über Erweiterungen von Drittanbietern auf, wie z. B. pgvector für PostgreSQL und sqlite-vss für SQLite.
In diesem Artikel stelle ich Vectorlite vor, eine weitere schnelle und anpassbare Vektorsucherweiterung, die ich für SQLite schreibe und die gerade ihre erste Beta-Version veröffentlicht hat. Es kann jetzt mit pip
installiert werden
pip install vectorlite-py
Eine SQLite-Erweiterung ist eine dynamische Bibliothek, die von SQLite zur Laufzeit geladen werden kann. Unten finden Sie ein Beispiel für die Verwendung von Vectorlite in der SQLite-CLI-Shell:
-- 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) ;
Die vollständige API-Referenz finden Sie hier.
Zuallererst ist SQLite ein wertvolles Tool für LLM-basierte Apps, da es leichtgewichtig ist und Daten lokal speichert, wodurch Ihre Daten viel sicherer werden.
Obwohl es bereits sqlite-vss gibt, das die Vektorsuche für SQLite ermöglicht, sind einige seiner technischen Entscheidungen eine Diskussion wert, weshalb ich Vectorlite schreibe. Der Autor von sqlite-vss ist zu einem anderen Projekt zur Erweiterung der Vektorsuche übergegangen und hat einen Artikel geschrieben, in dem er erklärt, was aus seiner Sicht mit sqlite-vss nicht stimmt. Ich werde meine teilen.
Sqlite-vss verwendet Faiss für die Vektorsuche. Es ist eine großartige Open-Source-Bibliothek von Meta (Facebook) und bietet eine breite Palette von Algorithmen für die Vektorsuche. Es ist jedoch für Batch-Vorgänge über einen großen Datensatz optimiert, was es für eine einzelne Vektorabfrage und inkrementelle Indizierung auf der CPU langsam macht. Allerdings stellt das Erweiterbarkeitsmodell von SQLite (virtuelle Tabelle genannt) keine APIs für Batch-Vorgänge bereit und stellt nur APIs zum Einfügen/Aktualisieren/Löschen jeweils einer einzelnen Zeile bereit. Außerdem unterstützt sqlite-vss nur die Suche nach einzelnen Vektoren, was Faiss nicht gut kann. Daher kann sqlite-vss die Leistung von faiss nicht vollständig ausnutzen.
Vectorlite verwendet hnswlib, das eine schnelle Implementierung des HNSW-Algorithmus bietet und für die inkrementelle Indexkonstruktion und Einzelvektorabfragen optimiert ist, was gut mit der virtuellen Tabellen-API von SQLite zusammenspielt.
In meinem Benchmark ist Vectorlite im Vergleich zu sqlite-vss 10x schneller beim Einfügen von Vektoren und 2x-40x schneller bei der Suche (abhängig von den HNSW-Parametern mit Kompromiss zwischen Geschwindigkeit und Genauigkeit), was hauptsächlich auf die unterschiedliche Auswahl der Vektorsuchbibliothek zurückzuführen ist .
Eine weitere wichtige Funktion, die sqlite-vss vermisst, ist die Filterung von Vektormetadaten. In echten Wortszenarien ist ein Vektor immer mit einigen Metadaten versehen, darunter Datum, Genre, Kategorien, Name, Typ, Inhalt usw. Es besteht kein Zweifel daran, dass das Filtern eines Vektors anhand seiner Tags vor der Vektorsuche eine unverzichtbare Funktion ist eine funktionale Vektordatenbank.
Vectorlite unterstützt seit der ersten Veröffentlichung Vektor-Metadatenfilter (erfordert SQLite-Version >= 3.38). Bitte überprüfen Sie dieses Beispiel.
Sowohl sqlite-vss als auch vectorlite nutzen ANN-Algorithmen (Approximate Nearest Neighbors) für die Vektorsuche, was bedeutet, dass das Ergebnis nicht garantiert 100 % korrekt ist, die Suche jedoch relativ schnell ist. In der Produktion muss die Leistung von KNN-Algorithmen anhand Ihrer Arbeitslast, Einbettungen und spezifischen Anforderungen bewertet und abgestimmt werden. Beispielsweise benötigt einer möglicherweise eine schnelle Vektorabfrage auf Kosten einer geringeren Rückrufrate, weil er eine semantische Such-App in Echtzeit erstellt, während ein anderer möglicherweise eine hohe Rückrufrate benötigt, sich aber nicht um die Suchgeschwindigkeit kümmert, weil er eine Offline-Analyse durchführt.
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.
Das obige ist der detaillierte Inhalt vonWir stellen vor: vectorlite: Eine schnelle und anpassbare Vektorsucherweiterung für SQLite. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!