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

BASH
pip install turbovec

Für die Integration mit Frameworks wie LangChain oder LlamaIndex kannst du die entsprechenden zusätzlichen Abhängigkeiten installieren:

BASH
# 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:

TOML
[dependencies]
turbovec = "0.1"

Schnellstart: Grundlegende Python-Nutzung

Index erstellen und Vektoren hinzufügen

PYTHON
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

PYTHON
# 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

PYTHON
# 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

PYTHON
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):

PYTHON
# 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

PYTHON
# Speichern
index.write("my_index.tvim")

# Laden
loaded_index = IdMapIndex.load("my_index.tvim")

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:

PYTHON
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

PYTHON
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

PYTHON
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

PYTHON
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

RUST
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

RUST
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

BASH
pip install turbovec sentence-transformers langchain-community langchain-openai

Vollständiger Code

PYTHON
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:

PYTHON
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

Paper: TurboQuant: Data-Oblivious Vector Quantization