HN 日本語サマリー

← 一覧へ戻る
AI・機械学習

Turbovec – Rustにおけるベクトル検索のためのGoogleのTurboQuant

Turbovec – Google's TurboQuant for vector search in Rust (github.com)

282 pointsby fittingopposite32 コメント

要約

Turbovecは、GoogleのTurboQuantアルゴリズムを基盤としたRust製のベクトル検索ライブラリです。このライブラリは、大規模なドキュメントコーパスを大幅に圧縮し、FAISSよりも高速な検索性能を実現します。オンラインでのベクトル追加や、CPUアーキテクチャに最適化されたSIMD検索、差分のみを保存するインクリメンタルセーブなどの機能を備えています。

全文翻訳

1000万件のドキュメントコーパスはfloat32で31GBのRAMを消費しますが、turbovecはそれを4GBに収め、FAISSよりも高速に検索します。turbovecはPythonバインディングを持つRust製のベクトルインデックスで、Google ResearchのTurboQuantアルゴリズムに基づいています。これはデータ非依存の量子化器で、ほぼ最適な歪みと、個別の学習フェーズなしで動作します。 オンラインインジェスト。ベクトルを追加すると、インデックスが作成されます。学習ステップ、パラメータチューニング、コーパスの成長に伴う再構築は不要です。 高速SIMD検索。手書きのカーネル(ARMではNEON SDOT/SMMLA、x86ではAVX-512 VNNIとvpermb、AVX2とスカラーフォールバックあり)は、測定されたすべての構成でFAISS IndexPQFastScanを上回り、両アーキテクチャで4ビットでは平均3.4倍、2ビットでは平均23%高速です。 インクリメンタルセーブ。sync(path)は、前回のsync以降に変更された部分のみを永続化します。呼び出しごとに1回のfsyncで、どのバイト数でもクラッシュセーフであり、削除や小さな追加はインデックスのサイズに関わらずミリ秒単位で完了します。write/loadは、ファイル全体のスナップショットのために残されています。 検索時のフィルタリング。検索()にIDの許可リスト(またはスロットビットマスク)を渡すと、カーネルが直接それを処理します。常に許可されたセットから最大k個の結果が得られます。過剰な取得はなく、選択的なフィルタでのリコールヒットもありません。 純粋なローカル。マネージドサービスはなく、データはあなたのマシンやVPCから離れません。プライバシー、メモリ、またはレイテンシが重要なRAGを構築していますか?ここはあなたのための場所です。 Python pip install turbovec from turbovec import TurboQuantIndex index = TurboQuantIndex(dim=1536, bit_width=4) index.add(vectors) index.add(more_vectors) scores, indices = index.search(query, k=10) index.write("my_index.tv") loaded = TurboQuantIndex.load("my_index.tv") index.sync("my_index.tv") # さらに変更後: 耐久性のあるインクリメンタルセーブ vectorsとqueryは形状(n, dim)の2次元float32配列です。他のdtypeはサイレントに変換されるのではなく拒否されるため、必要に応じてまずnp.asarray(x, dtype=np.float32)でキャストしてください。 削除に耐える安定したIDが必要ですか?IdMapIndexを使用してください: import numpy as np from turbovec import IdMapIndex index = IdMapIndex(dim=1536, bit_width=4) index.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64)) scores, ids = index.search(query, k=10) # idsはあなたのuint64外部IDです index.remove(1002) # IDあたりO(1) index.write("my_index.tvim") loaded = IdMapIndex.load("my_index.tvim") index.sync("my_index.tvim") # IDを含む耐久性のあるインクリメンタルセーブ ハイブリッド検索(フィルタリングされた検索) 別のシステム(SQL、BM25、ACL、時間ウィンドウなど)によって生成された候補セットに結果を制限します: import numpy as np from turbovec import IdMapIndex idx = IdMapIndex(dim=1536, bit_width=4) idx.add_with_ids(vectors, ids) # ステージ1: 外部システムが候補IDに絞り込みます。 allowed = np.array(db.execute("SELECT id FROM docs WHERE tenant=?", (t,)).fetchall(), dtype=np.uint64) # ステージ2: 候補セット内での高密度リランク。 scores, ids = idx.search(query, k=10, allowlist=allowed) フィルタリングは、32ベクトルブロックの粒度でSIMDカーネル内で行われます。許可されたスロットがないブロックは、LUTルックアップやスコアリング作業の前にショートサーキットされ、スコアリングされたブロック内の個々の非許可スロットはヒープ挿入時にドロップされます。したがって、選択的な許可リスト(インデックスの小さな割合が許可される)は、結果を支払い破棄するのではなく、ほとんどのSIMDコストを回避します。出力長はmin(k, n_allowed)であり、n_allowedは一意の許可されたベクトルの数をカウントします。許可されたベクトルがkより少ない場合、パディングされたフォールバックではなく、その数だけ結果が得られます。完全なリファレンスについては、docs/api.mdを参照してください。 フレームワーク統合 各フレームワークのインツリー参照ベクトル/ドキュメントストアのドロップインリプレイスメントです。同じ公開サーフェス、同じ永続化セマンティクス、同じリトリーバーとパイプライン配線。インポートを切り替えて、パイプラインを維持してください。 LangChain — pip install turbovec[langchain] · langchain_core.vectorstores.InMemoryVectorStore を置き換えます LlamaIndex — pip install turbovec[llama-index] · llama_index.core.vector_stores.SimpleVectorStore を置き換えます Haystack — pip install turbovec[haystack] · haystack.document_stores.in_memory.InMemoryDocumentStore を置き換えます Agno — pip install turbovec[agno] · agno.vectordb.lancedb.LanceDb を置き換えます Rust cargo add turbovec use turbovec::TurboQuantIndex; let mut index = TurboQuantIndex::new(1536, 4).unwrap(); index.add(&vectors); let results = index.search(&queries, 10); index.write("index.tv").unwrap(); let loaded = TurboQuantIndex::load("index.tv").unwrap(); 削除に耐える安定した外部IDの場合: use turbovec::IdMapIndex; let mut index = IdMapIndex::new(1536, 4).unwrap(); index.add_with_ids(&vectors, &[1001, 1002, 1003]).unwrap(); let (scores, ids) = index.search(&queries, 10); index.remove(1002); index.write("index.tvim").unwrap(); let loaded = IdMapIndex::load("index.tvim").unwrap(); リコール TurboQuant vs FAISS IndexPQ (LUT256, nbits=8) — ペーパーのセクション4.4のベースライン。10万ベクトル、k=64。FAISS PQサブ量子化器数は、TurboQuantのビットレート(2ビットでm=d/4、4ビットでm=d/2)に合わせられています。チャートは、キャリブレーションされたTurboQuant(TQ+)をプロットしています。OpenAI d=1536およびd=3072全体で、TQ+は4つのセルのうち3つでR@1でFAISSを上回り(0.9〜2.9ポイント差、d=1536 4ビットは0.7ポイント差)、両方ともk=8で1.0に達します(k≤4で既に≥0.997)。GloVe d=200はより難しいレジームです。低次元では、漸近ベータ仮説は緩いです。TQ+は、4ビットで+1.9、2ビットで+0.8ポイント、R@1でFAISSを上回ります。FAISSはk≈8から2ビットでわずかに優位を保ちます。キャリブレーションされていない数値はJSON(tq_recalls)にあります。 ベースラインに関する注記。FAISS IndexPQ(LUT256、nbits=8、float32 LUT)と比較します。これは、ほとんどのユーザーが使用するデフォルトのプロダクショングレードPQであるためです。これは、TurboQuantペーパーのカスタムu8-LUT PQよりも強力なベースラインです。FAISSは、スコアリング時に高精度LUTを使用し、コードブック学習にk-means++を使用します。OpenAI d=1536 / d=3072でペーパーのTurboQuant数値を再現し、低次元埋め込み(d=384でのturboquant-pyを参照)で他のコミュニティ参照実装と同様の数値を達成しています。GloVe(d=200)では、漸近ベータ仮説が最も緩い低次元レジームで、TurboQuantは4ビットでFAISSを上回りますが、2ビットでは下回ります。TQ+キャリブレーションは、R@1(0.572 vs FAISSの0.564)で2ビットの赤字を回復し、FAISSは深いkでわずかに優位を保ちます。完全な結果:d=1536 2ビット、d=1536 4ビット、d=3072 2ビット、d=3072 4ビット、GloVe 2ビット、GloVe 4ビット。 圧縮 検索速度 すべてのベンチマーク:10万ベクトル、1000クエリ、k=64、5回の実行の中央値。 ARM(GCP c4a-standard-8、Google Axion、8 vCPU) ARMでは、TurboQuantはFAISS FastScanをすべての構成で上回り、4ビットでは平均3.5倍(セル全体で3.4〜3.7倍 — SDOT/SMMLAドット積カーネルはベクトルメジャーレイアウトを直接スコアリングします)、2ビットでは平均26%(22〜29%)高速です。 x86(Intel Xeon Platinum 8481C / Sapphire Rapids、8 vCPU) x86では、TurboQuantはすべての構成で勝利し、4ビットでは平均3.4倍(セル全体で3.2〜3.5倍 — ベクトルメジャーレイアウト上のAVX-512 VNNIドット積カーネル)、2ビットでは平均20%(5〜32%)高速です。ここではvpermb LUTスキャンが短い2ビット累積ループを処理します。 挿入と削除のレイテンシ 検索セルと同じコーパス:10万OpenAIベクトル、5回の実行の中央値、呼び出し元がオペレーションごとに実際に支払うPython呼び出しオーバーヘッドを含むタイミングループ。 挿入は、ウォームでポピュレートされたインデックス(学習は時間計測なし)へのベクトルごとのadd()レイテンシを測定します。n=1 — 単一ベクトルのadd() — およびn=100 — 100ベクトルのバッチ。バッチ処理が呼び出しごとのオーバーヘッドをどの程度償却するかを示します。学習済みでポピュレートされたFAISS IndexPQFastScanへのadd()(学習は時間計測なし)と比較します。単一のadd()は、セルに応じて6.3〜19.7マイクロ秒(FAISSの単一addより7.6〜13.9倍高速)で完了し、100ベクトルのバッチはTurboQuantをベクトルあたり4.6〜16.3マイクロ秒(FAISSへの同じバッチより4.6〜15.1倍高速)に償却します。 削除は、n=1(1000回の削除にわたる定常的なオペレーションあたりのレート)およびn=100(新規インデックスでの最初の100回の削除)でのIDによる削除オペレーションあたりのレイテンシを測定します。IdMapIndex.remove(id) — O(1)のスワップアンドポップとIDマップのブックキーピング — は、セル全体でオペレーションあたり0.44〜1.22マイクロ秒および0.59〜1.37マイクロ秒で完了します。