. Sie lösen ein echtes Downside und sind in vielen Fällen die richtige Wahl für RAG-Systeme. Aber hier ist die Sache: Nur weil Sie Einbettungen verwenden, heißt das nicht, dass Sie es sind brauchen eine Vektordatenbank.
Wir haben einen wachsenden Development beobachtet, bei dem jede RAG-Implementierung mit dem Einbinden einer Vektor-DB beginnt. Das magazine für große, persistente Wissensdatenbanken sinnvoll sein, ist aber nicht immer der effizienteste Weg, insbesondere wenn Ihr Anwendungsfall dynamischer oder zeitkritischer ist.
Bei Planck nutzen wir Einbettungen, um LLM-basierte Systeme zu verbessern. In einer unserer realen Anwendungen haben wir uns jedoch dafür entschieden vermeiden eine Vektordatenbank und verwendete stattdessen a einfacher Schlüsselwertspeicherwas sich als viel besser herausstellte.
Bevor ich näher darauf eingehe, wollen wir uns eine einfache, verallgemeinerte Model unseres Szenarios ansehen, um zu erklären, warum.
Foo-Beispiel
Stellen wir uns ein einfaches RAG-System vor. Ein Benutzer lädt einige Textdateien hoch, möglicherweise einige Berichte oder Besprechungsnotizen. Wir teilen diese Dateien in Blöcke auf, generieren Einbettungen für jeden Block und verwenden diese Einbettungen, um Fragen zu beantworten. Der Benutzer stellt in den nächsten Minuten eine Handvoll Fragen und geht dann. Zu diesem Zeitpunkt sind sowohl die Dateien als auch ihre Einbettungen unbrauchbar und können bedenkenlos verworfen werden.
Mit anderen Worten, die Daten sind vergänglichwird der Benutzer nur a fragen paar Fragenund wir wollen sie beantworten so schnell wie möglich.
Halten Sie nun einen Second inne und fragen Sie sich:
Wo soll ich diese Einbettungen speichern?
Der Instinkt der meisten Menschen ist: „Ich habe Einbettungen, additionally brauche ich eine Vektordatenbank“, aber halten Sie einen Second inne und denken Sie darüber nach, was tatsächlich hinter dieser Abstraktion passiert. Wenn Sie Einbettungen an eine Vektor-Datenbank senden, werden diese nicht nur „gespeichert“. Es erstellt einen Index, der die Suche nach Ähnlichkeiten beschleunigt. Von dieser Indexierungsarbeit kommt ein Großteil der Magie, aber auch ein Großteil der damit verbundenen Kosten.
In einer langlebigen, großen Wissensdatenbank ist dieser Kompromiss absolut sinnvoll: Sie zahlen einmalig (oder inkrementell, wenn sich Daten ändern) die Indizierungskosten und verteilen diese Kosten dann auf Millionen von Abfragen. In unserem Foo-Beispiel ist das nicht der Fall. Wir machen das Gegenteil: Wir fügen ständig kleine, einmalige Chargen von Einbettungen hinzu, beantworten eine winzige Anzahl von Anfragen professional Cost und werfen dann alles weg.
Die eigentliche Frage lautet additionally nicht: „Soll ich eine Vektordatenbank verwenden?“ Aber „Lohnt sich die Indexierungsarbeit?„Um das zu beantworten, können wir uns einen einfachen Benchmark ansehen.
Benchmarking: No-Index-Abruf vs. indizierter Abruf

Dieser Abschnitt ist eher technischer Natur. Wir schauen uns den Python-Code an und erklären die zugrunde liegenden Algorithmen. Wenn die genauen Implementierungsdetails für Sie nicht related sind, können Sie gerne mit dem fortfahren Ergebnisse Abschnitt.
Wir wollen zwei Systeme vergleichen:
- Keine Indizierung überhaupt, behält Einbettungen einfach im Speicher und scannt sie direkt.
- Eine Vektordatenbankwobei wir im Voraus eine Indizierungskosten zahlen, um jede Abfrage schneller zu machen.
Betrachten Sie zunächst den Ansatz „Kein Vektor-DB“. Wenn eine Abfrage eingeht, berechnen wir Ähnlichkeiten zwischen der Abfrageeinbettung und allen gespeicherten Einbettungen und wählen dann das High-Ok aus. Das ist einfach Ok-Nächste Nachbarn ohne Index.
import numpy as np
def run_knn(embeddings: np.ndarray, query_embedding: np.ndarray, top_k: int) -> np.ndarray:
sims = embeddings @ query_embedding
return sims.argsort()(-top_k:)(::-1)
Der Code verwendet das Skalarprodukt als Proxy für Kosinusähnlichkeit (unter der Annahme normalisierter Vektoren) und sortiert die Ergebnisse, um die besten Übereinstimmungen zu finden. Es ist im wahrsten Sinne des Wortes einfach scannt alle Vektoren und wählt die nächstgelegenen aus.
Schauen wir uns nun an, was eine Vektor-DB normalerweise tut. Unter der Haube basieren die meisten Vektordatenbanken auf einer ungefährer Index des nächsten Nachbarn (ANN). ANN-Methoden tauschen ein wenig Genauigkeit gegen eine deutliche Steigerung der Suchgeschwindigkeit ein, und einer der am häufigsten verwendeten Algorithmen hierfür ist HNSW. Wir werden das verwenden hnswlib Bibliothek zur Simulation des Indexverhaltens.
import numpy as np
import hnswlib
def create_hnsw_index(embeddings: np.ndarray, num_dims: int) -> hnswlib.Index:
index = hnswlib.Index(house='cosine', dim=num_dims)
index.init_index(max_elements=embeddings.form(0))
index.add_items(embeddings)
return index
def query_hnsw(index: hnswlib.Index, query_embedding: np.ndarray, top_k: int) -> np.ndarray:
labels, distances = index.knn_query(query_embedding, ok=top_k)
return labels(0)
Um zu sehen, wo der Kompromiss landet, können wir einige zufällige Einbettungen generieren, sie normalisieren und messen, wie lange jeder Schritt dauert:
import time
import numpy as np
import hnswlib
from tqdm import tqdm
def run_benchmark(num_embeddings: int, num_dims: int, top_k: int, num_iterations: int) -> None:
print(f"Benchmarking with {num_embeddings} embeddings of dimension {num_dims}, retrieving top-{top_k} nearest neighbors.")
knn_times: listing(float) = ()
index_times: listing(float) = ()
hnsw_query_times: listing(float) = ()
for _ in tqdm(vary(num_iterations), desc="Working benchmark"):
embeddings = np.random.rand(num_embeddings, num_dims).astype('float32')
embeddings = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
query_embedding = np.random.rand(num_dims).astype('float32')
query_embedding = query_embedding / np.linalg.norm(query_embedding)
start_time = time.time()
run_knn(embeddings, query_embedding, top_k)
knn_times.append((time.time() - start_time) * 1e3)
start_time = time.time()
vector_db_index = create_hnsw_index(embeddings, num_dims)
index_times.append((time.time() - start_time) * 1e3)
start_time = time.time()
query_hnsw(vector_db_index, query_embedding, top_k)
hnsw_query_times.append((time.time() - start_time) * 1e3)
print(f"BENCHMARK RESULTS (averaged over {num_iterations} iterations)")
print(f"(Naive KNN) Common search time with out indexing: {np.imply(knn_times):.2f} ms")
print(f"(HNSW Index) Common index building time: {np.imply(index_times):.2f} ms")
print(f"(HNSW Index) Common question time with indexing: {np.imply(hnsw_query_times):.2f} ms")
run_benchmark(num_embeddings=50000, num_dims=1536, top_k=5, num_iterations=20)
Ergebnisse
In diesem Beispiel verwenden wir 50.000 Einbettungen mit 1.536 Dimensionen (entsprechend OpenAIs). text-embedding-3-small) und rufen Sie die High-5-Nachbarn ab. Die genauen Ergebnisse variieren je nach Konfiguration, aber das Muster, das uns wichtig ist, ist dasselbe.
Ich empfehle Ihnen, den Benchmark mit Ihren eigenen Zahlen durchzuführen. Dies ist der beste Weg, um zu sehen, wie sich die Kompromisse in Ihrem spezifischen Anwendungsfall auswirken.
Im Durchschnitt dauert die naive KNN-Suche 24,54 Millisekunden professional Abfrage. Die Erstellung des HNSW-Index für dieselben Einbettungen dauert etwa 277 Sekunden. Sobald der Index erstellt ist, dauert jede Abfrage etwa 0,47 Millisekunden.
Daraus können wir den Break-Even-Punkt abschätzen. Der Unterschied zwischen naivem KNN und indizierten Abfragen beträgt 24,07 ms professional Abfrage. Das bedeutet, dass Sie es brauchen 11.510 Abfragen, bevor die bei jeder Abfrage eingesparte Zeit den Zeitaufwand für die Indexerstellung ausgleicht.

Darüber hinaus liegt der Break-Even-Punkt auch bei unterschiedlichen Werten für die Anzahl der Einbettungen und High-k bei Tausenden von Abfragen und bleibt in einem recht engen Bereich. Es kommt nicht vor, dass sich die Indizierung bereits nach ein paar Dutzend Abfragen auszahlt.

Vergleichen Sie das nun mit dem Foo-Beispiel. Ein Benutzer lädt eine kleine Menge Dateien hoch und stellt ein paar Fragen, nicht Tausende. Das System erreicht nie den Punkt, an dem sich der Index auszahlt. Stattdessen verzögert der Indexierungsschritt lediglich den Second, in dem das System die erste Frage beantworten kann, und erhöht die betriebliche Komplexität.
Für diese Artwork von kurzlebigem, benutzerspezifischem Kontext ist der einfache In-Reminiscence-KNN-Ansatz nicht nur einfacher zu implementieren und zu betreiben, sondern auch durchgängig schneller.
Wenn In-Reminiscence-Speicher keine Possibility ist, weil das System verteilt ist oder weil wir den Standing des Benutzers einige Minuten lang beibehalten müssen, können wir a verwenden Schlüsselwertspeicher wie Redis. Wir können eine eindeutige Kennung für die Benutzeranfrage als Schlüssel und alle Einbettungen als Wert speichern.
Dadurch erhalten wir eine leichte Lösung mit geringer Komplexität, die für unseren Anwendungsfall intestine geeignet ist kurzlebige Kontexte mit wenigen Abfragen.
Beispiel aus der Praxis: Warum wir uns für einen Schlüsselwertspeicher entschieden haben

Bei PlanckWir beantworten versicherungstechnische Fragen zu Unternehmen. Eine typische Anfrage beginnt mit einem Firmennamen und einer Firmenadresse, die wir dann abrufen Echtzeitdaten Informationen zu diesem bestimmten Unternehmen, einschließlich seiner On-line-Präsenz, Registrierungen und anderen öffentlichen Aufzeichnungen. Diese Daten werden zu unserem Kontext und wir verwenden LLMs und Algorithmen, um darauf basierende Fragen zu beantworten.
Das Wichtigste ist, dass wir jedes Mal, wenn wir eine Anfrage erhalten, eine Generierung durchführen ein neuer Kontext. Wir verwenden vorhandene Daten nicht wieder, sie werden bei Bedarf abgerufen und bleiben für related höchstens ein paar Minuten.
Wenn Sie an den früheren Benchmark zurückdenken, sollte dieses Muster bereits Ihren „Dies ist kein Vektor-DB-Anwendungsfall“-Sensor auslösen.
Jedes Mal, wenn wir eine Anfrage erhalten, generieren wir neue Einbettungen für kurzlebige Daten, die wir wahrscheinlich nur ein paar hundert Mal abfragen werden. Die Indizierung dieser Einbettungen in einer Vektor-DB führt zu unnötiger Latenz. Im Gegensatz dazu können wir mit Redis Bewahren Sie die Einbettungen sofort auf Und Führen Sie eine schnelle Ähnlichkeitssuche im Anwendungscode durch nahezu ohne Indexierungsverzögerung.
Deshalb haben wir uns entschieden Redis anstelle einer Vektordatenbank. Während Vektor-DBs hervorragend dazu geeignet sind, große Mengen an Einbettungen zu verarbeiten und schnelle Abfragen nach dem nächsten Nachbarn zu unterstützen, führen sie ein Indizierungsaufwandund in unserem Fall lohnt sich dieser Mehraufwand nicht.
Abschließend
Wenn Sie Millionen von Einbettungen speichern und Arbeitslasten mit hohen Abfragen in einem gemeinsamen Korpus unterstützen müssen, wäre eine Vektor-Datenbank die bessere Lösung. Und ja, es gibt definitiv Anwendungsfälle, die eine Vektor-Datenbank wirklich benötigen und von ihr profitieren.
Aber nur weil Sie Einbettungen verwenden oder Der Aufbau eines RAG-Programs bedeutet nicht, dass Sie standardmäßig eine Vektor-Datenbank verwenden sollten.
Jede Datenbanktechnologie hat ihre Stärken und Nachteile. Die beste Wahl beginnt mit einem tiefen Verständnis Ihrer Daten und Ihres Anwendungsfalls. anstatt gedankenlos dem Development zu folgen.
Wenn Sie additionally das nächste Mal eine Datenbank auswählen müssen, halten Sie einen Second inne und fragen Sie: Entscheide ich mich aufgrund objektiver Kompromisse für die richtige Datenbank oder entscheide ich mich einfach für die trendigste und glänzendste Wahl?
