バッチ検索と並列処理

1. なぜ「バッチ検索」が重要か

FAISS は 単一クエリよりもクエリをまとめて(batched)検索することを前提に最適化されています。

  • 多くのインデックス(特に IVF 系)は検索時に クラスタセンター行列 × クエリ行列 の形で BLAS の matrix–matrix 演算を呼び出します。行ベクトル(単一クエリ)の matrix–vector 演算を繰り返すよりも、BLAS が内部で SIMD やキャッシュを最大限活用できるため スループットとキャッシュ局所性が大幅に向上します。github.comarxiv.org

  • GPU では cuBLAS/Tensor Cores を利用した gemm がバッチサイズ依存でスケールし、1000 クエリ以上をまとめるとレイテンシはほとんど伸びずに QPS が線形増加します。

  • ベストプラクティス

    • CPU: 数百〜数千クエリを index.search(xq, k) に渡す。

    • GPU: 1024 × dim のクエリチャンクを目安にし、GpuResources.setTempMemory() で十分なワークスペースを確保。

    • 1 クエリのみの場合は、クエリを複製してでも最小バッチ幅を確保する方が速いことが多い。

2. CPU 上の並列処理

FAISS は OpenMP による内部スレッド化を行い、クエリバッチをスレッド間で自動分割します。

  • スレッド数は faiss::omp_set_num_threads(n) または環境変数 OMP_NUM_THREADS で設定。

  • デフォルトでは「クエリ次元 × nprobe」のループが #pragma omp parallel for で分散されるため、外側で Python の concurrent.futures などを同時に使っても追加の利得はほぼありませんmedium.com

  • M × N 探索では L2Flat / IPFlat の距離計算が基本的に メモリ帯域律速になるため、スレッド数を無闇に増やすとキャッシュ競合・メモリ飽和で逆効果になることもあります。コア数の 1 〜 1.5 倍程度が上限の目安です。

3. GPU 上の並列処理

3-1. 1 GPU 内の並列

  • StandardGpuResources には複数ストリーム(既定 8 本)が設定されており、データ転送・距離計算・再ランキングをストリーム間でパイプライン実行します。

  • ワークスペース不足時は一時メモリをホスト⇔デバイス間でプールしますが、零コピーを活かすには ピン留めメモリを確保すると良いです。

3-2. 複数 GPU の活用

FAISS は 二つのマルチ GPU インデックスを提供します。

インデックス 特徴 適用シナリオ スケール特性
GpuIndexReplicas 同一データを各 GPU に複製 高レイテンシ許容、リコール重視 8 GPU で 6–7×(線形に近い)github.com
GpuIndexShards データを水平分割 メモリ節約、超大規模コーパス サブ線形(通信コストあり)

さらに 2025-05 リリースでは NVIDIA cuVS ベースの IVF/PQ 強化版が追加され、従来比で 4.7× のビルド高速化とレイテンシ短縮が報告されています。engineering.fb.com

4. 非同期 & ハイブリッド並列

  • 非同期検索: index.search_async()(C++) / faiss.search_async()(Python binding)を使うと、検索呼び出しをバックグラウンドスレッドに送り、呼び出し側は I/O や前処理を並行できます。github.com

  • CPU+GPU 混在: 同一ノードで「GPU で coarse search → CPU で細かい再ランキング」をパイプライン化するワークフローも可能です(例: IVF+PQ を GPU、リスト内再計算を CPU)。

5. スケーラビリティ設計パターン

パターン 概要 主な利点 主な注意点
バッチ+内部 OMP クエリ=行列として一括検索 CPU キャッシュ最適 1 クエリは不利
Multi-Thread Outside Python などでプロセス/スレッド分割 GIL を回避 インデックス複製が必要⇒メモリ増
Multi-GPU Replicas 高並列・高リコール ほぼ線形スケール メモリ 𝑁×
Multi-GPU Shards 容量スケールアウト メモリ節約 リコール低下 & 集約コスト
分散クラスタ (Dask/Spark) ノード間シャーディング 無限水平拡張 アプリ側集約実装が必須

6. 実装例(Python)

python
import faiss, numpy as np # ── GPU リソース初期化 ───────────────── res = faiss.StandardGpuResources() res.setTempMemory(512 * 1024 * 1024) # 512 MB res.setDefaultNullStreamAllDevices() # 多ストリーム有効化 # ── インデックス(IVF Flat)作成 ──────── d, nlist, k = 768, 4096, 10 cpu_index = faiss.index_factory(d, f"IVF{nlist},Flat") gpu_index = faiss.index_cpu_to_gpu(res, 0, cpu_index) # ── バッチ検索 ────────────────────────── xb = np.random.randn(10_000, d).astype("float32") xq = np.random.randn(2048, d).astype("float32") gpu_index.train(xb) gpu_index.add(xb) # OMP: CPU 並列制御 faiss.omp_set_num_threads(8) # 検索(GPU 内部並列+バッチ) D, I = gpu_index.search(xq, k)

7. まとめ & チューニング要点

  1. クエリは必ずバッチ化し、BLAS / cuBLAS の行列演算を活かす。

  2. CPU では OpenMP スレッド制御を一元的に行い、外部多重並列は極力避ける。

  3. GPU では ストリーム数と一時メモリを調整してカーネルとメモリ転送を重畳。

  4. データ量に応じて Replicas(速度)/Shards(容量) を選択し、複数 GPU で水平スケール。

  5. 2025 年以降のリリースでは cuVS 連携などが追加されており、新機能の API 互換を確認する。

これらの設計指針を踏まえることで、FAISS のスケーラビリティを最大限引き出しつつ、検索精度とレイテンシのバランスを最適化できます。

ChatGPT4o 生成日:2025/06/18