複数GPUの活用

複数 GPU を活かした FAISS 高速化の全体像

1. なぜマルチ GPU か

  • メモリ拡張 ― 1 台の GPU では載り切らない 10 億件規模の埋め込みを保持可能

  • スループット向上IndexReplicas 方式でバッチ検索を分散すると 8 GPU 構成で 6〜7 倍のスピードアップが報告されているgithub.com

  • 最新 GPU アルゴリズム ― FAISS 1.10 では NVIDIA cuVS/CAGRA を正式採用し,IVF/HNSW 系の GPU 検索をさらに 1.7〜8.1 倍短縮できるengineering.fb.com


2. アーキテクチャ別の選択肢

方式 仕組み 長所 留意点
IndexReplicas 同一インデックスを各 GPU に複製し,クエリを均等に分配 ほぼ線形に QPS が伸びる/実装が簡単 全 GPU にフルサイズを保持 → メモリ総量は変わらない
IndexShards ベクトル集合を GPU 台数で分割して配置 1 /N のメモリで巨大コーパスを保持 k が大きいとマージコスト増.リコール低下を防ぐには nprobe を十分確保
Hybrid (common_ivf_quantizer) IVF の粗量子化器だけ共有しコードブックはシャード メモリ節約と計算共有のバランス FAISS ≥1.7 で common_ivf_quantizer=True が必要

index_cpu_to_gpu_multiple はデフォルトで IndexReplicas を生成し,GpuMultipleClonerOptions(shard=True) を渡すと IndexShards に切り替わるgithub.com


3. Python 実装フロー

python
import faiss, numpy as np # CPU で IVF-PQ を作成 dim, nlist, m = 768, 4096, 16 index_cpu = faiss.index_factory(dim, f"IVF{nlist},PQ{m}", faiss.METRIC_L2) index_cpu.train(train_vectors) #=== マルチ GPU へ複製 ========================================= replica_opts = faiss.GpuMultipleClonerOptions() # 既定は replicate index_gpu_rep = faiss.index_cpu_to_all_gpus(index_cpu, co=replica_opts) #=== マルチ GPU へシャード ===================================== shard_opts = faiss.GpuMultipleClonerOptions() shard_opts.shard = True shard_opts.shard_type = faiss.ShardType.ShardsBalanced # v1.8 以降 index_gpu_shard = faiss.index_cpu_to_all_gpus(index_cpu, co=shard_opts) #=== 検索 ====================================================== queries = np.random.rand(10000, dim).astype('float32') D, I = index_gpu_rep.search(queries, k=10) # または index_gpu_shard

ラップレベル API: index_cpu_to_all_gpus / index_cpu_gpu_list / index_cpu_to_gpu_multiple_pyGpuResources を自動生成し,co= で微調整できるgithub.com
低レベル API: 自前で StandardGpuResources() を個別生成し GpuResourcesVector + Int32Vector を渡すと GPU ごとのワークスペースサイズや CUDA ストリームを細かく設定できるbge-model.com


4. GpuMultipleClonerOptions の主要フィールド

フィールド 代表的な値 効果
shard True / False シャードかレプリカかを決定
shard_type ShardsBalanced, ShardsRandom 分割方法(ベクトル数均等/ランダム)
useFloat16 True 学習済みコードブック/PQ も FP16 に圧縮
common_ivf_quantizer True 各シャードで粗量子化器を共有しメモリを削減
copyToHostIfOom True GPU メモリ不足時に自動で CPU へ退避(性能は低下)

5. パフォーマンスを引き出す Best Practices

  1. バッチサイズ – 検索時は 4 K~16 K 単位で投げると GPU が飽和しやすい。

  2. ピンメモリfaiss::gpu::PinnedMemoryConfig あるいは torch.cuda.set_device+ Torch tensor をそのまま渡し,CPU↔GPU コピーを最小化。

  3. FP16/INT8 量子化useFloat16=True または IndexIVFPQFastScan を併用し,帯域とメモリを半減。

  4. NVLink or PCIe Gen4+ – GPU 間通信⏩IndexReplicas はクエリのみ転送,IndexShards は結果マージ時に通信.高速リンクが効果的。

  5. 更新頻度に応じた再クローンindex_cpu.add()index_cpu_to_all_gpus() を超すボリューム更新は,GPU サイドで直接 add() するより一度 CPU でバルク追加して再複製した方が安定。

  6. スレッド数 – GPU ごとに 1 CPU スレッドで十分(I/O 管理だけ)なため,スレッドを増やしてもほぼ効果なし。


6. よくある落とし穴と対処法

症状 原因 回避策
CUDA out-of-memory インデックス複製で GPU VRAM 枯渇 シャード方式+FP16/indices_options=INDICES_CPU
リコール低下(シャード) 各シャードが独立に nprobe を行うため探索範囲が狭い nprobe を GPUs × nprobe_base に拡大
追加が極端に遅い cudaMalloc 多発 StandardGpuResources.setTempMemory(bytes) で一時領域を事前確保
PyTorch Tensor→FAISS で同期オーバーヘッド 異なる CUDA ストリーム resources.setDefaultNullStreamAllDevices() で統一か,明示的に torch.cuda.synchronize()

7. 今後の展望

  • cuVS/CAGRA 統合により GPU 検索アルゴリズムが増強され,とくに大規模 IVF/HNSW で 2〜8 倍の性能向上が確認されている(FAISS 1.10, 2025-05)engineering.fb.com

  • Faiss-NGT ブランチ(進行中)では GPU-DRAM + NVMe のハイブリッド格納,スケールアウト RPC 対応が議論されており,単一ノードを超える分散 GPU 検索が現実味を帯びている。


まとめ

  • 複製 (IndexReplicas) は高 QPS 低レイテンシに最適。

  • シャード (IndexShards) は巨大データセット向けで,common_ivf_quantizer と併用するとメモリ効率が高い。

  • Python ラッパで index_cpu_to_all_gpus を呼び,GpuMultipleClonerOptions を切り替えるだけで実運用が可能。

  • 最新の FAISS 1.10 + cuVS に更新し,FP16/NVLink/十分なバッチサイズを確保すると,CPU 比で 50〜100 倍クラスのスループットが期待できる。

ChatGPT4o 生成日:2025/06/18