RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Eruo Fredoline
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /RAG Systems: Part 1
  6. /Ch. 13
RAG Systems: Part 1

13. Dense Retrieval

Chapter 13 of 22 · 20 min
KEY INSIGHT

Dense retrieval quality depends more on embedding model choice and fine-tuning than on index parameters.

Dense retrieval converts text into high-dimensional vectors where semantically similar content clusters together. The search operation becomes finding the nearest neighbors in this vector space.

How Embedding Models Work

An embedding model maps text to a fixed-size vector (typically 384 to 1536 dimensions). The model encodes semantic meaning, not word frequency. "The cat sat on the mat" and "A feline rested on the rug" produce vectors closer together than their word overlaps suggest.

from your_rag_library import EmbeddingModel

model = EmbeddingModel(
    name="BAAI/bge-large-en-v1.5",
    dimension=1024,
    batch_size=32
)

# Encode single text
query_vector = model.encode("How does authentication work?")
print(f"Vector shape: {query_vector.shape}")  # (1024,)

# Encode batch
texts = ["Text chunk 1", "Text chunk 2", "Text chunk 3"]
chunk_vectors = model.encode(texts)
print(f"Batch shape: {chunk_vectors.shape}")  # (3, 1024)

The model choice matters. BGE, E5, and GTE models from HuggingFace outperform OpenAI Ada-002 on many benchmarks. MTEB leaderboard ranking helps compare models.

Vector Search Implementation

Vector search uses approximate nearest neighbor (ANN) algorithms. Exact search on 1 million vectors takes seconds. ANN achieves 99% accuracy in milliseconds through algorithmic shortcuts.

from your_rag_library import VectorStore, HNSWIndex

store = VectorStore(
    dimension=1024,
    index_type=HNSWIndex,
    index_params={
        "m": 16,      # Connections per node (higher = more accurate, slower)
        "ef_construction": 200,  # Build-time quality
        "ef_search": 50    # Search-time quality
    }
)

# Add chunks to store
store.add_vectors(
    ids=["chunk_1", "chunk_2", "chunk_3"],
    vectors=chunk_vectors
)

# Search
results = store.search(
    query_vector=query_vector,
    top_k=10,
    metric="cosine"  # or "dot" or "l2"
)

Parameter m=16 works well for most use cases. Increase to 32 or 64 if recall drops below 95%. Increasing ef_search from 50 to 100 improves recall at the cost of search latency.

Filtering with Metadata

Pure vector search ignores document metadata. Production queries often need metadata filters: "Only documents from the last 30 days" or "Only from the API documentation section."

results = store.search(
    query_vector=query_vector,
    top_k=10,
    filters={
        "category": "API",
        "last_updated": {"$gte": "2024-01-01"},
        "version": {"$in": ["v2", "v3"]}
    }
)

The filter runs as a post-processing step. HNSW indexes handle millions of vectors; metadata filtering narrows the candidate set before vector comparison.

Embedding Model Training

Pre-trained models work for general content. Domain-specific content benefits from fine-tuning on your data. Fine-tuning with just 100-1000 curated query-document pairs improves performance significantly:

from your_rag_library import FineTuner

fine_tuner = FineTuner(
    base_model="BAAI/bge-large-en-v1.5",
    negative_strategy="hard"  # Mine hard negatives from corpus
)

fine_tuner.train(
    train_data="training_pairs.jsonl",  # Format: {"query": "", "positive": "", "negative": ""}
    epochs=3,
    learning_rate=2e-5,
    batch_size=16
)

# Export fine-tuned model
fine_tuner.save("models/my-rag-embedder")

Without fine-tuning, medical RAG systems fail on specialized terminology. Without fine-tuning, legal RAG systems miss case citations and statute references.

EXERCISE

Compare dense retrieval performance (hit rate at k=10) between a general embedding model and a domain-specific fine-tuned model on your target corpus. Expect 15-30% improvement with fine-tuning.

← Chapter 12
Retrieval Strategies
Chapter 14 →
Sparse Retrieval (BM25)