Was ist TurboVec?
TurboVec ist eine Hochleistungs-Vektorindex-Bibliothek, die in Rust geschrieben ist und Python-Bindungen bereitstellt. Sie basiert auf dem TurboQuant-Quantisierungsalgorithmus von Google Research. Ihr Kernziel ist es, zwei Hauptprobleme in RAG-Systemen (Retrieval-Augmented Generation) zu lösen: Speicherverbrauch und Suchgeschwindigkeit.
Warum braucht man TurboVec?
In traditionellen Vektorsuch-Szenarien benötigt ein Korpus mit 10 Millionen Dokumenten, bei dem Vektoren im float32-Format gespeichert werden, etwa 31 GB RAM. TurboVec komprimiert denselben Datensatz durch datenunabhängige Quantisierung (data-oblivious quantization) auf nur 4 GB – eine Reduzierung des Speicherverbrauchs um 87 %, bei gleichzeitig schnellerer Suche als FAISS.
TurboVec vs. FAISS
| Merkmal | FAISS | TurboVec |
|---|---|---|
| Speicherverbrauch | Hoch (float32 oder PQ-Quantisierung) | Extrem niedrig (TurboQuant-Quantisierung) |
| Training erforderlich | ✅ Ja, Trainingsphase nötig | ❌ Kein Training, Plug-and-Play |
| Online-Vektoren hinzufügen | ⚠️ Index-Neuerstellung nötig | ✅ Echtzeit-Hinzufügen ohne Neuerstellung |
| Gefilterte Suche | Nachbearbeitung nötig | ✅ Kernel-Level-Unterstützung, kein Leistungsverlust |
| SIMD-Optimierung | Vorhanden | Handgeschriebenes NEON (ARM) + AVX-512BW (x86) |
| Rein lokaler Betrieb | ✅ | ✅ |
| Python-Bindungen | ✅ | ✅ |
| Native Rust-Unterstützung | ❌ | ✅ |
Kerntechnologie: Der TurboQuant-Algorithmus
TurboQuant ist ein neuartiger Quantisierungsalgorithmus, der 2025 von Google Research vorgestellt wurde. Zu seinen Hauptmerkmalen gehören:
- Datenunabhängig (Data-Oblivious): Kein Training eines Codebuchs für bestimmte Datensätze erforderlich – vermeidet den Trainingsaufwand herkömmlicher PQ (Product Quantization)
- Annäherung an die Shannon-Untergrenze: Liegt in der Verzerrung nahe am theoretischen Optimum
- Flexible Bitbreite: Unterstützt Quantisierungsgenauigkeiten von 2 Bit, 4 Bit und 8 Bit für eine ausgewogene Balance zwischen Recall und Speicherverbrauch
In Benchmarks ist TurboVec auf ARM-Architekturen 12–20 % schneller als FAISS IndexPQFastScan und auf x86-Architekturen gleichauf oder leicht überlegen.
Installation von TurboVec
TurboVec bietet Schnittstellen für Python und Rust.
Python-Installation
pip install turbovec
Für die Integration mit Frameworks wie LangChain oder LlamaIndex kannst du die entsprechenden zusätzlichen Abhängigkeiten installieren:
# LangChain-Integration
pip install turbovec[langchain]
# LlamaIndex-Integration
pip install turbovec[llama-index]
# Haystack-Integration
pip install turbovec[haystack]
# Agno-Integration
pip install turbovec[agno]
Rust-Installation
Füge die Abhängigkeit in deiner Cargo.toml hinzu:
[dependencies]
turbovec = "0.1"
Schnellstart: Grundlegende Python-Nutzung
Index erstellen und Vektoren hinzufügen
import numpy as np
from turbovec import TurboQuantIndex
# Index erstellen: Dimension 1536 (Standard-Dimension von OpenAI-Embeddings), 4-Bit-Quantisierung
index = TurboQuantIndex(dim=1536, bit_width=4)
# Beispiel-Vektoren generieren (im echten Einsatz durch deine Embedding-Vektoren ersetzen)
vectors = np.random.rand(10000, 1536).astype(np.float32)
# Vektoren zum Index hinzufügen
index.add(vectors)
# Weitere Vektoren hinzufügen – ohne Index-Neuerstellung
more_vectors = np.random.rand(5000, 1536).astype(np.float32)
index.add(more_vectors)
print(f"Der Index enthält {len(index)} Vektoren")
Suche durchführen
# Abfragevektor generieren
query = np.random.rand(1536).astype(np.float32)
# Die 10 ähnlichsten Vektoren suchen
scores, indices = index.search(query, k=10)
print("Ähnlichkeitswerte:", scores)
print("Vektor-Indizes:", indices)
Persistenz: Speichern und Laden
# Index auf Disk speichern
index.write("my_index.tq")
# Index von Disk laden
loaded_index = TurboQuantIndex.load("my_index.tq")
# Erfolgreiches Laden überprüfen
scores, indices = loaded_index.search(query, k=10)
Erweiterte Funktion 1: Verwenden einer externen ID-Zuordnung
In der Praxis musst du den Vektor-Index oft mit den Dokument-IDs in deiner Datenbank verknüpfen. TurboVec bietet hierfür IdMapIndex.
Vektoren mit IDs hinzufügen
import numpy as np
from turbovec import IdMapIndex
# Index mit Unterstützung für externe IDs erstellen
index = IdMapIndex(dim=1536, bit_width=4)
# Angenommen, du hast 3 Vektoren mit den externen IDs 1001, 1002, 1003
vectors = np.random.rand(3, 1536).astype(np.float32)
external_ids = np.array([1001, 1002, 1003], dtype=np.uint64)
# Vektoren mit ihren externen IDs hinzufügen
index.add_with_ids(vectors, external_ids)
# Die Suche gibt externe IDs zurück, nicht die internen Indizes
query = np.random.rand(1536).astype(np.float32)
scores, ids = index.search(query, k=10)
print("Zurückgegebene externe IDs:", ids) # [1001, 1003, 1002, ...]
Vektoren löschen
IdMapIndex unterstützt das direkte Löschen von Vektoren über ihre externe ID mit einer Zeitkomplexität von O(1):
# Vektor mit ID 1002 löschen
index.remove(1002)
# Bei erneuter Suche taucht 1002 nicht mehr in den Ergebnissen auf
scores, ids = index.search(query, k=10)
print("IDs nach dem Löschen:", ids) # Enthält 1002 nicht mehr
Persistenz des Index mit IDs
# Speichern
index.write("my_index.tvim")
# Laden
loaded_index = IdMapIndex.load("my_index.tvim")
Erweiterte Funktion 2: Gefilterte Suche (Filter-at-Search)
Dies ist eines der Haupt-Highlights von TurboVec. In traditionellen Vektor-Datenbanken musst du, wenn du die Suchergebnisse auf einen bestimmten Mandanten oder Zeitraum beschränken möchtest, zunächst eine große Anzahl von Kandidaten durchsuchen und dann auf der Anwendungsebene filtern – das führt zu vermindertem Recall und Leistungsverschwendung.
TurboVec unterstützt Filterung auf Kernel-Ebene. Über den Parameter allowlist übergibst du eine Liste erlaubter IDs, und die SIMD-Kernel überspringen direkt während der Berechnung nicht erlaubte Slots.
Szenario: Multi-Tenant-RAG-System
Angenommen, du hast ein Multi-Tenant-RAG-System, bei dem jeder Mandant nur auf seine eigenen Dokumente zugreifen kann:
import numpy as np
from turbovec import IdMapIndex
# Index erstellen
idx = IdMapIndex(dim=1536, bit_width=4)
# Angenommen, es gibt 10000 Vektoren, jeder entspricht einer Dokument-ID
vectors = np.random.rand(10000, 1536).astype(np.float32)
doc_ids = np.arange(1, 10001, dtype=np.uint64)
idx.add_with_ids(vectors, doc_ids)
# Datenbankabfrage simulieren: Dokument-ID-Liste für Mandant A abrufen
# In der Praxis käme dies von PostgreSQL / MySQL
tenant_a_docs = np.array([1, 5, 10, 15, 20, 25, 30, 35, 40, 45], dtype=np.uint64)
# Innerhalb des Dokumentenbereichs von Mandant A suchen
query = np.random.rand(1536).astype(np.float32)
scores, ids = idx.search(query, k=5, allowlist=tenant_a_docs)
print("Die relevantesten Dokumente für Mandant A:", ids)
# Die Ausgabe enthält nur IDs aus tenant_a_docs
Leistungsvorteile
Die Filterung erfolgt innerhalb der SIMD-Kernel mit einer Shortcut-Mechanismus auf Blockebene von 32 Vektoren:
- Enthält ein Block keine erlaubten Slots, wird die gesamte LUT-Suche und Bewertungsberechnung für diesen Block übersprungen
- Enthält ein Block teilweise erlaubte Slots, werden nur die erlaubten Slots bewertet
- Bei sehr selektiven Filterbedingungen (erlaubte IDs machen nur einen kleinen Anteil aus) werden die meisten SIMD-Berechnungskosten vermieden
Die Ergebnislänge beträgt min(k, len(allowlist)). Wenn die erlaubte Liste kleiner als k ist, werden genau len(allowlist) Ergebnisse zurückgegeben, ohne Auffüllen mit irrelevanten Treffern.
Integration mit den wichtigsten RAG-Frameworks
TurboVec bietet nahtlose Integration mit LangChain, LlamaIndex, Haystack und Agno – du musst lediglich die Import-Anweisung austauschen.
LangChain-Integration
from langchain_community.vectorstores import TurboVec
from langchain_openai import OpenAIEmbeddings
# Embeddings initialisieren
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# TurboVec-Vektorspeicher erstellen
vector_store = TurboVec.from_documents(
documents=documents, # Deine Dokumentenliste
embedding=embeddings,
bit_width=4 # 4-Bit-Quantisierung
)
# Ähnlichkeitssuche
results = vector_store.similarity_search("Deine Frage", k=5)
# Persistieren
vector_store.save_local("turbovec_index")
# Laden
loaded_store = TurboVec.load_local("turbovec_index", embeddings)
LlamaIndex-Integration
from llama_index.vector_stores.turbovec import TurboVecVectorStore
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
# Dokumente laden
documents = SimpleDirectoryReader("./data").load_data()
# TurboVec-Vektorspeicher erstellen
vector_store = TurboVecVectorStore(dim=1536, bit_width=4)
# Index erstellen
index = VectorStoreIndex.from_documents(
documents,
vector_store=vector_store
)
# Abfrage-Engine
query_engine = index.as_query_engine()
response = query_engine.query("Deine Frage")
print(response)
Haystack-Integration
from haystack_integrations.document_stores.turbovec import TurboVecDocumentStore
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack import Pipeline
# Dokumentenspeicher erstellen
document_store = TurboVecDocumentStore(dim=768, bit_width=4)
# Embedder
embedder = SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")
# Pipeline aufbauen
pipeline = Pipeline()
pipeline.add_component("embedder", embedder)
# ... weitere Komponenten hinzufügen
Native Rust-Nutzung
Wenn du mit Rust einen leistungsstarken Backend-Dienst entwickelst, kannst du direkt die nativen Rust-APIs von TurboVec verwenden.
Grundlegende Nutzung
use turbovec::TurboQuantIndex;
use ndarray::Array2;
fn main() {
// Index erstellen: 1536 Dimensionen, 4-Bit-Quantisierung
let mut index = TurboQuantIndex::new(1536, 4);
// Vektordaten vorbereiten (hier mit Zufallszahlen als Beispiel)
let vectors = Array2::<f32>::random((10000, 1536), &mut rand::thread_rng());
// Vektoren hinzufügen
index.add(&vectors);
// Abfragevektor vorbereiten
let query = Array1::<f32>::random(1536, &mut rand::thread_rng());
// Die 10 ähnlichsten Vektoren suchen
let results = index.search(&query, 10);
println!("Top 10 Ergebnisse: {:?}", results);
// Persistieren
index.write("index.tv").unwrap();
// Laden
let loaded = TurboQuantIndex::load("index.tv").unwrap();
}
Index mit externen IDs
use turbovec::IdMapIndex;
fn main() {
let mut index = IdMapIndex::new(1536, 4);
let vectors = Array2::<f32>::random((100, 1536), &mut rand::thread_rng());
let ids = vec![1001u64, 1002, 1003, /* ... */];
index.add_with_ids(&vectors, &ids);
let query = Array1::<f32>::random(1536, &mut rand::thread_rng());
let (scores, returned_ids) = index.search(&query, 10);
println!("Zurückgegebene externe IDs: {:?}", returned_ids);
// Löschen
index.remove(1002);
// Persistieren
index.write("index.tvim").unwrap();
let loaded = IdMapIndex::load("index.tvim").unwrap();
}
Leistungs-Benchmarks
Laut den offiziellen Benchmark-Daten zeigt TurboVec bei verschiedenen Datensätzen und Bitbreiten folgende Leistung:
Recall-Vergleich (TurboQuant vs. FAISS IndexPQ)
Testbedingungen: 100.000 Vektoren, k=64
| Datensatz | Bitbreite | TurboVec R@1 | FAISS R@1 | Vorteil |
|---|---|---|---|---|
| GloVe d=200 | 4-Bit | +0,3 Pkt. | Basis | TurboVec höher |
| OpenAI d=1536 | 2-Bit | +0,4 Pkt. | Basis | TurboVec höher |
| OpenAI d=1536 | 4-Bit | +1,2 Pkt. | Basis | TurboVec höher |
| OpenAI d=3072 | 4-Bit | +3,4 Pkt. | Basis | TurboVec deutlich höher |
In allen Tests konvergierten beide bei k=4 zu einem Recall von 1,0.
Geschwindigkeitsvergleich
- ARM (NEON): TurboVec ist 12–20 % schneller als FAISS IndexPQFastScan
- x86 (AVX-512BW): TurboVec ist gleichauf mit FAISS oder leicht überlegen
Speicherverbrauch
| Vektoren | Dimensionen | float32-Speicher | TurboVec (4-Bit) Speicher | Ersparnis |
|---|---|---|---|---|
| 10 Mio. | 1536 | ~31 GB | ~4 GB | 87 % |
| 1 Mio. | 1536 | ~3,1 GB | ~400 MB | 87 % |
| 100.000 | 1536 | ~310 MB | ~40 MB | 87 % |
Praxisbeispiel: Ein lokales RAG-System erstellen
Hier ist ein vollständiges Beispiel, das zeigt, wie du mit TurboVec ein vollständig lokales RAG-System ohne Cloud-Dienste aufbaust.
Umgebungsvorbereitung
pip install turbovec sentence-transformers langchain-community langchain-openai
Vollständiger Code
import numpy as np
from turbovec import IdMapIndex
from sentence_transformers import SentenceTransformer
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. Embedding-Modell laden (lokal, kein API-Key erforderlich)
print("Lade Embedding-Modell...")
model = SentenceTransformer('all-MiniLM-L6-v2') # 384 Dimensionen
dim = 384
# 2. Dokumente laden und aufteilen
print("Lade Dokumente...")
loader = TextLoader('./data/my_documents.txt', encoding='utf-8')
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(documents)
print(f"Aufgeteilt in {len(chunks)} Textblöcke")
# 3. Vektoren generieren und Index erstellen
print("Generiere Vektoren und erstelle Index...")
index = IdMapIndex(dim=dim, bit_width=4)
texts = [chunk.page_content for chunk in chunks]
metadata_list = [chunk.metadata for chunk in chunks]
# Batch-Vektorgenerierung
embeddings = model.encode(texts, show_progress_bar=True)
# Vektoren und Metadaten hinzufügen (Index als externe ID verwenden)
external_ids = np.arange(len(texts), dtype=np.uint64)
index.add_with_ids(embeddings.astype(np.float32), external_ids)
# Index speichern
index.write("rag_index.tvim")
print("Index gespeichert")
# 4. Suchfunktion
def search(query: str, k: int = 5):
"""Suche nach den relevantesten Textblöcken"""
# Index laden
idx = IdMapIndex.load("rag_index.tvim")
# Abfragevektor generieren
query_embedding = model.encode([query])[0].astype(np.float32)
# Suche
scores, ids = idx.search(query_embedding, k=k)
# Ergebnisse zurückgeben
results = []
for score, doc_id in zip(scores, ids):
doc_id = int(doc_id)
results.append({
'text': texts[doc_id],
'score': float(score),
'metadata': metadata_list[doc_id]
})
return results
# 5. Suche testen
if __name__ == "__main__":
query = "Was ist TurboVec?"
results = search(query, k=3)
print("\n=== Suchergebnisse ===")
for i, result in enumerate(results, 1):
print(f"\n[{i}] Ähnlichkeit: {result['score']:.4f}")
print(f"Inhalt: {result['text'][:200]}...")
Filterfunktion hinzufügen
Falls deine Dokumente mit Kategorien versehen sind, kannst du bei der Suche filtern:
def search_with_filter(query: str, category: str, k: int = 5):
"""Suche mit Kategoriefilter"""
idx = IdMapIndex.load("rag_index.tvim")
# IDs ermitteln, die zur angegebenen Kategorie gehören
allowed_ids = np.array([
i for i, meta in enumerate(metadata_list)
if meta.get('category') == category
], dtype=np.uint64)
if len(allowed_ids) == 0:
return []
# Abfragevektor generieren
query_embedding = model.encode([query])[0].astype(np.float32)
# Suche mit Filter
scores, ids = idx.search(query_embedding, k=k, allowlist=allowed_ids)
results = []
for score, doc_id in zip(scores, ids):
doc_id = int(doc_id)
results.append({
'text': texts[doc_id],
'score': float(score),
'metadata': metadata_list[doc_id]
})
return results
# Nur Dokumente der Kategorie "Technik" durchsuchen
results = search_with_filter("Wie installiere ich?", category="Technik", k=3)
Häufig gestellte Fragen
F1: Für welche Anwendungen eignet sich TurboVec?
- Speicherbeschränkte Umgebungen: Wenn du große Vektor-Indizes in begrenztem RAM speichern musst
- Datenschutzsensible Szenarien: Wenn Daten das lokale Netzwerk oder die VPC nicht verlassen dürfen
- Dynamisch wachsende Korpora: Wenn du häufig neue Vektoren hinzufügen musst, ohne den Index neu aufbauen zu können
- Multi-Tenant-RAG: Wenn du bei der Suche feingranulare Berechtigungsfilter benötigst
F2: Wofür eignet sich TurboVec nicht?
- Hochskalierbare verteilte Suche: Wenn der Index auf mehrere Maschinen verteilt werden muss, sind verteilte Vektor-Datenbanken wie Milvus oder Weaviate besser geeignet
- Komplexe Metadatenfilter: TurboVec unterstützt derzeit nur ID-basierte Filterung; komplexe Metadatenabfragen müssen auf der Anwendungsebene verarbeitet werden
F3: Wie wählt man die Bitbreite (bit_width)?
- 4-Bit: Empfohlener Standardwert – gute Balance zwischen Recall und Speicherverbrauch
- 2-Bit: Maximale Komprimierung mit leicht reduziertem Recall – geeignet für extrem speicherbeschränkte Szenarien
- 8-Bit: Höchste Genauigkeit, etwa doppelter Speicherverbrauch im Vergleich zu 4-Bit, Recall nahe float32
F4: Können TurboVec und FAISS gleichzeitig verwendet werden?
Ja. Du kannst FAISS für die Grobsuche (coarse search) und TurboVec für die Feinsortierung (rerank) verwenden – oder umgekehrt. Die API-Entwürfe beider Bibliotheken sind unterschiedlich und können sich ergänzen.
Zusammenfassung
TurboVec ist eine vielversprechende neue Vektorsuchbibliothek, die durch den TurboQuant-Algorithmus eine ausgezeichnete Balance zwischen Speicherverbrauch, Suchgeschwindigkeit und Benutzerfreundlichkeit bietet:
- ✅ 87 % weniger Speicher: 10 Millionen Vektoren von 31 GB auf 4 GB reduziert
- ✅ Schneller als FAISS: Auf ARM 12–20 % schneller, auf x86 gleichauf
- ✅ Kein Training nötig: Plug-and-Play mit Online-Vektorerweiterung
- ✅ Kernel-Level-Filterung: Ideale Wahl für Multi-Tenant-RAG
- ✅ Ökosystem-Integration: Nahtlose Anbindung an LangChain, LlamaIndex, Haystack
Wenn dein RAG-Projekt unter Speicherengpässen leidet oder eine rein lokale Bereitstellung erfordert, ist TurboVec einen Versuch wert.
Projektseite: https://github.com/RyanCodrai/turbovec