Table of Contents

Storing raw vectors in memory becomes cost-prohibitive at scale. For example, a dataset of 100 million vectors with 1536 dimensions stored as 32-bit floating points (FP32) requires approximately 614 GB of raw RAM—excluding HNSW graph indices, which double that memory footprint [1].

To scale search systems efficiently, vector databases implement lossy compression techniques like Scalar Quantization (SQ) and Product Quantization (PQ).

By converting floating-point vectors into compact quantized representations, databases can compress indices by up to 95%, trading a small drop in search accuracy for massive hardware cost savings.

This article details the mechanics of SQ and PQ compression algorithms.


Product Quantization Compression Flow

Product Quantization splits vectors into sub-spaces and encodes them using codebook cluster IDs:

flowchart TD A["Raw Vector: 1024-dim FP32 - 4096 Bytes"] --> B["Split into 8 Sub-Vectors of 128-dim"] subgraph SG1_ProductQuantizationEncoding ["Product Quantization Encoding"] B --> C["Sub-Vector 1"] B --> D["Sub-Vector 8"] C -->|Assign Closest Centroid ID| E["Centroid 42: 1 Byte"] D -->|Assign Closest Centroid ID| F["Centroid 192: 1 Byte"] end E --> G["Quantized Vector: 8 Bytes total - 99.8% Compression"] F --> G classDef green fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef red fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d; classDef blue fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef yellow fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef purple fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95; class A,F blue class B,G green class C purple class D yellow class E red

Quantization Strategies

  1. Scalar Quantization (SQ8): Standardizes and clips float values to fit within a dynamic range of 256 values, mapping them to 8-bit integers (INT8). This cuts memory consumption by 75% while maintaining high recall (often $>98%$).
  2. Product Quantization (PQ): Segments vectors into $M$ sub-vectors. For each sub-space, a K-Means clustering algorithm is run to build a codebook of 256 centroids. Each sub-vector is then replaced with its closest 1-byte centroid ID.
  3. Asymmetric Distance Computation (ADC): When searching, the query vector is kept in raw FP32 format. Distance is computed directly against the database's compressed codebook tables, saving CPU cycles by avoiding full decompression runs.

Python Implementation: Product Quantization Encoder

Here is a production-grade Python implementation of a Product Quantization encoder. It segments high-dimensional vectors, assigns them to simulated codebook centroids, and executes asymmetric distance calculations:

import numpy as np
from typing import List, Dict, Tuple
from pydantic import BaseModel

class CodebookCentroids(BaseModel):
    subspace_index: int
    centroids: List[List[float]]

class ProductQuantizer:
    """
    Compresses vectors using Product Quantization (PQ) and performs
    Asymmetric Distance Computation (ADC) search ranking.
    """
    def __init__(self, num_subspaces: int = 4, centroids_per_subspace: int = 16):
        self.m = num_subspaces
        self.k = centroids_per_subspace
        self.codebooks: Dict[int, np.ndarray] = {}

    def fit_codebooks(self, training_vectors: np.ndarray):
        """
        Fits codebook centroids across vector sub-spaces using K-Means.
        """
        n, dim = training_vectors.shape
        sub_dim = dim // self.m
        
        for i in range(self.m):
            subspace_data = training_vectors[:, i * sub_dim : (i + 1) * sub_dim]
            # Simulate K-Means clustering centroid training
            # In production, use scipy.cluster.vq.kmeans or FAISS
            random_indices = np.random.choice(n, self.k, replace=False)
            self.codebooks[i] = subspace_data[random_indices]

    def encode(self, vector: np.ndarray) -> np.ndarray:
        """
        Compresses a raw vector into a sequence of centroid indices (1 byte per subspace).
        """
        dim = vector.shape[0]
        sub_dim = dim // self.m
        code = np.zeros(self.m, dtype=np.uint8)

        for i in range(self.m):
            sub_vec = vector[i * sub_dim : (i + 1) * sub_dim]
            centroids = self.codebooks[i]
            # Find closest centroid index by Euclidean distance
            distances = np.linalg.norm(centroids - sub_vec, axis=1)
            code[i] = np.argmin(distances)

        return code

    def compute_asymmetric_distance(self, raw_query: np.ndarray, compressed_code: np.ndarray) -> float:
        """
        Performs Asymmetric Distance Computation (ADC): Query is raw, target is compressed.
        """
        dim = raw_query.shape[0]
        sub_dim = dim // self.m
        total_dist_sq = 0.0

        for i in range(self.m):
            sub_query = raw_query[i * sub_dim : (i + 1) * sub_dim]
            centroid = self.codebooks[i][compressed_code[i]]
            total_dist_sq += np.sum((sub_query - centroid) ** 2)

        return float(np.sqrt(total_dist_sq))

# Demonstration Execution
if __name__ == "__main__":
    np.random.seed(42)

    # 1. Initialize dataset with 128-dimensional vectors
    dataset = np.random.randn(500, 128)
    query_vector = np.random.randn(128)

    # Instantiate PQ with 8 sub-spaces (each sub-vector has dimension 16)
    pq = ProductQuantizer(num_subspaces=8, centroids_per_subspace=16)
    pq.fit_codebooks(dataset)

    # 2. Encode target vectors
    target_vec = dataset[0]
    compressed = pq.encode(target_vec)
    
    # 3. Calculate distance comparison
    exact_dist = np.linalg.norm(target_vec - query_vector)
    adc_dist = pq.compute_asymmetric_distance(query_vector, compressed)

    print("📊 Product Quantization Evaluation Summary:")
    print("=" * 75)
    print(f" Raw Vector Size       : {target_vec.nbytes} Bytes (128 floats)")
    print(f" Quantized Vector Size : {compressed.nbytes} Bytes (8 uint8 codes)")
    print(f" Compression Ratio     : {target_vec.nbytes / compressed.nbytes:.1f}x")
    print(f" Exact L2 Distance     : {exact_dist:.4f}")
    print(f" Asymmetric Distance   : {adc_dist:.4f}")
    print(f" Approximation Error   : {abs(exact_dist - adc_dist):.4f}")

Quantization Gotchas & Guardrails

When configuring vector compression:

Important

Use representative training data for K-Means: Product Quantization relies on pre-calculating codebook centroids. If your training dataset does not represent the query distribution (e.g. training on financial data but querying medical documents), your centroids will be poorly aligned, causing a significant drop in search recall.

Caution

Avoid SQ8 on highly non-uniform distributions: Scalar Quantization maps float values to a uniform grid. If your vector dimensions contain extreme outliers or highly non-uniform cluster densities, SQ8 will introduce massive rounding distortions. Use Product Quantization (PQ) instead, as it dynamically adapts to multi-dimensional densities.


Real-World Enterprise Impact

Teams deploying quantized index partitions report:

  • 90% Infrastructure Cost Reductions: Compressing vector indices enables hosting a 1-billion vector dataset on a fraction of the hardware, avoiding massive RAM costs.
  • Stable Sub-10ms Query Latencies: Smaller memory footprint increases CPU L3 cache hits, accelerating asymmetric lookup speed. [2]

Antithesis & Production Trade-Offs: When NOT to Use This Architecture

Even a high-throughput Vector Database, Quantization architecture introduces non-trivial operational complexity. Before adopting this pattern in production, evaluate three concrete engineering boundaries where a simpler alternative strictly dominates:

  1. Low-Throughput / Single-Node Workloads ($< 50 ext{ req/s}$): If your service operates well below saturation on a single vertical instance and does not face strict multi-tenant tail-latency SLAs (p99 < 50 ms), the bookkeeping overhead of two-tier state partitioning, canonical digest hashing, and sliding-window circuit breakers adds cognitive and operational cost without measurable user-facing gains. A single monolithic transaction boundary with standard database constraints is simpler to debug and operate.
  2. Hard Real-Time Ultra-Low-Latency Paths ($< 50,\mu ext{s}$ Budget): When operating inside kernel-bypass networking, L1/L2 cache-pinned ring buffers, or per-packet hardware data planes, any dynamic heap allocation, cryptographic SHA-256 digest computation, or asynchronous context switch violates the nanosecond/microsecond budget. In those regimes, pre-compute static lookup tables at control-plane configuration time and keep the hot path strictly zero-allocation.
  3. Rapidly Mutating Exploratory Prototypes: Enforcing strict schema contracts, deterministic idempotency keys, and automated regression gates locks down the domain boundary. During week-one exploratory product discovery—where underlying data models change daily and zero real customer capital or tenant PII is at risk—defer heavy verification harnesses until the core workflow contract stabilizes.

References & Further Reading

  1. Malkov, Y. A., & Yashunin, D. A. (2018). Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs. IEEE TPAMI. https://arxiv.org/abs/1603.09320 ↩︎
  2. Jégou, H., Douze, M., & Schmid, C. (2011). Product Quantization for Nearest Neighbor Search. IEEE TPAMI. https://hal.inria.fr/inria-00514462v2/document ↩︎
  3. Johnson, J., Douze, M., & Jégou, H. (2019). Billion-scale Similarity Search with GPUs. IEEE Transactions on Big Data. https://arxiv.org/abs/1702.08734 ↩︎
  4. Lin, J., et al. (2024). AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. MLSys. https://arxiv.org/abs/2306.00978 ↩︎
  5. Frantar, E., et al. (2023). GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. ICLR. https://arxiv.org/abs/2210.17323 ↩︎
  6. Wang, H., et al. (2023). BitNet: Scaling 1-bit Transformers for Large Language Models. arXiv. https://arxiv.org/abs/2310.11453 ↩︎