Faiss-GPU

Posted by Jingbiao on May 17, 2023, Reading time: 4 minutes.

Faiss-GPU library import

To use Faiss-GPU, you need to import torch before faiss. Otherwise, you will get an error like this when you are trying to use faiss gpu_index = faiss.index_cpu_to_gpu(res, 0, index) # make it a GPU index:

1
RuntimeError: Error in faiss::gpu::GpuIndex::GpuIndex(std::shared_ptr, int, faiss::MetricType, float, faiss::gpu::GpuIndexConfig) at /root/miniconda3/conda-bld/faiss-pkg_1669821803039/work/faiss/gpu/GpuIndex.cu:55: Error: 'config_.device < getNumDevices()' failed: Invalid GPU device 0

and also when trying to use a = torch.tensor([[1.,2,3],[4,5,6]], device='cuda')

1
RuntimeError: No CUDA GPUs are available

Solution: import torch before faiss:

1
2
3
4
import torch
import faiss
import faiss.contrib.torch_utils
#import torch

Example code for Faiss-GPU

IndexFlatL2

Note that the distance here is the squared Euclidean (L2) distance, avoiding the square root. If we want the exact distance between two features, we need to apply square root.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
index = faiss.IndexFlatL2(3)   # build the index
res = faiss.StandardGpuResources()  # use a single GPU
gpu_index = faiss.index_cpu_to_gpu(res, 0, index) # make it a GPU index

a = torch.tensor([[1.,2,3],[4,5,6],[4,5,100], [4,123,6], [434,5,6]], device='cuda')
b = torch.tensor([[1.,2.,3.]], device='cuda')
# Normalize before adding to index
doc = f.normalize(a, p=2, dim=1)
q = f.normalize(b, p=2, dim=1)

gpu_index.add(doc)
D,I = gpu_index.search(q, topk)
# D gives the L2 distacne 
# I gives index, the closest(smallest D)/most similar index in the docs first

L2squared_pytorch = torch.nn.MSELoss(reduction='none')
L2_squared = L2squared_pytorch(a,b)
L2_squared = L2_squared.sum(dim=1)

# at this point L2_squared should be consistent as to D

# if you want to get actual distance:
L2_squared = L2_squared ** 0.5

IndexFlatIP

Inner product, if normalized then we would have a result equivalent to cosine similarity. Note here normalised Inner product measures similarity (range +1 → -1), larger the value, more similar which is the opposite to the distance metrics in L2.

1
2
3
4
5
6
7
8
9
10
11
12
13
index_IP = faiss.IndexFlatIP(3)
res = faiss.StandardGpuResources()  # use a single GPU
gpu_index_IP = faiss.index_cpu_to_gpu(res, 0, index_IP) # make it a GPU index

gpu_index_IP.add(doc)
Sim,I = gpu_index_IP.search(q, topk)
# Sim gives the similarity
# I gives index, the closest/most similar(largest Sim) index in the docs first

# Given that the embeddings are normalised, Sim should be equivalent to cosine sim
cos = torch.nn.functional.cosine_similarity(a,b)
# cos should be approximately equal to Sim, 
# Due to different precision, they are not exactly equal in floats

Reference

  1. https://github.com/facebookresearch/faiss/wiki/MetricType-and-distances
  2. https://github.com/facebookresearch/faiss/wiki/Faiss-indexes
  3. https://pytorch.org/docs/master/generated/torch.nn.MSELoss.html#mseloss
  4. https://www.facebook.com/groups/faissusers/posts/1025663204524632/
  5. https://github.com/facebookresearch/faiss/wiki/Faiss-on-the-GPU
  6. https://github.com/facebookresearch/faiss/wiki/Faiss-on-the-GPU