Published
-
Product Quantization (PQ): Compact Vectors and Fast Approximate Distance
Large vector databases create two bottlenecks:
- Memory footprint of storing high-dimensional float vectors.
- Compute cost of distance comparisons during retrieval.
Product Quantization (PQ) addresses both by compressing vectors into short codes and estimating distances from those codes.
In this post we cover:
- PQ intuition
- Training and encoding pipeline
- A worked numeric example with explicit codebooks
- ADC distance computation
- Tuning and pitfalls
Intuition
A 128D float vector (FP32) needs:
At 100M vectors, that is about 51.2 GB for vectors alone.
PQ splits each vector into sub-vectors, then quantizes each subspace with a small codebook.
- Divide dimension into chunks.
- Train one k-means codebook per chunk.
- Store one codeword ID per chunk.
If each chunk uses 256 codewords, each ID is 1 byte. Total code length is bytes.
Example: gives 16 bytes/vector instead of 512 bytes/vector.
PQ Pipeline
1. Split dimensions
For and , split each vector into:
- first 4 dims (subspace 1)
- last 4 dims (subspace 2)
2. Train sub-codebooks
Run k-means independently in each subspace.
- Codebook 1: centroids in subspace 1
- Codebook 2: centroids in subspace 2
3. Encode database vectors
For each subspace, replace sub-vector by nearest centroid ID.
Stored code for vector is [id_1, id_2, ..., id_m].
4. Query-time distance (ADC)
Use Asymmetric Distance Computation (ADC):
- Keep query as float vector.
- Compare query sub-vectors with centroid tables.
- Sum lookup-table values for each vector code.
No need to fully decode vectors.
Worked Example (small but explicit)
Use 4D vectors and (two 2D subspaces). Let each codebook have centroids.
Database vectors:
Split each as:
- subspace A: first 2 dims
- subspace B: last 2 dims
Train codebooks (assume learned centroids)
Codebook A (2D):
Codebook B (2D):
Encode vectors
For each vector, pick nearest centroid in each subspace.
- : A part (1,1) -> , B part (8,8) -> => code
[0,0] - : A part (2,1) -> , B part (9,8) -> => code
[0,0] - : A part (8,9) -> , B part (1,2) -> => code
[1,1] - : A part (9,8) -> , B part (2,1) -> => code
[1,1]
Now each 4D vector is stored as 2 small IDs.
Query with ADC
Let query be:
Split query:
Build lookup tables (squared L2 distances).
For subspace A:
For subspace B:
Approximate distance for a vector code [i,j]:
So:
- code
[0,0]: - code
[1,1]:
Hence are much nearer to query than , which matches geometry.
Why PQ Is Fast
Without PQ, each candidate distance uses many multiply-add operations.
With PQ + ADC:
- Precompute small lookup tables once per query.
- Per vector, do only table reads + additions.
This gives large speedups and huge memory savings.
Important Design Choices
Number of subquantizers m
- Larger
m: finer representation, longer code, better recall. - Smaller
m: shorter code, more compression, lower recall.
Codebook size k
- Commonly for 8-bit IDs.
- Larger
kimproves precision but may increase table size and training complexity.
Preprocessing
PQ works best when dimensions are balanced and less correlated. In practice, people often use OPQ (Optimized PQ), which learns a rotation before splitting dimensions.
PQ in Real Systems
PQ is often combined with coarse partitioning, yielding IVF+PQ:
- IVF narrows candidate set.
- PQ makes candidate storage and scoring compact.
This combination powers many large-scale vector search systems.
Caveats
- Approximate distances can reorder close neighbors.
- Poor codebook training hurts recall.
- Distribution shift can make old codebooks stale.
- Very small datasets may not benefit much from PQ.
Minimal Pseudocode
train_pq(vectors, m, k):
split vectors into m subspaces
for each subspace s:
codebook[s] = kmeans(subspace_vectors[s], k)
return codebooks
encode_pq(vector, codebooks):
for each subspace s:
id[s] = nearest_centroid(vector_subspace[s], codebook[s])
return id
adc_distance(query, code, codebooks):
total = 0
for each subspace s:
total += distance(query_subspace[s], codebook[s][code[s]])
return total
If IVF helps you reduce the number of candidates, PQ helps you store and score those candidates efficiently. Together, they are a practical default for large-scale ANN.