Information Retrieval2017intermediate9 min read

Billion-Scale Similarity Search with GPUs

البحث عن التشابه بين مليارات المتّجهات باستخدام GPU

Johnson, J. · Douze, M. · Jégou, H. — IEEE Transactions on Big Data

The problem

By 2017, had turned images, text, and video into high-dimensional vectors (50–1000+ dimensions). Finding the most similar vectors in a billion-scale collection is critical for image search, recommendation, and retrieval — but exhaustive comparison is impossibly slow, and existing methods were bottlenecked by poor algorithms and wasteful memory access patterns. No single-machine solution could search a billion vectors at both high speed and acceptable accuracy.

The contribution

: a GPU-optimized library for search that combines three ideas. First, a novel k-selection algorithm (WarpSelect) that runs entirely in GPU register memory at up to 55% of theoretical peak . Second, an optimized implementation of the IVFADC index — with — that compresses each to a few bytes and searches only a fraction of the database. Third, multi-GPU sharding and replication that scales to billions of vectors. The result is 8.5× faster than prior GPU state of the art, builds a k-NN graph over 95 million images in 35 minutes, and handles 1 billion vectors on 4 GPUs in under 12 hours.

The impact

FAISS became the de facto infrastructure for vector similarity search in both research and industry. Every major (RAG) system, every dense passage retrieval pipeline (DPR), and most -based recommendation engines rely on FAISS or its direct descendants. It transformed similarity search from a niche research topic into a standard building block of modern AI systems — from ChatGPT's knowledge retrieval to Meta's content understanding at scale.

Imagine a library with a billion books and no catalog. To find books similar to one you're holding, you'd have to walk past every single shelf — even with a team, it takes forever.

FAISS is like building a catalog system in three steps: first, compress each book's description into a short code (product ). Second, sort books into labeled rooms so you only search the rooms relevant to your query (inverted file). Third, send a whole army of speed-readers (GPU threads) to check every book in those rooms simultaneously.

The result: what took hours on a single librarian's desk now takes milliseconds with thousands of readers working in parallel.

The problem: finding needles in a billion-dimensional haystack

Deep learning turns images, sentences, and videos into vectors — lists of numbers that capture meaning. Two similar images produce vectors that are close together in this high-dimensional space. Finding the kk nearest neighbors of a query vector xx among a database of vectors [yi][y_i] means solving:

L=k-arg⁡min⁡i∥x−yi∥2L = k\text{-}\arg\min_{i} \|x - y_i\|^2

For a million vectors this is already expensive. For a billion, the naive approach — computing all distances and sorting — is completely impractical. Even on a GPU with teraflops of throughput, two bottlenecks remain: the raw volume of distance computations and the challenge of selecting the top-kk results efficiently.

Open in Lab
Watch how brute-force search time grows linearly with database size. At a billion vectors, even GPU throughput isn't enough without smarter algorithms.
The demo wakes as you arrive…

Idea 1: compress vectors with product quantization

A 128-dimensional vector takes 512 bytes as 32-bit floats. A billion of them needs 512 GB — far more than any GPU's memory. Product quantization (PQ) solves this by splitting each vector into bb sub-vectors and replacing each sub-vector with the index of its closest centroid from a small learned codebook of 256 entries. The result: each vector is encoded in just bb bytes (typically 8–64 bytes), a compression ratio of 8–64×.

Think of it like color quantization in a GIF image: instead of storing exact RGB values for every pixel, you build a 256-color palette and store just the palette index per pixel. PQ does the same thing, but independently for each slice of the vector.

y≈q(y)=q1(y)+q2(y−q1(y))y \approx q(y) = q_1(y) + q_2(y - q_1(y))
Two-level quantization — the IVFADC approximation — q₁ maps to a coarse centroid (which inverted list the vector belongs to) · q₂ encodes the residual with a product quantizer · together they approximate the original vector with just a few bytes
Open in Lab
Drag a vector to see how it is split into sub-vectors, each mapped to its nearest centroid. The compressed code is the sequence of centroid indices.
The demo wakes as you arrive…

Idea 2: inverted file index — search only where it matters

Even with compressed vectors, scanning all of them is wasteful. The inverted file (IVF) adds a routing step: first, cluster the entire database into ∣C1∣|C_1| groups using k-means. At query time, find the τ\tau closest cluster centers to the query (the multi-probe parameter), then search only the vectors in those clusters. If ∣C1∣=n|C_1| = \sqrt{n} and τ\tau is small, you skip the vast majority of the database.

Picture a city-wide post office system. Instead of checking every mailbox in the city, you first identify which τ\tau postal districts your letter most likely belongs to, then search only the mailboxes in those districts. More districts (τ\tau) means better recall at the cost of more scanning.

Open in Lab
Adjust the number of probes (τ) and watch how search narrows from the full database to a small subset of clusters.
The demo wakes as you arrive…

Idea 3: WarpSelect — fast k-selection in GPU registers

The GPU's power comes from thousands of threads working in parallel, but finding the top-kk smallest distances is inherently sequential — classic heaps are slow on GPUs because they have irregular memory access and high warp divergence.

WarpSelect solves this by keeping all intermediate state in the GPU register file — the fastest memory tier, with 10–100× the bandwidth of even shared memory. Each warp (32 threads) maintains a sorted "warp queue" of the kk smallest values seen so far, plus small per-thread queues that act as filters. New values are rejected instantly if they're larger than the per-thread queue head. Only when the thread queues fill up does a bitonic sorting network merge everything back into the warp queue.

The result: a single-pass algorithm that processes input at up to 55% of peak GPU memory bandwidth — close to the theoretical speed limit of simply reading the data.

Open in Lab
Watch how thread queues filter incoming distances and periodically merge into the warp queue. Most values are rejected without ever touching the main queue.
The demo wakes as you arrive…

Lookup tables: computing distances without decompressing

A brilliant property of product quantization: you never need to decompress the vectors to compute distances. For a given query xx and a known coarse centroid q1(y)q_1(y), precompute a lookup table TjT_j of size 256 for each of the bb sub-quantizers. Each entry stores the squared distance from the query's sub-vector to a centroid. Then, computing the approximate distance to any compressed vector is just bb table lookups and additions — no multiply-adds, no decompression.

This turns the problem from n×dn \times d floating-point operations (expensive) to n×bn \times b byte lookups (cheap and cache-friendly). On a GPU, these lookup tables fit perfectly in shared memory, the fast on-chip cache that all threads in a block can access.

∥x−q(y)∥2=∑j=1bTj[codej(y)]\|x - q(y)\|^2 = \sum_{j=1}^{b} T_j\bigl[\text{code}_j(y)\bigr]
Distance from lookup tables — the core PQ trick — Each T_j is a precomputed table of 256 sub-vector distances · code_j(y) is the j-th byte of the compressed vector · summing b lookups gives the approximate squared distance

Putting it all together: the IVFADC pipeline on GPU

The full search pipeline for a batch of queries works in three stages:

  • Coarse quantization: find the τ\tau closest cluster centers for each query using exact brute-force search (matrix multiplication via cuBLAS).

  • List scanning: for each query, scan only the τ\tau selected inverted lists. For each compressed vector in a list, compute its approximate distance via lookup tables in shared memory, and feed the result into WarpSelect running in registers.

  • k-selection finalization: merge partial results across lists and tiles to produce the final top-kk for each query.

Tiling over queries (tqt_q at a time) and multi-streaming keep GPU utilization high while bounding memory usage. The entire pipeline avoids unnecessary global memory round-trips — the critical insight that makes FAISS so fast.

Open in Lab
Step through the three stages of the IVFADC pipeline. Watch queries flow from coarse quantization through list scanning to final k-selection.
The demo wakes as you arrive…

Scaling beyond one GPU: sharding and replication

When the index is too large for a single GPU, FAISS supports two complementary strategies:

  • Replication: copy the entire index to RR GPUs. Each GPU handles nq/Rn_q/R queries against the full database. This gives near-linear speedup for large query batches.

  • Sharding: split the database across SS GPUs, each holding ℓ/S\ell/S vectors. Every query is sent to all shards, and partial results are merged with an additional round of k-selection. This lets you index datasets that exceed any single GPU's memory.

Both strategies compose: SS shards with RR replicas each use S×RS \times R GPUs total. In practice, 4 GPUs can handle a billion 128-dimensional vectors with room to spare.

Open in Lab
Toggle between replication and sharding to see how queries and data are distributed across GPUs.
The demo wakes as you arrive…

The same idea in code

FAISS IVFPQ index — build and searchpython

Simplified to show the idea — not the real implementation.

import numpy as np
import faiss

d = 128           # vector dimension
n = 1_000_000     # database size
nq = 1000         # number of queries
k = 10            # neighbors to find

# Random data for demonstration
xb = np.random.random((n, d)).astype('float32')
xq = np.random.random((nq, d)).astype('float32')

# Build the IVFPQ index
nlist = 1024                    # number of inverted lists (clusters)
m = 8                           # sub-quantizers (bytes per vector)
quantizer = faiss.IndexFlatL2(d)  # coarse quantizer: exact L2
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, 8)

index.train(xb)      # learn the codebooks via k-means
index.add(xb)        # add all vectors to the index

index.nprobe = 10    # search 10 closest clusters (τ)
D, I = index.search(xq, k)  # D = distances, I = indices

# D[i] = k nearest distances for query i
# I[i] = k nearest vector IDs for query i

Results: how fast and how accurate

Open in Lab
Drag the probe count to explore the speed-accuracy tradeoff. More probes improve recall but increase search time.
The demo wakes as you arrive…

Why it mattered

  1. 2011

    Product Quantization (Jégou et al.)

    Introduced PQ for nearest neighbor search: split vectors into sub-vectors, quantize each independently. Achieved unprecedented compression with distance-preserving codes.

  2. 2017

    FAISS (this paper)

    GPU-optimized IVFADC with WarpSelect. Made billion-scale vector search practical on a single machine. Open-sourced by Facebook AI Research.

  3. 2020

    Dense Passage Retrieval (DPR)

    Used FAISS as its vector search backend to retrieve passages for open-domain question answering. Proved that dense retrieval can outperform keyword-based search.

  4. 2020

    RAG (Lewis et al.)

    Retrieval-Augmented Generation combined FAISS-backed retrieval with language model generation. The architecture behind knowledge-grounded LLM responses.

  5. 2023

    Vector databases era

    Pinecone, Weaviate, Milvus, and others build commercial vector databases. Most use FAISS-derived algorithms internally. Vector search becomes standard AI infrastructure.

FAISS is to vector search what cuBLAS is to matrix multiplication: the optimized building block that every higher-level system builds upon. Dense Passage Retrieval would not exist without it, and neither would the retrieval backends powering today's language model assistants.

CitationJohnson, J., Douze, M. & Jégou, H.. Billion-Scale Similarity Search with GPUs. IEEE Transactions on Big Data, 2017.

Terms in this paper