🕸️

FaissをソースコードからビルドしてGPUで利用する

2022/11/04に公開約6,800字

概要

Faissは、Facebook Research (現Meta Research) が開発した近似最近傍探索 (Approximate Nearest Neighbor: ANN) のライブラリです。C++で実装されており、GPUでの実行にも対応しているほか、PythonのWrapperも提供されいます。Pythonパッケージではfaiss-cpufaiss-gpuの2種類が存在し、公式ではAnacondaでのインストールを推奨しているのですが、PyPIからもパッケージがインストール可能です。

GPU環境でのFaiss実行を試みたのですが、現在PyPIで提供されているfaiss-gpu==1.7.2では私の環境では正常に動作しませんでした。そのためソースコードからビルドしてインストールしたのでその経緯と、最後にGPU/CPUの簡単なベンチマークを紹介したいと思います。

環境

  • Ubuntu: 22.04
  • CUDA: 11.8
pypiパッケージで動作しなかったときのエラーメッセージ
Faiss assertion 'err == CUBLAS_STATUS_SUCCESS' failed in void faiss::gpu::runMatrixMult(faiss::gpu::Tensor<float, 2, true>&, bool, faiss::gpu::Tensor<T, 2, true>&, bool, faiss::gpu::Tensor<IndexType, 2, true>&, bool, float, float, cublasHandle_t, cudaStream_t) [with AT = float; BT = float; cublasHandle_t = cublasContext*; cudaStream_t = CUstream_st*] at /__w/faiss-wheels/faiss-wheels/faiss/faiss/gpu/utils/MatrixMult-inl.cuh:265; details: cublas failed (13): (512, 64) x (100000, 64)' = (512, 100000) gemm params m 100000 n 512 k 64 trA T trB N lda 64 ldb 64 ldc 100000

ソースコードからのビルド

基本的にはFaissのINSTALL.mdに書かれている通りにビルドすれば問題ありません。

faiss/INSTALL.md at main · facebookresearch/faiss

インストールに必要なライブラリやパッケージ等がありますので、あらかじめ準備しておく必要があります。私の場合CUDAやPython等はすでに導入済みでしたので、今回はMKLとSWIGをインストールしていきます。

事前準備 - MKL

まずは"BLAS implementation"としてMKLをインストールします。MKLとは、Intelの数値演算ライブラリです。Anacondaでは標準でMKLを利用しているので、FaissがAnaconda環境を推奨しているのでしょう。以下のドキュメントに沿ってaptからインストールできます。

Installing Intel® Performance Libraries and Intel® Distribution for...

$ curl -sfL https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB | sudo apt-key add -
$ sudo curl -sfL https://apt.repos.intel.com/setup/intelproducts.list -o /etc/apt/sources.list.d/intelproducts.list
$ sudo apt update
$ sudo apt install -y intel-mkl-64bit-2020.4-912

今回はintel-mkl-64bit-2020.4-912をインストールしました。sudo apt install intel-mkl-64bitを実行するとパッケージのバージョン一覧が表示され指定を促されるので、その中から最新のものを選択しました。

あとは、インストールした先に各種PATHを通します。利用しているshellの設定ファイル (.bashrc.zshrc)に下記内容を追記します。なお、私の環境ではNumpy実行時にエラーがでたので4行目を追加しています。

# for MKL
export MKL_ROOT_DIR=/opt/intel/mkl
export LD_LIBRARY_PATH=$MKL_ROOT_DIR/lib/intel64:/opt/intel/lib/intel64_lin:$LD_LIBRARY_PATH
export LIBRARY_PATH=$MKL_ROOT_DIR/lib/intel64:$LIBRARY_PATH
export LD_PRELOAD=/opt/intel/mkl/lib/intel64/libmkl_def.so:/opt/intel/mkl/lib/intel64/libmkl_avx2.so:/opt/intel/mkl/lib/intel64/libmkl_core.so:/opt/intel/mkl/lib/intel64/libmkl_intel_lp64.so:/opt/intel/mkl/lib/intel64/libmkl_intel_thread.so:/opt/intel/lib/intel64_lin/libiomp5.so

事前準備 - SWIG

次にPython WrapperのためのSWIGをインストールします。

$ sudo apt -y install swig

ちなみに、過去のソースからのビルド例ではaptのSWIGでは駄目とありましたが(link)、今回の私の環境では問題ありませんでした。

FaissのビルドとPython Wrapperのインストール

それではFaiss本体をビルドしていきます。

$ git clone https://github.com/facebookresearch/faiss
$ cd faiss
$ cmake -B build .
$ make -C build -j faiss

最後にPython Wrapperのビルドとインストールを行います。

$ make -C build -j swigfaiss
$ cd build/faiss/python
$ python setup.py install

インストールが完了したかを確認して、ようやく終了です。

$ pip freeze | grep faiss
faiss==1.7.2

GPUでの実行

それでは試しにGPUで動作するか試してみましょう。Getting Startedのサンプルを利用し、GPUで実行してみます。なお、小さいサンプルサイズだとすぐに終わってしまうので、nbのサイズを増やしています。

import faiss
import numpy as np

d = 64                           # dimension
nb = 1000000                     # database size
nq = 10000                       # nb of queries
np.random.seed(1234)             # make reproducible
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.
xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.

gpu_resource = faiss.StandardGpuResources()
cpu_index = faiss.IndexFlatL2(d)
gpu_index = faiss.index_cpu_to_gpu(gpu_resource, 0, cpu_index)
gpu_index.add(xb)

k = 4                              # we want to see 4 nearest neighbors
D, I = gpu_index.search(xb, k)     # actual search
print(I)

あとはnvidia-sminvitop等でGPUのUtilizationを見て、実行時にGPUが使われていればOKです。このサンプルスクリプトは約25秒で終了しました。

ベンチマーク

せっかくFaissが動作するようになったので、GPUとGPUで実行環境による性能差を比較してみましょう。

今回はNLPの事例として、単語埋め込み(Word Embedding)のベクトルを対象に全単語の類似単語を求める操作の実行時間を計測します。それぞれ単語ベクトルは300次元で密なベクトルです。実験条件は以下の通りです。

  • 単語ベクトル:chiVe Version v1.2 mc5
    • 語彙数 3,197,456
    • 次元数: 300
  • 求める近傍の点: 10個
  • Faissのインデックス: IndexFlatL2

つまり、約319万のベクトルのANNのインデックスに対して、約319万回の近似最近傍探索の操作を行って各10件の近傍点を求めるという操作を行います。

コード

単語ベクトルの読み込み部分は割愛し、差分のあるインデックス作成部分のみ示します。

# Run with CPU
index = faiss.IndexFlatL2(d)
index.add(vectors)
# Run with GPU
gpu_resource = faiss.StandardGpuResources()
cpu_index = faiss.IndexFlatL2(d)
gpu_index = faiss.index_cpu_to_gpu(gpu_resource, 0, cpu_index)
gpu_index.add(vectors)

実験に使用したソースコードは、下記レポジトリにあります。

https://github.com/yagays/faiss_cpu_gpu

結果

実行結果は以下のようになりました。なお実行時の計算機リソース使用状況は、CPUの場合は全24スレッド使用しLoad Averageで11~12程度、使用率はトータルで50%程度でした。GPUの場合は、GPUメモリ使用率27%、GPUのUTL使用率は100%でした。

Method Device Time (hh:mm:ss)
GPU GTX 3090 (Mem:24GB) 00:07:26
CPU Ryzen 9 5900 (12core/24thread) 18:36:19

結果としては、GPUは7分で終了したのに対し、CPUは18時間もかかりました。圧倒的ですね……! CPUはマルチスレッドで動かしてもこれだけの時間を要したことを考えると、GPUの超並列処理の強さを改めて実感しました。

(補足) Poetryのインストール

最後にPoetryでインストールする方法も書いておきます。なお、ビルドしたfaissのディレクトリはここでは/home/yag_ays/local/src/faiss/にあります。

$ poetry add /home/yag_ays/local/src/faiss/build/faiss/python
Updating dependencies
Resolving dependencies... (0.5s)

Writing lock file

Package operations: 1 install, 0 updates, 0 removals

  • Installing faiss (1.7.2 /home/yag_ays/local/src/faiss/build/faiss/python)

pyproject.tomlには以下のように記述されます。

[tool.poetry.dependencies]
faiss = {path = "/home/yag_ays/local/src/faiss/build/faiss/python"}

あとは実際に動作するかを確認して完了です。

In [1]: import faiss

In [2]: faiss.get_num_gpus()
Out[2]: 1

参考

Discussion

ログインするとコメントできます