Vector Embeddings Cheat Sheet
Generate, store, and search dense vector embeddings for semantic search, covering models, distance metrics, and vector databases.
Generate an Embedding
Convert text into a fixed-length dense vector using a sentence embedding model.
from sentence_transformers import SentenceTransformermodel = SentenceTransformer("all-MiniLM-L6-v2") # 384-dim vectorssentences = ["How do I reset my password?", "Password reset instructions"]embeddings = model.encode(sentences, normalize_embeddings=True)print(embeddings.shape) # (2, 384)
Compute Similarity
Measure semantic closeness between two vectors with cosine similarity.
import numpy as npdef cosine_similarity(a, b): return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))score = cosine_similarity(embeddings[0], embeddings[1])print(f"similarity: {score:.4f}") # closer to 1.0 = more similar
Store and Query in a Vector Database
Upsert embeddings into a vector database and run an approximate nearest-neighbor query.
import pineconepc = pinecone.Pinecone(api_key="...")index = pc.Index("docs")index.upsert(vectors=[ ("doc-1", embeddings[0].tolist(), {"text": sentences[0]}), ("doc-2", embeddings[1].tolist(), {"text": sentences[1]}),])query_vec = model.encode("reset my login").tolist()results = index.query(vector=query_vec, top_k=3, include_metadata=True)
Distance Metrics
The most common ways to compare two vectors, and when to use each.
- Cosine similarity- angle between vectors; ignores magnitude, most common for text
- Dot product- fast, magnitude-sensitive; use with normalized embeddings for ranking
- Euclidean (L2) distance- straight-line distance; common for image and general-purpose embeddings
- Hamming distance- bit differences between binary/quantized vectors, very fast
- HNSW index- graph-based ANN index trading recall for speed at scale
Popular Vector Databases
Common storage backends for production embedding search.
- pgvector- Postgres extension, good when you already run Postgres
- Pinecone- managed, serverless, low-ops vector search
- Qdrant- open source, self-hostable, strong filtering support
- Weaviate- open source with built-in hybrid search and modules
- Chroma- lightweight, embedded, ideal for prototyping and small apps
Compress Vectors with Product Quantization
Shrink memory footprint of large indexes by encoding vectors into compact PQ codes with FAISS.
import faissd = 384 # embedding dimensionm = 48 # number of sub-quantizers (must divide d)nbits = 8 # bits per sub-vector codequantizer = faiss.IndexFlatL2(d)index = faiss.IndexIVFPQ(quantizer, d, 100, m, nbits)index.train(training_vectors) # needs representative sample, e.g. 50k+ vectorsindex.add(all_vectors)index.nprobe = 10 # cells to search at query timedistances, ids = index.search(query_vectors, k=10)print(f"compressed size ~{m * nbits / 8}B per vector vs {d * 4}B uncompressed")
Tune an HNSW Index
Trade off build time, memory, and recall by tuning HNSW's M and efConstruction/efSearch parameters.
import faissd = 384M = 32 # graph connectivity, higher = better recall, more memoryindex = faiss.IndexHNSWFlat(d, M)index.hnsw.efConstruction = 200 # build-time search depthindex.add(embeddings)index.hnsw.efSearch = 128 # query-time search depth, tune per latency budgetD, I = index.search(query_vecs, k=10)# rule of thumb: efSearch >= k, and increase until recall@k plateaus on a held-out set
Truncate Matryoshka (MRL) Embeddings
Use Matryoshka Representation Learning embeddings at a smaller dimension for cheaper storage with minimal recall loss.
from sentence_transformers import SentenceTransformerimport numpy as npmodel = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5") # MRL-trainedfull = model.encode(sentences, normalize_embeddings=False) # e.g. 768-dim# truncate to the first N dims, then re-normalizedim = 256truncated = full[:, :dim]truncated = truncated / np.linalg.norm(truncated, axis=1, keepdims=True)# store `truncated` in the vector DB: ~3x less storage, small recall drop vs full dim
ANN Index Families
Core algorithmic approaches behind approximate nearest-neighbor search, beyond the single HNSW mention already covered.
- IVF (Inverted File)- clusters vectors into cells via k-means, searches only nprobe nearest cells
- IVF-PQ- combines IVF's coarse quantization with PQ's compressed codes for billion-scale indexes
- ScaNN- Google's anisotropic vector quantization, optimized for dot-product ranking
- LSH (Locality-Sensitive Hashing)- hashes similar vectors into the same buckets; simple but lower recall than graph methods
- DiskANN / Vamana- graph index designed to be served mostly from SSD for indexes too large for RAM
- recall@k- fraction of true top-k neighbors returned by the approximate search, the key ANN quality metric
Multi-Vector Retrieval with ColBERT
Encode each token separately and score with MaxSim for finer-grained relevance than single-vector embeddings.
from colbert import Searcherfrom colbert.infra import ColBERTConfigconfig = ColBERTConfig(nbits=2, root="./colbert_index")searcher = Searcher(index="docs.nbits2", config=config)# late interaction: each query token is matched against every doc token,# then scored via MaxSim(q_i, d_j) summed over query tokensresults = searcher.search("how does gradient clipping work", k=10)for doc_id, rank, score in zip(*results): print(rank, doc_id, score)
Normalize embeddings at generation time so you can use the cheaper dot-product metric in your vector index — it's mathematically equivalent to cosine similarity but avoids a normalization step on every query.