Product Quantization — 6 KB to 96 bytes

Product Quantization splits each vector into m subvectors and replaces each with the 1-byte id of its nearest prototype from a 256-entry codebook, shrinking a 6 KB vector to ~96 bytes (~64×) and finally making the memory axis a live knob — at the cost of lossy, approximate distances.

Previously

HNSW's RAM bill is the old 6 TB wall in disguise. PQ chops each vector into chunks and stores only which of 256 prototypes each chunk is closest to — 6 KB becomes ~96 bytes. The memory axis is finally a knob we can turn. But the codes are lossy, so recall drops — which raises the question of how to get HNSW-or-better speed AND PQ's tiny footprint at the same time.

Scene 08

Product Quantization — 6 KB to 96 bytes

  1. Watch
  2. Try it
  3. Predict
  4. Capture
VECTOR · 1,536 dims · 96 subvectorsd0d1d2d3d4d5d6d7d8d9d10d11d12d13d14d15d16d17d18d19d20d21d22d23… 96each chunk = 16 dims = 64 raw bytesCODEBOOK · 256 prototypesnearest prototype → 1 byte (id 0–255)PQ CODE · 96 bytes-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1-1… 96BYTES PER VECTORraw float326,144→PQ code9664×smallerRECALL0.95lossyPQ approx onlyRECALL vs LATENCYrecall ↑latency →FlatHNSWIVFPQEach chunk → 1 of 256 prototypes → 1 byte. 6,144 bytes → 96 bytes.
What to watch for

Here's the move that finally shrinks memory. Take one song's vector — 1,536 numbers, stored as 4-byte floats, so 6,144 bytes (~6 KB) each. Slice it into m equal chunks. For each chunk, we trained a little catalogue of 256 typical chunk-shapes once, up front (the same k-means idea that drew IVF's cells, run separately inside each slice of the vector). Now we just store, per chunk, which of those 256 shapes it's nearest to — a number from 0 to 255, which fits in a single byte. Watch the chunks get rounded one by one and the byte counter fall: 6,144 bytes collapses to 96. That's a ~64× shrink. Distances later get estimated from those stored numbers using a small precomputed lookup table.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

PQ.encode
store each chunk as the id of its nearest prototype
def encode(vector, codebooks): # m codebooks, 256 each
chunks = split(vector, m)
codes = bytearray(m)
for j, chunk in enumerate(chunks):
best = argmin(
dist(chunk, proto)
for proto in codebooks[j] # 256 protos
)
codes[j] = best # 0..255, one byte
return codes # m bytes total
PQ.searchADC
score the codes against a full-precision query
def search(query, codes_table):
chunks = split(query, m) # query stays full-precision
lut = [
[dist(chunks[j], proto) for proto in codebooks[j]]
for j in range(m) # 256-wide table per chunk
]
scores = []
for codes in codes_table: # every stored vector
d = sum(lut[j][codes[j]] for j in range(m))
scores.append(d) # summed table lookups
return argsort(scores)
PQ.rerank
re-score the shortlist with exact vectors
def topK(query, k=10):
shortlist = search(query)[:N] # N >> k, cheap codes
if not rerank_enabled:
return shortlist[:k]
rescored = [
(exact_dist(query, full_vectors[i]), i)
for i in shortlist # only N exact reads
]
return [i for _, i in sorted(rescored)][:k]

Where this sits in Build a vector database (Pinecone / Weaviate / pgvector style)

Scene 08 of 15, in the Compress & pick act — PQ shrinks 6 KB to 96 bytes, IVFPQ combines both, and the trilemma forces a choice.. Split each vector into subvectors and store the 1-byte id of each one's nearest prototype: 6 KB → ~96 bytes (~64×). The memory axis finally becomes a knob — at the cost of lossy distances.

Up next. PQ shrank memory but, on its own, still scans codes for every vector. IVF skipped most vectors but stored them full-size. The obvious move is to combine them: use IVF cells to skip most of the data, and PQ to compress what remains. That combination — IVFPQ — is the billion-scale workhorse.

All 15 scenes in Build a vector database (Pinecone / Weaviate / pgvector style) · Every curriculum

Built with Arqly
Every scene in Build a vector database (Pinecone / Weaviate / pgvector style) builds on the one before it.All 15 Build a vector database (Pinecone / Weaviate / pgvector style) scenes