Skip to main content
Embeddings are the core of OpenGround’s semantic search. They transform text chunks into numerical vectors that capture meaning, enabling the system to find relevant documentation even when query words don’t exactly match.

What Are Embeddings?

An embedding is a dense vector representation of text. Similar concepts have vectors that are close together in high-dimensional space.
OpenGround uses cosine similarity to measure how close vectors are:
  • Score near 1.0 = very similar meaning
  • Score near 0.0 = unrelated

Embedding Backends

OpenGround supports two embedding backends with different trade-offs:

FastEmbed

Default - Lightweight, ONNX-based
  • Smaller install size
  • CPU-optimized by default
  • Optional GPU support (experimental)
  • Fastest for CPU inference

Sentence-Transformers

Full-featured - PyTorch-based
  • Larger install size
  • Automatic GPU/MPS detection
  • Better GPU performance
  • More model options

Backend Selection

From config.py:62, the default backend:
Change it with:

FastEmbed Backend

FastEmbed uses ONNX Runtime for inference (from embeddings.py:93-116):
ONNX (Open Neural Network Exchange) is an optimized runtime for neural networks. FastEmbed converts PyTorch models to ONNX for faster CPU inference.

Installation Options

GPU support requires matching CUDA drivers and cuDNN versions. See ONNX Runtime CUDA docs for requirements.

GPU Compatibility Check

OpenGround automatically detects GPU availability (from embeddings.py:44-90):

Sentence-Transformers Backend

Sentence-Transformers uses PyTorch with automatic hardware acceleration (from embeddings.py:14-25):

Installation

The default openground package includes sentence-transformers with automatic GPU/MPS/CPU support. This is the easiest option if you have a GPU.

Embedding Models

From config.py:58-60, the default model:

Why BGE-Small-EN-v1.5?

  • Multilingual: Good English performance
  • Compact: 384 dimensions (vs 768 for larger models)
  • Fast: Smaller vectors = faster search
  • Quality: Strong performance on MTEB benchmarks

Changing Models

Important: Changing the embedding model requires re-embedding all documentation. Models are not compatible with each other.
1

Choose a Model

Browse models on Hugging Face:Look for:
  • Dimensions: 384-768 (smaller = faster)
  • Language: Match your docs (multilingual, en, etc.)
  • Size: Smaller models = faster inference
2

Update Configuration

3

Delete Existing Embeddings

This deletes the LanceDB table but preserves raw documentation.
4

Re-embed Documentation

This processes all raw data with the new model.

Model Compatibility Validation

OpenGround stores embedding metadata in the LanceDB schema (from ingest.py:159-177):
When adding new documentation, OpenGround validates the model matches (from ingest.py:111-142):
This prevents mixing embeddings from different models, which would break search quality.

Embedding Generation

From embeddings.py:207-234, the main generation function:

Batch Processing

Both backends process embeddings in batches for efficiency (from config.py:65):
From embeddings.py:119-160 (sentence-transformers example):
Increase batch_size if you have a GPU with lots of VRAM:

FastEmbed Passage Embedding

FastEmbed distinguishes between passage (document) and query embeddings (from embeddings.py:163-204):
Some models are trained differently for documents vs. queries. FastEmbed uses passage_embed() for document chunks and would use query_embed() for search queries (though OpenGround currently uses passage_embed for both).

Embedding Dimensions

From config.py:60:
Dimension count affects:
Each vector = dimensions × 4 bytes (float32)
Rule of thumb: Stick with the model’s native dimensions. Don’t try to change dimensions independently from the model.

Configuration Examples

Optimal CPU Performance

GPU Performance (NVIDIA)

Apple Silicon (M1/M2/M3)

Apple Silicon automatically uses MPS (Metal Performance Shaders) via sentence-transformers. No special configuration needed.

Chunking Strategy

Before embedding, documents are split into chunks (from config.py:66-67):
From ingest.py:52-76, using LangChain’s text splitter:

Why 800 Characters?

  • Context window: Most embedding models handle 512 tokens well
  • 800 chars ≈ 200 tokens: Safe margin for tokenization
  • Not too small: Preserves context
  • Not too large: Enables precise retrieval

Why 200 Character Overlap?

Overlap ensures:
  • Information spanning boundaries isn’t lost
  • Better retrieval for queries matching boundary content
  • 25% overlap provides good coverage without excessive duplication

Adjusting Chunking

Changing chunk settings requires re-embedding:

Model Caching

Both backends use @lru_cache to load models once (from embeddings.py:14 and 93):
Models are:
  1. Downloaded from Hugging Face (first run)
  2. Cached locally in ~/.cache/huggingface/
  3. Loaded into memory once per process
  4. Reused for all embedding operations
The first run downloads the model (~100-500MB depending on model). Subsequent runs are instant.

Performance Comparison

Best for: Most users, CPU-only machines
  • ~500 chunks/sec (CPU)
  • Lightweight install
  • Low memory usage
  • No GPU setup hassle
Performance varies by hardware. These are approximate estimates for the default model.

Next Steps

Search

Learn how embeddings power hybrid search

Configuration

Full configuration reference for embeddings

Architecture

See where embeddings fit in the architecture

Update Documentation

Efficiently update docs with incremental re-embedding