brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
ann-algorithmsApproximate Nearest Neighbor algorithm theory and selection. HNSW, IVF, IVF+PQ, ScaNN, DiskANN, LSH. Covers parameter effects (m, ef, nlist, nprobe), recall/latency/memory tradeoffs, exhaustive vs approximate, and when to pick which index. USE WHEN: user mentions "ANN", "HNSW", "IVF", "nearest neighbor", "ScaNN", "DiskANN", "LSH", "recall vs latency", "ind…
Independent GitHub snapshot · Automated classification · Original documentation governs
name: ann-algorithms description: | Approximate Nearest Neighbor algorithm theory and selection. HNSW, IVF, IVF+PQ, ScaNN, DiskANN, LSH. Covers parameter effects (m, ef, nlist, nprobe), recall/latency/memory tradeoffs, exhaustive vs approximate, and when to pick which index.
USE WHEN: user mentions "ANN", "HNSW", "IVF", "nearest neighbor", "ScaNN", "DiskANN", "LSH", "recall vs latency", "index type choice", "FAISS index"
vector-stores/*-advanced skill; quantization details - use vector-quantization
allowed-tools: Read, Grep, Glob, Write, EditExhaustive (brute-force) search compares the query against every stored vector — 100% recall, O(N) per query. Approximate Nearest Neighbor (ANN) trades a few percent recall for 10-1000x speedup.
Break-even heuristic:
flat in FAISS / flat in ES).Multi-layer graph where upper layers are sparse long-range links and lower layers are dense local links. Search starts at the top, greedy-descends to the bottom.
| Param | Meaning | Typical | Effect of raising |
|---|---|---|---|
M | max neighbors per node per layer | 16-32 | More memory, better recall, slower build |
ef_construction | neighbors considered at build | 100-400 | Slower build, higher-quality graph |
ef_search | neighbors considered at query | 40-200 | Slower queries, higher recall |
Memory footprint per vector: 4*d + 8*M + 4*M bytes (vector + neighbor IDs + neighbor distances per layer).
import faiss, numpy as np
d = 1024
index = faiss.IndexHNSWFlat(d, 32) # M=32
index.hnsw.efConstruction = 200
index.add(corpus) # corpus: (N, d) float32
index.hnsw.efSearch = 100
D, I = index.search(queries, k=10)
Cluster vectors into nlist Voronoi cells. At query time, search only the nprobe nearest cells.
| Param | Meaning | Typical |
|---|---|---|
nlist | # clusters | sqrt(N) to N/1000 |
nprobe | clusters scanned at query | 1-256 |
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFFlat(quantizer, d, nlist=4096, faiss.METRIC_L2)
index.train(sample) # needs a representative training set (~256*nlist points)
index.add(corpus)
index.nprobe = 32
D, I = index.search(queries, 10)
IVF alone (IVFFlat) is memory-efficient (no graph) but less accurate than HNSW at equal memory. Its real strength is combining with PQ.
Product Quantization splits each vector into m subvectors and quantizes each to one of ksub centroids. Combines with IVF for massive compression.
m = 64 # subvector count (must divide d)
nbits = 8 # bits per code per subvector -> 256 centroids
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFPQ(quantizer, d, nlist=8192, m, nbits)
index.train(sample)
index.add(corpus)
index.nprobe = 64
Memory: m bytes per vector (e.g. 64 bytes vs 4096 bytes for d=1024). Enables
100M+ vector indexes on a single machine. Pair with reranking on full vectors
for top-K.
Uses anisotropic vector quantization tuned for maximum inner product search. Typically 2-4x faster than HNSW at equal recall on dense embeddings, especially with quantization.
import scann
import numpy as np
searcher = (
scann.scann_ops_pybind.builder(corpus, 10, "dot_product")
.tree(num_leaves=2000, num_leaves_to_search=100, training_sample_size=250_000)
.score_ah(dimensions_per_block=2, anisotropic_quantization_threshold=0.2)
.reorder(100)
.build()
)
neighbors, distances = searcher.search_batched(queries)
Best for: read-heavy, inference-server-style workloads with rebuild windows. Updates require a retrain.
Graph-based index designed for SSDs. Stores the graph + full vectors on disk, loads only what a query needs.
Strengths:
Costs:
Used under the hood by Vespa, Milvus (disk variant), and some managed offerings.
Hash-based; projects vectors into buckets via random hyperplanes. Fast insert, fast query, but recall is mediocre on modern embeddings.
index = faiss.IndexLSH(d, nbits=1024) # nbits = number of hash bits
index.add(corpus)
D, I = index.search(queries, 10)
Modern use cases: dedup / near-duplicate detection on huge, low-dim, sparse-ish data, or as a prefilter before reranking. NOT the first choice for neural embeddings.
| Scenario | Best choice |
|---|---|
| <100k vectors, needs 100% recall | Brute-force (flat) |
| 100k-10M, fits in RAM, <50ms latency | HNSW |
| 10M-100M, fits in RAM but tight | HNSW + scalar/binary quantization |
| 100M-1B, limited RAM | IVF+PQ or DiskANN |
| Read-only, top-recall at low latency | ScaNN |
| Insert-heavy stream | HNSW with periodic rebuild, or IVF with re-train |
| SSD-only / data residency per-box | DiskANN |
| Near-duplicate detection | LSH or MinHash |
Memory Latency Recall
----- ------- ------
Flat worst best recall, slowest, full vectors only
HNSW high low latency, near-perfect recall
IVFFlat low moderate latency, good recall
IVFPQ lowest fast, lower recall (rescore to recover)
DiskANN disk moderate latency, good recall on disk
ScaNN low lowest latency, high recall (static data)
LSH low fastest, poor recall on dense
Rule: tune for a target recall (say 95% or 98%), then compare QPS and RAM per provider.
import numpy as np, time
def recall_at_k(gt: np.ndarray, pred: np.ndarray, k: int) -> float:
# gt, pred: (Q, k) arrays of doc IDs
return np.mean([
len(set(pred[i, :k]) & set(gt[i, :k])) / k
for i in range(len(gt))
])
# Compute ground truth with brute force on a sample
flat = faiss.IndexFlatL2(d)
flat.add(corpus)
_, gt = flat.search(queries, 10)
for ef in [32, 64, 128, 256, 512]:
index.hnsw.efSearch = ef
t = time.perf_counter()
_, pred = index.search(queries, 10)
ms = (time.perf_counter() - t) / len(queries) * 1000
print(f"ef={ef:3d} recall@10={recall_at_k(gt, pred, 10):.3f} {ms:.2f} ms/q")
| Anti-Pattern | Fix |
|---|---|
| Picking HNSW for a 500M-vector dataset with 64 GB RAM | Use IVF+PQ or DiskANN |
| LSH for dense neural embeddings | Use HNSW/IVF; LSH is for sparse/minhash-style data |
| Not retraining IVF centroids after distribution shift | Retrain on fresh sample periodically |
Ignoring num_candidates / ef_search tuning | Sweep per index, report recall@k curve |
| Trusting library defaults | Every workload is different; benchmark |
| Benchmarking recall without a ground-truth flat baseline | Build IndexFlat on a sample for truth |
| Re-adding all vectors to HNSW after every update | Use incremental updates; rebuild on a schedule |