Product quantization (PQ)
Compress a vector by splitting it into m sub-vectors and storing each as a 1-byte codebook index: 3,072 bytes become 64, with fast approximate distances.
Product quantization (PQ) shrinks vectors so far more of them fit in memory. It cuts each vector into, say, 64 small pieces, and for each piece stores only the number of the closest entry in a learned 'codebook' of 256 typical pieces. Distances are then estimated from those codes using small lookup tables. You lose some accuracy, so systems usually re-check the best candidates with the full vectors.
Describing a face as 'nose type 17, eyes type 203, mouth type 4…' from a catalogue of standard features instead of sending the full photo.
Reporting PQ recall from codes alone and concluding 'quantization is too lossy', or the opposite — shipping without rescoring and losing 10–20 recall points silently. PQ is a candidate generator; always pair it with a rescoring pass and measure after it.
Split a D-dim vector into m sub-vectors of D/m dims; run k-means with 2^nbits centroids (usually 256 → 1 byte) per subspace. A vector becomes m bytes (PQ64 at 768-d → 64 B, 48× smaller than float32). At query time, asymmetric distance computation (ADC) builds an m × 256 table of query-subvector-to-centroid distances once, then each database distance is m table lookups and adds — cache-friendly and SIMD-able (FAISS PQ64x4fs fast-scan uses 4-bit codes in registers). Error comes from subspaces being quantized independently; OPQ learns a rotation first so variance is balanced across subspaces (OPQ64,IVF65536,PQ64). Recall@10 from PQ codes alone is 0.6–0.85 depending on m; rescoring the top 100–1,000 with full-precision vectors from SSD or RAM recovers 0.95+. PQ is almost always combined with IVF (inverted file index) (IVFADC, Jégou et al. 2011) so only a few lists are scanned. DiskANN, ScaNN and Milvus/Zilliz use PQ-style codes in memory with full vectors on disk.
FAISS OPQ{m},IVF{nlist},PQ{m} with m = D/8–D/12 (64–96 at 768-d), 8-bit codes, train on ≥ 256 × 256 vectors per subspace sample (~1M vectors is safe), rescore the top 200–1,000 with float16 vectors. Use fast-scan (PQ{m}x4fs) when CPU-bound.
Clinical literature corpus, 500M passages × 768-d. Float32: 1.5 TB. OPQ96,IVF262144,PQ96: 96 B code + 8 B id ≈ 52 GB → fits one 64 GB node. Query: nprobe 64, ADC over ~120k codes (2 ms CPU), top 1,000 rescored from float16 vectors on NVMe (768 KB/query read, ~3 ms) → recall@10 0.96 vs exact. Without rescoring: 0.81.
Frame PQ as a memory/recall dial with a rescoring escape hatch: "PQ codes pick candidates, full vectors decide the order; I size m so codes fit RAM and keep float16 originals on SSD." Expect: how do you pick m? Measure recall@10 after rescoring at m = 32/64/96 on 1,000 queries and pick the smallest m that clears the target.
Numbers worth knowing
- Compression
- 48× (3,072 B → 64 B)768-d float32, PQ64 with 8-bit codes
- Recall@10 from codes alone
- 0.6–0.85m = 32–96 at 768-d; rises to 0.95+ after rescoring top 1,000
- Rescore cost
- ~2–5 ms1,000 float16 vectors read from NVMe per query
How to decide
| When | Do this |
|---|---|
| Vectors > ~5× available RAM | IVF-PQ in RAM + full vectors on SSD for rescoring. |
| Fits in RAM with int8 | Prefer scalar quantization (Scalar & binary quantization) — simpler, ~99% recall retained. |
| Recall after rescoring < target | Raise m or rescore depth before raising nprobe; add OPQ. |
Cost maths
RAM per vector = m + 8 (id) bytes. 500M × (96 + 8) = 52 GB → one 64 GB node (~$310/month at ~$0.43/hr, illustrative Oct 2026) vs float32 1.5 TB → ~24 nodes (~$7,400/month) per replica. SSD for float16 originals: 768 GB ≈ $60/month at $0.08/GB-month. Training: k-means on 1M samples ≈ minutes on a GPU, < 1 hour on CPU.
Operating it
- Rescore depth needed for target recall > 2,000 — raise m or switch to int8 SQ if RAM allows
- SSD read p99 > 10 ms during rescoring — move originals to RAM tier or a faster volume
- New embedding model (different D) — retrain codebooks; codes are model-specific
- Recall@10 after rescoring vs exact — alert below 0.95
- Rescore stage latency p99 — alert above 10 ms
- Codebook model version vs embedding model version — page on mismatch
When it goes wrong
After compressing a 300M-vector research index to save RAM, answer quality fell but retrieval latency improved, so nobody noticed for a week.
Faithfulness on the nightly eval set dropped from 0.88 to 0.79; ANN-vs-exact recall@10 audit read 0.78.
The migration switched to IVF,PQ32 without a rescoring stage; final ranking used PQ approximate distances.
Added float16 rescoring of the top 500 from NVMe and OPQ rotation; recall@10 returned to 0.96 for +3 ms p50. Recall gate added to the index CI.
Related terms
Sources
- Product Quantization for Nearest Neighbor Search — Jégou, Douze & Schmid, 2011paper
- The Faiss library — Douze et al., 2024paper
- Faiss wiki: The index factory — Meta AI (FAISS), 2026docs
Check yourself
Three questions. Every option is explained once you answer.