TurboVec とは?

TurboVec は、Rust で書かれ、Python バインディングを提供する高性能ベクトルインデックスライブラリです。Google Research の TurboQuant 量子化アルゴリズムを基盤としています。その核心目標は、RAG(検索拡張生成)システムにおける二つの大きな課題——メモリ使用量と検索速度——を解決することです。

なぜ TurboVec が必要なのか?

従来のベクトル検索シナリオでは、1000 万ドキュメントのコーパスがある場合、float32 形式でベクトルを保存するには約 31 GB の RAM が必要です。一方、TurboVec はデータ非依存量子化(data-oblivious quantization)技術により、同じデータセットをわずか 4 GB に圧縮します。メモリ使用量は 87% 削減され、検索速度は FAISS を超えます。

TurboVec vs FAISS

特徴 FAISS TurboVec
メモリ使用量 高い(float32 または PQ 量子化) 極めて低い(TurboQuant 量子化)
トレーニング必要 ✅ 必要 ❌ 不要、プラグ&プレイ
オンラインでのベクトル追加 ⚠️ インデックス再構築が必要 ✅ リアルタイム追加、再構築不要
フィルタリング検索 後処理が必要 ✅ カーネルレベルでサポート、性能劣化なし
SIMD 最適化 あり 手書き NEON (ARM) + AVX-512BW (x86)
純ローカル実行 ✅ ✅
Python バインディング ✅ ✅
Rust ネイティブサポート ❌ ✅

核心技術:TurboQuant アルゴリズム

TurboQuant は Google Research が 2025 年に提案した新しい量子化アルゴリズムで、主な特徴は以下の通りです:

  • データ非依存(Data-Oblivious):特定のデータセットに対するコードブックのトレーニングが不要。従来の PQ(Product Quantization)のトレーニングオーバーヘッドを回避
  • シャノン限界に接近:歪み度が理論的最適値に近づく
  • 柔軟なビット幅:2-bit、4-bit、8-bit など多様な量子化精度をサポートし、再現率とメモリ使用量のバランスを取れる

ベンチマークテストでは、TurboVec は ARM アーキテクチャで FAISS IndexPQFastScan より 12-20% 高速で、x86 アーキテクチャでは同等かやや優れています。


TurboVec のインストール

TurboVec は Python と Rust の両方の言語インターフェースを提供しています。

Python インストール

BASH
pip install turbovec

LangChain や LlamaIndex などのフレームワークと統合する場合は、対応する追加依存関係をインストールできます:

BASH
# LangChain 統合
pip install turbovec[langchain]

# LlamaIndex 統合
pip install turbovec[llama-index]

# Haystack 統合
pip install turbovec[haystack]

# Agno 統合
pip install turbovec[agno]

Rust インストール

Cargo.toml に依存関係を追加:

TOML
[dependencies]
turbovec = "0.1"

クイックスタート:Python 基本使い方

インデックス作成とベクトル追加

PYTHON
import numpy as np
from turbovec import TurboQuantIndex

# インデックス作成:次元 1536(OpenAI embedding のデフォルト次元)、4-bit 量子化
index = TurboQuantIndex(dim=1536, bit_width=4)

# サンプルベクトル生成(実際の使用では embedding ベクトルに置き換えてください)
vectors = np.random.rand(10000, 1536).astype(np.float32)

# ベクトルをインデックスに追加
index.add(vectors)

# さらにベクトルを追加可能。再構築不要
more_vectors = np.random.rand(5000, 1536).astype(np.float32)
index.add(more_vectors)

print(f"インデックスには {len(index)} 個のベクトルがあります")

検索の実行

PYTHON
# クエリベクトル生成
query = np.random.rand(1536).astype(np.float32)

# 最も類似した 10 個のベクトルを検索
scores, indices = index.search(query, k=10)

print("類似度スコア:", scores)
print("ベクトルインデックス:", indices)

永続化保存と読み込み

PYTHON
# インデックスをディスクに保存
index.write("my_index.tq")

# ディスクからインデックスを読み込み
loaded_index = TurboQuantIndex.load("my_index.tq")

# 読み込み成功を確認
scores, indices = loaded_index.search(query, k=10)

高度な機能その 1:外部 ID マッピングの使用

実際のアプリケーションでは、ベクトルインデックスをデータベース内のドキュメント ID と関連付ける必要があります。TurboVec はこのニーズに対応するため IdMapIndex を提供しています。

ID 付きベクトルの追加

PYTHON
import numpy as np
from turbovec import IdMapIndex

# 外部 ID をサポートするインデックスを作成
index = IdMapIndex(dim=1536, bit_width=4)

# 3 つのベクトルがあり、対応する外部 ID が 1001, 1002, 1003 と仮定
vectors = np.random.rand(3, 1536).astype(np.float32)
external_ids = np.array([1001, 1002, 1003], dtype=np.uint64)

# ベクトルと対応する外部 ID を追加
index.add_with_ids(vectors, external_ids)

# 検索結果は内部インデックスではなく外部 ID を返す
query = np.random.rand(1536).astype(np.float32)
scores, ids = index.search(query, k=10)

print("返された外部 ID:", ids)  # [1001, 1003, 1002, ...]

ベクトルの削除

IdMapIndex は外部 ID による直接削除をサポートしており、時間計算量は O(1) です:

PYTHON
# ID 1002 のベクトルを削除
index.remove(1002)

# 再度検索すると、1002 は結果に含まれない
scores, ids = index.search(query, k=10)
print("削除後の ID:", ids)  # 1002 を含まない

ID 付きインデックスの永続化

PYTHON
# 保存
index.write("my_index.tvim")

# 読み込み
loaded_index = IdMapIndex.load("my_index.tvim")

これは TurboVec の主要な亮点の一つです。従来のベクトルデータベースでは、検索結果を特定のテナントや時間範囲に制限したい場合、通常は大量の候補結果をまず検索し、その後アプリケーション層でフィルタリングを行います。これにより再現率の低下と性能の無駄が発生します。

TurboVec はカーネルレベルでフィルタリングをサポートしており、allowlist パラメータで許可された ID リストを渡すと、SIMD カーネルが計算プロセス中に許可されていないスロットを直接スキップします。

シナリオ:マルチテナント RAG システム

マルチテナントの RAG システムがあり、各テナントが自分のドキュメントにのみアクセスできると仮定しましょう:

PYTHON
import numpy as np
from turbovec import IdMapIndex

# インデックス作成
idx = IdMapIndex(dim=1536, bit_width=4)

# 10000 個のベクトルがあり、各ベクトルがドキュメント 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)

# データベースクエリのシミュレーション:テナント A のドキュメント ID リストを取得
# 実際のシナリオでは、PostgreSQL / MySQL からのクエリ結果になる
tenant_a_docs = np.array([1, 5, 10, 15, 20, 25, 30, 35, 40, 45], dtype=np.uint64)

# テナント A のドキュメント範囲内で検索
query = np.random.rand(1536).astype(np.float32)
scores, ids = idx.search(query, k=5, allowlist=tenant_a_docs)

print("テナント A の最も関連性の高いドキュメント:", ids)
# 出力は tenant_a_docs 内の ID のみを含む

性能上の利点

フィルタリングは SIMD カーネル内部で行われ、32 ベクトルブロック粒度のショートサーキットメカニズムを採用しています:

  • ブロック内に許可されたスロットが一つもない場合、そのブロック全体の LUT 参照とスコア計算をスキップ
  • ブロック内に部分的に許可されたスロットがある場合、許可されたスロットのみをスコアリング
  • 選択性が強いフィルタ条件(許可された ID が全体に占める割合が小さい)の場合、大部分の SIMD 計算オーバーヘッドを回避できる

出力結果の長さは min(k, len(allowlist)) になります。許可リストが k より小さい場合、不関連な結果で埋めるのではなく、正確に len(allowlist) 個の結果を返します。


主要 RAG フレームワークとの統合

TurboVec は LangChain、LlamaIndex、Haystack、Agno とシームレスに統合されており、インポート文を置き換えるだけで利用できます。

LangChain 統合

PYTHON
from langchain_community.vectorstores import TurboVec
from langchain_openai import OpenAIEmbeddings

# embeddings の初期化
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# TurboVec ベクトルストアを作成
vector_store = TurboVec.from_documents(
    documents=documents,  # Document リスト
    embedding=embeddings,
    bit_width=4  # 4-bit 量子化
)

# 類似性検索
results = vector_store.similarity_search("あなたの質問", k=5)

# 永続化
vector_store.save_local("turbovec_index")

# 読み込み
loaded_store = TurboVec.load_local("turbovec_index", embeddings)

LlamaIndex 統合

PYTHON
from llama_index.vector_stores.turbovec import TurboVecVectorStore
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# ドキュメント読み込み
documents = SimpleDirectoryReader("./data").load_data()

# TurboVec ベクトルストアを作成
vector_store = TurboVecVectorStore(dim=1536, bit_width=4)

# インデックス作成
index = VectorStoreIndex.from_documents(
    documents,
    vector_store=vector_store
)

# クエリエンジン
query_engine = index.as_query_engine()
response = query_engine.query("あなたの質問")
print(response)

Haystack 統合

PYTHON
from haystack_integrations.document_stores.turbovec import TurboVecDocumentStore
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack import Pipeline

# ドキュメントストアを作成
document_store = TurboVecDocumentStore(dim=768, bit_width=4)

# エンベッダー
embedder = SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2")

# パイプライン構築
pipeline = Pipeline()
pipeline.add_component("embedder", embedder)
# ... 他のコンポーネントを追加

Rust ネイティブでの使用

高性能バックエンドサービスを Rust で構築している場合、TurboVec のネイティブ Rust API を直接使用できます。

基本使い方

RUST
use turbovec::TurboQuantIndex;
use ndarray::Array2;

fn main() {
    // インデックス作成:1536 次元、4-bit 量子化
    let mut index = TurboQuantIndex::new(1536, 4);

    // ベクトルデータの準備(ここでは乱数を使用)
    let vectors = Array2::<f32>::random((10000, 1536), &mut rand::thread_rng());

    // ベクトル追加
    index.add(&vectors);

    // クエリベクトル準備
    let query = Array1::<f32>::random(1536, &mut rand::thread_rng());

    // 最も類似した 10 個のベクトルを検索
    let results = index.search(&query, 10);

    println!("Top 10 結果: {:?}", results);

    // 永続化
    index.write("index.tv").unwrap();

    // 読み込み
    let loaded = TurboQuantIndex::load("index.tv").unwrap();
}

外部 ID 付きインデックス

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!("返された外部 ID: {:?}", returned_ids);

    // 削除
    index.remove(1002);

    // 永続化
    index.write("index.tvim").unwrap();
    let loaded = IdMapIndex::load("index.tvim").unwrap();
}

性能ベンチマーク

公式が提供するベンチマークデータによると、TurboVec は異なるデータセットとビット幅で以下のようなパフォーマンスを示します:

再現率比較(TurboQuant vs FAISS IndexPQ)

テスト条件:100K ベクトル、k=64

データセット ビット幅 TurboVec R@1 FAISS R@1 優位性
GloVe d=200 4-bit +0.3 ポイント ベースライン TurboVec が上回る
OpenAI d=1536 2-bit +0.4 ポイント ベースライン TurboVec が上回る
OpenAI d=1536 4-bit +1.2 ポイント ベースライン TurboVec が上回る
OpenAI d=3072 4-bit +3.4 ポイント ベースライン TurboVec が大幅に上回る

すべてのテストで、両者は k=4 の時点で再現率 1.0 に収束します。

速度比較

  • ARM (NEON):TurboVec は FAISS IndexPQFastScan より 12-20% 高速
  • x86 (AVX-512BW):TurboVec は FAISS と同等かやや優れている

メモリ使用量

ベクトル数 次元 float32 メモリ TurboVec (4-bit) メモリ 削減
1000 万 1536 ~31 GB ~4 GB 87%
100 万 1536 ~3.1 GB ~400 MB 87%
10 万 1536 ~310 MB ~40 MB 87%

実践例:ローカル RAG システムの構築

以下は、TurboVec を使用してクラウドサービス不要の完全ローカル RAG システムを構築する完全な例です。

環境準備

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

完全なコード

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. 埋め込みモデルの読み込み(ローカル実行、API Key 不要)
print("埋め込みモデルを読み込んでいます...")
model = SentenceTransformer('all-MiniLM-L6-v2')  # 384 次元
dim = 384

# 2. ドキュメントの読み込みと分割
print("ドキュメントを読み込んでいます...")
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"{len(chunks)} 個のテキストチャンクに分割")

# 3. ベクトル生成とインデックス構築
print("ベクトルを生成し、インデックスを構築しています...")
index = IdMapIndex(dim=dim, bit_width=4)

texts = [chunk.page_content for chunk in chunks]
metadata_list = [chunk.metadata for chunk in chunks]

# バッチでベクトル生成
embeddings = model.encode(texts, show_progress_bar=True)

# ベクトルとメタデータを追加(インデックスを外部 ID として使用)
external_ids = np.arange(len(texts), dtype=np.uint64)
index.add_with_ids(embeddings.astype(np.float32), external_ids)

# インデックスを保存
index.write("rag_index.tvim")
print("インデックスを保存しました")

# 4. 検索関数
def search(query: str, k: int = 5):
    """最も関連性の高いテキストチャンクを検索"""
    # インデックスを読み込み
    idx = IdMapIndex.load("rag_index.tvim")

    # クエリベクトル生成
    query_embedding = model.encode([query])[0].astype(np.float32)

    # 検索
    scores, ids = idx.search(query_embedding, k=k)

    # 結果を返す
    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. 検索テスト
if __name__ == "__main__":
    query = "TurboVec とは何ですか?"
    results = search(query, k=3)

    print("\n=== 検索結果 ===")
    for i, result in enumerate(results, 1):
        print(f"\n[{i}] 類似度: {result['score']:.4f}")
        print(f"内容: {result['text'][:200]}...")

フィルタリング機能の追加

ドキュメントに分類タグがある場合、検索時にフィルタリングできます:

PYTHON
def search_with_filter(query: str, category: str, k: int = 5):
    """分類フィルタ付き検索"""
    idx = IdMapIndex.load("rag_index.tvim")

    # 指定された分類に属するドキュメント ID を特定
    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 []

    # クエリベクトル生成
    query_embedding = model.encode([query])[0].astype(np.float32)

    # フィルタ付き検索
    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

# 「技術」分類のドキュメントのみを検索
results = search_with_filter("インストール方法は?", category="技術", k=3)

よくある質問

Q1: TurboVec はどのようなシナリオに適していますか?

  • メモリ制約のある環境:限られた RAM で大規模ベクトルインデックスを保存する必要がある場合
  • プライバシー重視のシナリオ:データをローカルまたは VPC 外に出せない場合
  • 動的に成長するコーパス:頻繁に新しいベクトルを追加する必要があり、インデックス再構築のコストを負担できない場合
  • マルチテナント RAG:検索時に細かな権限フィルタリングが必要な場合

Q2: TurboVec はどのようなシナリオに適していませんか?

  • 超大規模分散検索:インデックスを複数マシンに分散させる必要がある場合、Milvus や Weaviate などの分散ベクトルデータベースを検討すべき
  • 複雑なメタデータフィルタリングが必要:TurboVec は現在 ID ベースのフィルタリングのみをサポートしており、複雑なメタデータクエリはアプリケーション層で処理する必要がある

Q3: bit_width はどのように選択すればよいですか?

  • 4-bit:推奨デフォルト値。再現率とメモリの間で良好なバランスを取る
  • 2-bit:極限の圧縮。再現率がやや低下するが、メモリが極度に制約されているシナリオに適する
  • 8-bit:最高精度。メモリ使用量は 4-bit の約 2 倍。再現率は float32 に近い

Q4: TurboVec と FAISS は同時に使用できますか?

はい、可能です。FAISS で粗召回(coarse search)を行い、TurboVec で精査(rerank)を行う、またはその逆もできます。両者の API 設計思想は異なり、補完的に使用できます。


まとめ

TurboVec は注目すべき新興ベクトル検索ライブラリです。TurboQuant アルゴリズムを通じて、メモリ使用量、検索速度、使いやすさの三つの側面で良好なバランスを実現しています:

  • ✅ メモリ 87% 削減:1000 万ベクトルが 31GB から 4GB に
  • ✅ FAISS を超える速度:ARM で 12-20% 高速、x86 で同等
  • ✅ トレーニング不要:プラグ&プレイ、オンラインでのベクトル追加をサポート
  • ✅ カーネルレベルのフィルタリング:マルチテナント RAG に理想的
  • ✅ エコシステム統合:LangChain、LlamaIndex、Haystack とシームレスに連携

RAG プロジェクトでメモリボトルネックに直面している場合、または純ローカルデプロイが必要な場合、TurboVec は試す価値があります。

プロジェクトアドレス: https://github.com/RyanCodrai/turbovec

論文: TurboQuant: Data-Oblivious Vector Quantization