IVF — carve the space into cells

IVF runs k-means once to split the space into cells around centroids, then at query time scans only the few cells nearest the query — turning an all-N scan into a small fraction of it, the first explicit recall-for-speed trade.

Previously

Flat touched all N vectors; IVF carves the space into cells and touches only the nprobe cells near the query, buying a 10-100× speedup for a sliver of recall. But that sliver has a specific, repeatable shape — and it's worth its own scene, because it's the gotcha that bites every junior who ships IVF.

Scene 04

IVF — carve the space into cells

  1. Watch
  2. Try it
  3. Predict
  4. Capture
acoustic → electroniccalm → energeticLo-fi RainAcoustic Suns…Campfire FolkCoffeehouseIndie DriveSynth DawnNeon CityClub PulseRave PeakBass DropMidnight DriveGarage BeatStudy BeatsLonely SynthNow Playingk-means is planting centroids…RECALL vs LATENCYrecallslower →FlatIVF
What to watch for

Flat's whole problem was that it compared the query against every single song. IVF — short for inverted file index — fixes that with one upfront step: it runs a clustering pass called k-means that drops a handful of marker points (centroids) into the space, and every song joins whichever centroid it's nearest to. The colored region a centroid owns is its cell. Watch the centroids drop in and the boundaries snap shut — the map is now carved into a few neighborhoods. The query 'Now Playing' lands squarely in the middle cell, alongside Midnight Drive, Synth Dawn, and Study Beats. (A common rule of thumb sizes the number of cells around 4·√N to 16·√N — small enough that each holds a real neighborhood.)

Continue unlocks when the animation finishes.
Implementation

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

IVF.train
one upfront k-means pass carves the space into cells
def train(vectors, nlist):
# Lloyd's algorithm: nlist marker points
centroids = kmeans(vectors, k = nlist)
# inverted lists: cell_id -> vectors in that cell
lists = {c: [] for c in range(nlist)}
for v in vectors:
c = argmin(dist(v, centroids)) # nearest cell
lists[c].append(v)
return centroids, lists
IVF.coarseQuantize
pick which cells to open for this query
def probe_set(query, centroids, nprobe):
# rank cells by centroid distance to the query
ordered = sort(
range(nlist),
key = lambda c: dist(query, centroids[c]),
)
# open only the nprobe nearest; skip the rest
return ordered[:nprobe]
IVF.search
scan only the opened cells, then keep the top-k
def search(query, centroids, lists, nprobe, k):
cells = probe_set(query, centroids, nprobe)
candidates = []
for c in cells: # not all nlist cells
for v in lists[c]: # only the opened lists
candidates.append((dist(query, v), v))
return smallest_k(candidates, k)

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

Scene 04 of 15, in the First index act — Flat baseline, IVF cells, and the boundary miss that bites every junior.. k-means splits the space into Voronoi cells; at query time scan only the nprobe cells nearest the query — turning an all-N scan into a fraction of it. The first explicit recall-for-speed trade.

Up next. We saw 'Lonely Synth' vanish at nprobe=1. That wasn't bad luck — it's the signature failure of cell-based search, and it happens to the obvious neighbor sitting just on the wrong side of a boundary. Let's freeze that one picture and understand exactly why a clearly-close point gets missed.

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