05 Foundations 11 min read 2,402 words

Embeddings and Vector Spaces

Embeddings are dense vector representations of text that capture semantic meaning. They are foundational to RAG systems, semantic search, and many AI applications.

embeddingsretrievalchunkingfundamentalscore
01embeddings

What Are Embeddings

Embeddings map discrete text (words, sentences, documents) to continuous vector spaces where semantic similarity corresponds to geometric proximity.

Key properties:

  • Similar meanings are close together
  • Relationships can be encoded as vector operations (king - man + woman = queen)
  • Enable efficient similarity search through approximate nearest neighbor algorithms

Mental model: Think of embeddings as coordinates in a very high-dimensional space. Dimensionality (512 to 4096) provides expressiveness. Each dimension captures some aspect of meaning, though individual dimensions are not interpretable.

02model architectures

Embedding Model Architectures

2.1

Word Embeddings (Historical)

Early approaches embedded individual words:

ModelYearApproachLimitation
Word2Vec2013Skip-gram, CBOWStatic: "bank" same in all contexts
GloVe2014Co-occurrence matrixStatic
FastText2017Subword embeddingsStatic, but handles OOV

Key limitation: Same word gets same embedding regardless of context.

2.2

Contextual Embeddings

Transformer-based models produce context-dependent embeddings:

Pythonpython · 6 lines
123456
# Static embedding (Word2Vec)
embed("bank") = [0.1, 0.3, ...]  # Same vector always

# Contextual embedding (BERT)
embed("river bank") = [0.1, 0.3, ...]   # Geography sense
embed("bank account") = [0.5, 0.2, ...]  # Finance sense
2.3

Sentence/Document Embeddings

For retrieval, we need to embed entire texts:

ApproachMethodProsCons
Mean poolingAverage token embeddingsSimpleLoses information
CLS tokenUse [CLS] token embeddingStandard for BERTMay not capture full text
Last tokenUse final tokenWorks for decoder modelsPosition bias
Trained poolingLearn pooling weightsBetter qualityRequires training

Modern embedding models are trained specifically for sentence/document embedding, not just adapted from language models.

2.4

Bi-Encoder Architecture

Standard retrieval embedding architecture:

Texttext · 4 lines
1234
Document -> Encoder -> Document Embedding
Query    -> Encoder -> Query Embedding

Similarity = cosine(doc_embedding, query_embedding)

Properties:

  • Documents can be pre-computed and indexed
  • Query embedding computed at query time
  • O(1) similarity computation per document (with ANN)
2.5

Cross-Encoder Architecture

Alternative that processes query and document together:

Texttext · 1 line
1
[Query, Document] -> Encoder -> Relevance Score

Properties:

  • More accurate (sees both together)
  • Cannot pre-compute: O(n) inference for n documents
  • Used for reranking, not retrieval
03objectives

Training Objectives

3.1

Contrastive Learning

Most modern embedding models use contrastive learning:

Pythonpython · 9 lines
123456789
# Simplified contrastive loss
def contrastive_loss(anchor, positive, negatives):
    pos_sim = cosine_similarity(anchor, positive)
    neg_sims = [cosine_similarity(anchor, neg) for neg in negatives]
    
    # Push positive close, negatives far
    loss = -log(exp(pos_sim / tau) / 
                (exp(pos_sim / tau) + sum(exp(neg_sim / tau) for neg_sim in neg_sims)))
    return loss

Key factors:

Positive pairs

Semantically similar texts (parallel sentences, query-document pairs)

Hard negatives

Similar but not matching texts (BM25 retrieved non-relevant)

In-batch negatives

Other batch items as negatives (efficient)

3.2

Training Data Sources

SourcePositive PairsQualityScale
Parallel sentencesTranslation pairsHighMedium
Query-documentSearch logsHighMedium
Title-bodyDocument structureMediumLarge
ParaphraseNLI datasetsHighSmall
GeneratedLLM creates pairsVariableLarge
3.3

Instruction-Tuned Embeddings

Recent models accept task instructions:

Pythonpython · 3 lines
123
# Instruction-tuned (e.g., E5, BGE)
query_embedding = embed("Represent this query for retrieval: What is RAG?")
doc_embedding = embed("Represent this document for retrieval: RAG combines...")

This improves performance by specifying the intended use.

04metrics

Distance Metrics

4.1

Cosine Similarity

Most common for text embeddings:

Pythonpython · 2 lines
12
def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

Properties:

  • Range: [-1, 1] (for normalized vectors, [0, 1] if positive)
  • Measures angle, not magnitude
  • Invariant to vector length

When to use: Default choice for text embeddings.

4.2

Dot Product

Pythonpython · 2 lines
12
def dot_product(a, b):
    return np.dot(a, b)

Properties:

  • Magnitude matters
  • Unbounded range
  • Equivalent to cosine for normalized vectors

When to use: When embeddings are already normalized, or magnitude is meaningful.

4.3

Euclidean Distance

Pythonpython · 2 lines
12
def euclidean_distance(a, b):
    return np.linalg.norm(a - b)

Properties:

  • Measures absolute difference
  • Affected by magnitude
  • For normalized vectors: sqrt(2 - 2 * cosine)

When to use: Rarely for text; more common for image embeddings.

4.4

Metric Selection

MetricVector DatabasesCommon Use
CosinePinecone, Qdrant, WeaviateText embeddings
Dot ProductAll major DBsNormalized embeddings
EuclideanAll major DBsImage, multimodal
05model comparison

Embedding Model Comparison

5.1

Current Top Models (December 2025)

ModelDimensionsMax TokensMTEB RetrievalCost / 1M tokens
OpenAI text-embedding-4307216k68.2$0.10
Voyage-41024128k70.1$0.05
Cohere embed-v3.5102451267.5$0.10
Google text-embedding-0057688k67.2$0.02

MTEB scores represent late 2025 frontier standards.

MTEB scores are approximate and vary by benchmark subset. Always verify current values.

5.2

Open Source Models

ModelDimensionsMax TokensMTEB RetrievalNotes
BGE-large-en-v1.5102451263.9Strong open model
E5-large-v2102451262.4Instruction-tuned
GTE-large102451263.1Alibaba
Nomic-embed-text-v1.5768819262.3Long context, open
5.3

Selection Criteria

FactorConsiderations
Quality (MTEB)Higher is better, but task-specific evaluation matters more
DimensionsHigher = more expressive but more storage/compute
Max tokensMust accommodate your document sizes
CostAPI vs self-hosting tradeoffs
LatencyEmbedding generation time
MultilingualIf serving non-English content
06adaptive dimensions

Matryoshka and Adaptive Dimensions

6.1

The Idea

Matryoshka Representation Learning (MRL) trains embeddings such that prefixes of the full embedding are also meaningful:

Pythonpython · 7 lines
1234567
full_embedding = model.encode(text)  # 1024 dimensions

# All these are valid embeddings with decreasing quality
dim_512 = full_embedding[:512]  
dim_256 = full_embedding[:256]
dim_128 = full_embedding[:128]
dim_64 = full_embedding[:64]
6.2

Why It Matters

Use CaseDimensionTradeoff
Full Retrieval1024-3072Peak Accuracy
Two-Stage Retrieval128 -> 1024The 2025 Standard: Retrieve 1000 with 128-d, refine top 100 with 1024-d.
Cost-sensitive25612x storage savings, <2% MRR loss
Edge / Mobile64Maximum speed, handles simple intent
6.3

Models with Matryoshka Support

  • OpenAI text-embedding-3-* (native)
  • Nomic-embed-text-v1.5
  • Several fine-tuned models
6.4

Using Matryoshka Embeddings

Pythonpython · 9 lines
123456789
from openai import OpenAI
client = OpenAI()

# Request smaller dimensions
response = client.embeddings.create(
    model="text-embedding-3-large",
    input="Your text here",
    dimensions=256  # Request 256 instead of full 3072
)
6.5

Late Chunking (The 2025 Shift)

Traditional Chunking: Document -> Split into chunks -> Embed chunks individually

  • Issue: Chunk 2 loses the context from Chunk 1.

Late Chunking (introduced by Jina AI/Voyage): Full Document -> Model Encoder -> Token-level Embeddings -> Pool into chunk boundaries

  • Benefit: Each chunk's embedding contains information from the entire document because the transformer's self-attention was applied to the full sequence before pooling.
  • Requirement: A model with long-context support (at least 8k+ tokens).
07scale

Quantization for Scale

To handle billions of vectors, Binary and Scalar (Int8) quantization are now standard.

TypeData SizeMemory SavingsQuality LossSupported By
Float324 bytes/dimBaseline0%All
Int81 byte/dim4x<1%Cohere, BGE
Binary1 bit/dim32x~5-10%Cohere v3, v4

Binary Quantization Pattern:

  1. Retrieve top 1000 using Binary embeddings (extreme speed).
  2. Rerank top 50 using Float32 or a Cross-Encoder (peak accuracy).
7.1

When to Use ColBERT

  • Retrieval precision is critical
  • Can afford storage overhead
  • Query latency budget > 50ms
7.2

Implementation

Pythonpython · 13 lines
12345678910111213
# Using RAGatouille
from ragatouille import RAGPretrainedModel

model = RAGPretrainedModel.from_pretrained("colbert-ir/colbertv2.0")

# Index documents
model.index(
    collection=documents,
    index_name="my_index"
)

# Search
results = model.search(query="What is RAG?", k=10)
08considerations

Practical Considerations

8.1

Batch Processing

Pythonpython · 10 lines
12345678910
# Inefficient: one API call per document
embeddings = [embed(doc) for doc in documents]

# Efficient: batch API calls
batch_size = 100
embeddings = []
for i in range(0, len(documents), batch_size):
    batch = documents[i:i + batch_size]
    batch_embeddings = embed_batch(batch)
    embeddings.extend(batch_embeddings)
8.2

Chunking for Embeddings

Long documents must be chunked before embedding:

Pythonpython · 7 lines
1234567
def embed_document(document: str, max_tokens: int = 512) -> list[np.array]:
    chunks = chunk_document(document, max_tokens=max_tokens)
    embeddings = []
    for chunk in chunks:
        embedding = embed(chunk)
        embeddings.append(embedding)
    return embeddings

Considerations:

  • Chunk size should be less than model max tokens
  • Overlap helps preserve context across chunk boundaries
  • Store chunk-to-document mapping for retrieval
8.3

Normalization

Many systems expect normalized embeddings:

Pythonpython · 6 lines
123456
def normalize(embedding):
    norm = np.linalg.norm(embedding)
    return embedding / norm

# Cosine similarity of normalized vectors = dot product
similarity = np.dot(normalize(a), normalize(b))

Most vector databases and embedding APIs handle normalization, but verify.

8.4

Caching

Embedding computation is expensive. Cache aggressively:

Pythonpython · 11 lines
1234567891011
import hashlib

def get_embedding(text: str, cache: dict) -> np.array:
    key = hashlib.sha256(text.encode()).hexdigest()
    
    if key in cache:
        return cache[key]
    
    embedding = compute_embedding(text)
    cache[key] = embedding
    return embedding
09drift versioning

Embedding Drift and Versioning

9.1

The Problem

Embeddings are not comparable across:

  • Different models
  • Different versions of the same model
  • Sometimes different API calls (some APIs have non-determinism)
9.2

Consequences

If you update your embedding model:

  • All existing embeddings become incompatible
  • Must re-embed entire corpus
  • Search results will be inconsistent during migration
9.3

Mitigation Strategies

1. Version your embeddings:

Pythonpython · 6 lines
123456
embedding_metadata = {
    "model": "text-embedding-3-large",
    "model_version": "2024-01",
    "dimensions": 3072,
    "created_at": "2025-12-16"
}

2. Plan for re-embedding:

  • Estimate cost and time for full re-embed
  • Build pipelines that can run in background
  • Test new embeddings before switching

3. Blue-green deployment:

Texttext · 4 lines
1234
Index A: Current embeddings
Index B: New embeddings (building)

Query -> Both indexes -> Merge or switch

4. Track embedding quality:

  • Monitor retrieval metrics continuously
  • Detect drift in embedding distributions
  • Alert on quality degradation
10questions

Interview Questions

Q: How do embedding models learn semantic similarity?

Strong answer: Embedding models are trained with contrastive learning. The objective is to make embeddings of semantically similar texts close together and dissimilar texts far apart.

Training process:

  1. Positive pairs: Texts that should be similar (query-document pairs, paraphrases, translations)
  2. Negative pairs: Texts that should be dissimilar (often from same batch or hard negatives from BM25)
  3. Loss function: Pushes positive pairs close, negative pairs far

The model learns to place texts in a high-dimensional space where distance correlates with semantic similarity. This enables retrieval: embed the query, find nearest neighbors in the document embedding space.

Modern models like E5 and BGE are also instruction-tuned, where you prefix with task instructions to specialize the embedding.

Q: When would you use ColBERT over a bi-encoder?

Strong answer: ColBERT uses late interaction: instead of one embedding per document, it keeps per-token embeddings. At query time, it computes token-level similarity.

Choose ColBERT when:

  • Retrieval precision is critical (legal, medical, high-stakes)
  • You can afford 10-100x storage overhead per document
  • Query latency budget is 50ms+ (slightly slower than bi-encoder)
  • Your queries benefit from lexical matching (technical terms)

Choose bi-encoder when:

  • Storage is constrained
  • Need sub-20ms latency
  • Retrieval precision from bi-encoder is sufficient
  • Frequent re-indexing (ColBERT reindex is expensive)

In practice, a common pattern is: bi-encoder for first-stage retrieval (top 100), then cross-encoder or ColBERT for reranking.

Q: How do you handle embedding drift when updating models?

Strong answer: Embedding models produce vectors that are only meaningful relative to the same model. If you update the model, all old embeddings become incompatible.

My approach:

  1. Never update in place. Create a parallel index with new embeddings.
  2. Test before switching. Compare retrieval quality on a test set with both old and new embeddings.
  3. Background rebuild. Re-embed the entire corpus with the new model in the background.
  4. Atomic switch. Once the new index is complete and validated, switch traffic atomically.
  5. Rollback plan. Keep the old index available for quick rollback.

For cost estimation: if you have 10M documents at 500 tokens average, and text-embedding-3-large costs $0.13/1M tokens, re-embedding costs about $650. Plan for this cost when considering model updates.

Q: How do you choose dimensions for embeddings?

Strong answer: Higher dimensions capture more information but cost more storage and computation.

Considerations:

Storage

1024-d float32 = 4 KB per embedding. At 10M docs = 40 GB just for embeddings.

Search speed

Higher dimensions = slower nearest neighbor search.

Quality

Diminishing returns above certain dimensions for most tasks.

Practical approach:

  1. Start with the model's recommended dimensions.
  2. If using Matryoshka models (like text-embedding-3), experiment with lower dimensions on your task.
  3. Benchmark quality at different dimensions: often 256-512 is 95% of full quality.
  4. For two-stage retrieval: use low dimensions for first stage, full dimensions for reranking.

For most applications, 768-1024 dimensions provide good balance. The exception is very high-precision requirements where 2048-4096 may help.

11references

References

  • Reimers and Gurevych. "Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks" (2019)
  • Khattab and Zaharia. "ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT" (2020)
  • Wang et al. "Text Embeddings by Weakly-Supervised Contrastive Pre-training" (E5, 2022)
  • Xiao et al. "C-Pack: Packaged Resources To Advance General Chinese Embedding" (BGE, 2023)
  • Kusupati et al. "Matryoshka Representation Learning" (MRL, 2022)
  • MTEB Leaderboard: https://huggingface.co/spaces/mteb/leaderboard huggingface.co
  • OpenAI Embeddings Guide: https://platform.openai.com/docs/guides/embeddings platform.openai.com

Previous: Transformer Architecture | Next: Inference Pipeline

summary · added by this rebuild

Key takeaways

01

Bi-encoders index, cross-encoders rerank

A bi-encoder embeds query and document separately so documents can be precomputed; a cross-encoder reads both together, is more accurate, and costs one inference per candidate.

02

Matryoshka embeddings can be truncated

Prefixes of an MRL vector remain valid embeddings, enabling two-stage retrieval: scan a thousand candidates at 128 dimensions, then refine the top hundred at 1024.

03

Binary quantization buys 32x memory

One bit per dimension shrinks storage 32-fold for roughly 5-10 percent quality loss, recovered by reranking the top 50 with float32 vectors or a cross-encoder.

04

Late chunking embeds the document first

Running the encoder across the full document before pooling into chunk boundaries gives every chunk context from the whole text, and needs a model with 8k+ context.

05

Changing embedding model means reindexing

Vectors are incomparable across models and even model versions, so an upgrade requires re-embedding the corpus behind a blue-green index switch with versioned embedding metadata.