Vector search with uncertainty awareness. Knowledge graph + HNSW + GPU in a single Rust binary.
Search returns confidence scores based on κ (concentration) — agents know when to trust results.
SplatsDB is not a Faiss competitor on raw QPS. If you need the fastest possible brute-force ANN on a single metric, Faiss wins. SplatsDB's differentials are:
- Uncertainty-aware retrieval: Queries return confidence scores derived from κ (concentration). Ambiguous or out-of-distribution queries get flagged as "low confidence." No other vector DB does this.
- GraphSplat hybrid search: Vector similarity + knowledge graph traversal in one engine. LangChain and LlamaIndex typically need 3 separate tools (vector store, graph DB, fusion layer) to achieve the same result.
- Agent memory: Built-in MCP server for long-term AI agent memory. Connect to Claude in 2 minutes — no glue code needed.
- Single binary, pure Rust: No Python runtime, no Java, no Docker dependency. One
cargo build --releaseand you're done.
| Feature | SplatsDB | Faiss | Pinecone | Qdrant | Milvus/Zilliz | LanceDB |
|---|---|---|---|---|---|---|
| Language | Rust | C++ | Go/Rust | Rust | Go+C++ | Rust |
| License | GPL-3.0 | MIT | Proprietary | Apache 2.0 | Apache 2.0 | Apache 2.0 |
| Gaussian Splats | ✅ | — | — | — | — | — |
| Uncertainty scores | ✅ | — | — | — | — | — |
| Knowledge Graph | ✅ | — | — | — | — | — |
| Spatial Memory (Wing/Room/Hall/Tunnel) | ✅ | — | — | — | — | — |
| MCP server (18 tools) | ✅ | — | — | — | — | — |
| Distributed sharding (hash/cluster/geo) | ✅ | — | ✅ Cloud | ✅ | ✅ | — |
| Energy-aware routing | ✅ | — | — | — | — | — |
| RRF result fusion | ✅ | — | — | — | ✅ | — |
| Self-hosted | ✅ | ✅ | ❌ SaaS-only | ✅ | ✅ | ✅ |
| Embedded (no server) | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
| GPU custom kernels (14 total) | PTX | CUDA | Cloud | ❌ | ✅ Knowhere | ❌ |
| HNSW | ✅ | ✅ | ✅ | ✅ | ✅ | IVF-PQ |
| Vector compression | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Pricing (cloud managed) | Free | N/A | $50–$500/mo | Usage-based | $99–$155/mo | Free |
Honest note: Faiss remains the gold standard for raw CPU throughput on pure ANN benchmarks (HNSW 14.8× faster QPS on CPU). However, SplatsDB GPU (RTX 3090) beats Faiss CPU at 12,195 QPS. Milvus and Qdrant have richer ecosystem integrations (LangChain, LlamaIndex, managed cloud). SplatsDB's niche is uncertainty-aware retrieval + knowledge graph + agent memory + spatial memory in a single Rust binary with built-in distributed sharding.
- Why SplatsDB?
- What Is This?
- Architecture
- Quick Start
- How It Works
- Benchmarks
- Configuration Presets
- CLI Reference
- MCP Server (AI Agent Integration)
- HTTP API Server
- Rust API
- GPU Acceleration
- Knowledge Graph (GraphSplat)
- Vector Compression (TurboQuant / PolarQuant)
- Semantic Memory
- Roadmap
- Spatial Memory Architecture
- Competitive Landscape
- License
SplatsDB applies Gaussian Splatting — a technique from 3D neural rendering — to vector search. Instead of storing raw embedding vectors, each document is represented as a probabilistic Gaussian (mean μ, opacity α, concentration κ). This enables:
- Richer similarity semantics via splat overlap instead of point-to-point distance
- Natural compression through distribution parameters (3–8 bit quantization)
- Uncertainty-aware retrieval — sparse regions have high energy, guiding active learning
- Knowledge graph overlay — typed entities and relations augment vector retrieval
Combined with a two-level KMeans++ retrieval pipeline (HRM2), HNSW incremental indexing, 14 CUDA GPU kernels (6 distance + 8 extended), and hybrid BM25+vector semantic memory, SplatsDB provides a full-featured vector search engine in ~29K lines of pure Rust + CUDA.
Key use cases:
- AI agent long-term memory (MCP server for Claude, GPT, open-source LLMs)
- Semantic search over document collections
- Knowledge graph construction and retrieval
- GPU-accelerated nearest neighbor search at scale
┌──────────────────────────────────────────────────────────────────┐
│ Interfaces │
│ CLI (clap) │ MCP (stdio JSON-RPC) │ HTTP API │
├──────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ Query Layer │ │ Semantic │ │ GraphSplat │ │
│ │ HRM2 Two- │ │ Memory │ │ Knowledge Graph │ │
│ │ Level │ │ BM25+Vector │ │ Hybrid Search + │ │
│ │ Retrieval │ │ RRF Fusion │ │ BFS Traversal │ │
│ └──────┬───────┘ └──────┬───────┘ └───────┬─────────────┘ │
│ └─────────────────┼──────────────────┘ │
│ │ │
│ ┌────────────────────────▼────────────────────────────────────┐ │
│ │ Gaussian Splat Embeddings │ │
│ │ (mean μ + opacity α + concentration κ) │ │
│ ├──────────────────────────────────────────────────────────────┤ │
│ │ Compression: TurboQuant / PolarQuant (3–8 bit) │ │
│ ├──────────────────────────────────────────────────────────────┤ │
│ │ Indexes: HNSW │ LSH │ KMeans++ (coarse → fine) │ │
│ ├──────────────────────────────────────────────────────────────┤ │
│ │ Persistence: SQLite (WAL) │ GPU: CUDA PTX (optional) │ │
│ └─────────────────────────────┴───────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Download the latest release binary and run:
# Index pre-computed embeddings (any dimension, any provider)
# Format: [u64 n] [u64 dim] [f32 × n × dim]
splatsdb index --input embeddings.bin
# Search with a query vector
splatsdb search --query "0.12,0.45,-0.33,..." -k 10
# Or start MCP server for your AI agent
splatsdb mcp# Step 1: Generate embeddings with your provider
# OpenAI:
python3 -c "
import openai, struct, numpy as np
client = openai.OpenAI()
texts = ['Machine learning basics', 'Neural network architectures']
resp = client.embeddings.create(input=texts, model='text-embedding-3-small')
vecs = np.array([d.embedding for d in resp.data], dtype=np.float32)
n, dim = vecs.shape
with open('embeddings.bin','wb') as f:
f.write(struct.pack('<QQ',n,dim)); f.write(vecs.tobytes())
print(f'Wrote {n} vectors, dim={dim}')
"
# Step 2: Index and search
splatsdb index --input embeddings.bin
splatsdb search --query "0.12,0.45,-0.33,..." -k 10
# Step 3 (optional): MCP server for agents
splatsdb mcp # starts JSON-RPC on stdioNote: The MCP server can auto-embed text using a local embedding service (
all-MiniLM-L6-v2viaSPLATSDB_EMBED_URL). For production, always provide real embeddings — the built-in SimCos hash is for demos only.
- Rust 1.56+ (Edition 2021)
- C compiler (for SQLite bundling via
rusqlite) - CUDA Toolkit 12.x + MSVC
cl.exe(optional, for GPU acceleration)
# CPU-only (no GPU dependencies)
cargo build --release
# With CUDA GPU acceleration
cargo build --release --features cuda
# Run tests
cargo test --lib# Check engine status
./target/release/splatsdb status --verbose
# Search with a query vector
./target/release/splatsdb search --query "0.1,0.2,0.3,...,-0.5" -k 10
# Start MCP server for AI agent integration
./target/release/splatsdb mcp
# Start HTTP API server
./target/release/splatsdb serve --host 0.0.0.0 --port 8199
# Ingest vectors from binary file
./target/release/splatsdb ingest --input vectors.bin --n-clusters 100SplatsDB uses a simple binary format for vector data:
[ u64: number of rows ] [ u64: number of columns ] [ f32 × rows × cols: data ]
Traditional vector databases store raw embedding vectors and compute point-to-point distances. SplatsDB represents each document as a probabilistic Gaussian:
- μ (mean): Position in embedding space — the "center" of the concept
- α (opacity): How strongly this document is represented — akin to importance weight
- κ (concentration): How focused the representation is — high κ means precise, low κ means broad
ELI5: Imagine each document is a paint splat on a wall.
- A doc about "Golden Retriever puppies" has high κ — a tiny, precise dot.
- A doc about "dogs in general" has low κ — a big, blurry splat.
- α is how bright the paint is — important docs are brighter.
- When you search, we measure how much your query overlaps each splat. The AI knows how confident it is about each match.
Similarity is computed as splat overlap (integral of two Gaussians), which naturally captures:
- Documents that cover broad topics (low κ) match more queries, but with lower confidence
- Documents that are very specific (high κ) match fewer queries, but with higher precision
- Overlap is symmetric and differentiable — enabling energy-based exploration
The query pipeline uses Hierarchical Rejection Mapping (HRM2):
- Coarse KMeans++: Partition all splats into N coarse clusters
- Fine KMeans++: Each coarse cluster has M fine sub-clusters
- Probe: Only search the top P fine clusters relevant to the query
- Exact re-rank: Compute full splat overlap for candidates
This reduces search from O(N) to O(N/P) with minimal recall loss.
⚠️ SimCos is for quick demos only. For production, always use real embeddings (OpenAI, Cohere, MiniLM, etc). SimCos uses trigram hashing — it has no semantic understanding.
When no pre-computed embeddings are provided, SplatsDB uses SimCos — a similarity-consistent n-gram hashing scheme:
- Character trigrams are extracted from text
- Each trigram is hashed into the embedding dimension
- Overlapping n-grams between two texts produce high cosine similarity
- No model loading required — instant, deterministic, zero dependencies
SimCos is a toy embedding useful for quick demos and testing. For production use, pass pre-computed embeddings from your preferred model (OpenAI, Cohere, local transformers, etc.).
Two documents can share the same mean (μ) but have very different uncertainty profiles:
Document A: μ=[0.1, 0.3, ...], κ=50 (precise, narrow topic)
Document B: μ=[0.1, 0.3, ...], κ=5 (broad, covers many sub-topics)
Query: "machine learning"
→ Document A: high overlap score, high confidence → ranked #1
→ Document B: moderate overlap, low confidence → ranked lower, flagged as uncertain
A high κ means the document is tightly focused — it matches few queries, but when it matches, you can be confident. A low κ means the document is broad — it matches many queries, but results carry less certainty. This is information no plain vector distance can give you.
- HNSW is the default for production. It indexes μ vectors directly and achieves 0.995+ recall. α and κ are used during re-ranking for confidence scoring.
- HRM2 splats are useful when you need overlap-based similarity (capturing topic breadth), uncertainty estimation, or don't have enough data for a good HNSW graph.
- Rule of thumb: Use HNSW for speed, use HRM2 when uncertainty matters.
Integrity pledge: No gaming. No hardcoding. No cherry-picking. No squashed git history. No anonymous authors. Every number below is measured on real hardware with published methodology and reproducible code in
benchmarks/.
All numbers are measured on real hardware and independently validated. No simulated or estimated data.
- Dataset: SIFT-128 from ANN-Benchmarks (the industry standard for vector search benchmarking, created by Erik Bernhardsson / ex-Spotify)
- Subset: First 10K and 100K vectors from the 1M training set, first 1000 queries from the 10K test set
- Ground truth: Computed independently with
sklearn.neighbors.NearestNeighbors(brute-force,metric='euclidean',algorithm='brute'). Verified to match ANN-Benchmarks official GT on overlapping queries (100% agreement at top-10) - Hardware: Windows 11 on Ryzen 5 3400G + RTX 3090 (24GB VRAM) + 16GB RAM. GPU benchmarks use CUDA PTX kernels compiled with nvcc 12.4 + MSVC
- Tool: Built-in
bench-hnswCLI command — loads vectors, builds HNSW, runs queries in-process (eliminates CLI spawn overhead) - HNSW config: Advanced preset (M=32, ef_construction=400, ef_search=100)
- Recall metric: recall@10 — fraction of true top-10 nearest neighbors found by the algorithm
- Metric note: HNSW graph is built with cosine similarity, but
find_neighbors_fused()re-ranks all candidates with exact L2 distance before returning. For SIFT-128 (normalized data), cosine and L2 top-10 overlap is 99.4%, so the metric mismatch does not meaningfully affect results - Validation: Results re-run with a second independently generated GT. Numbers below are from the validated run
HNSW graph with persistence (save/load to hnsw_index.bin), exact L2 distance re-ranking of candidates.
Measured 2026-07-05 with the built-in
bench-hnswCLI. Ground truth: brute-force L2 (NumPy). Full methodology and raw output inbench-data/hnsw_validated_results.json.
| Dataset | N | Dim | Build Time | p50 Latency | p95 | p99 | QPS | Recall@10 |
|---|---|---|---|---|---|---|---|---|
| synthetic-sift128 | 10K | 128 | 22s* | 1.20ms | 1.69ms | 2.10ms | 789 | 0.861 |
| synthetic-sift128 | 100K | 128 | 982s* | 4.09ms | 5.12ms | 5.78ms | 242 | n/a† |
Why recall is lower than a typical SIFT-128 benchmark
This measurement uses synthetic uniform-random data (128-dim, uint8-valued, seed=42), not real ANN-Benchmarks SIFT. Synthetic data has no natural cluster structure, so it is harder for HNSW than real SIFT (which has learnable cluster patterns). These numbers validate the code path and order of magnitude. A run on real SIFT-128 data (which has natural clusters) would show higher recall. To reproduce: see bench-data/hnsw_validated_results.json.
*Build time is one-time — HNSW graph persists to hnsw_index.bin. Subsequent runs load from disk, skipping build entirely.
†Recall@10 for 100K not computed (brute-force GT on 100K×1000 was too expensive for this session). QPS and latency are valid.
Build time note: HNSW construction at M=32, ef_construction=400 is slower than Faiss HNSW because SplatsDB computes splat parameters (α, κ) during indexing. The index persists to disk — build once, query forever. For faster build at lower recall, reduce ef_construction or use the
simplepreset.
| Method | Dataset | N | p50 Latency | QPS | Recall@10 |
|---|---|---|---|---|---|
| HNSW (synthetic) | synthetic-sift128 | 10K | 1.20ms | 789 | 0.861 |
| HNSW (synthetic) | synthetic-sift128 | 100K | 4.09ms | 242 | n/a |
| Linear scan | synthetic-sift128 | 10K | 1,143ms | 0.9 | 1.000 |
| HRM2 splats | SIFT-128 | 100K | 76ms | 11.0 | ~0.95 |
On synthetic data HNSW delivers ~875× speedup over linear scan at 10K. Real-SIFT recall would be higher.
Honest, reproducible side-by-side. Same Ryzen 5 3400G + RTX 3090, same SIFT-128 100K dataset, same k=64. Faiss from
faiss-cpu1.13.2. SplatsDB frombench-hnsw+bench-gpuCLI.
|| Index | Build Time | p50 Latency | p99 Latency | QPS | Recall@64 | |-------|-----------|-------------|-------------|------|-----------| | Faiss HNSW (M=32, efSearch=100) | 24.5s | 0.10ms | 0.18ms | 9,758 | 0.9926 | | Faiss IVFFlat (nprobe=32) | 3.0s | 0.10ms | 0.15ms | 10,039 | 0.69 | | SplatsDB HNSW (CPU, ef=100) | 88s | 1.52ms | 2.52ms | 658 | 0.986 | | SplatsDB GPU (RTX 3090, brute-force) | 17ms* | 0.082ms | 0.095ms | 12,195 | 1.000 |
Takeaways:
- SplatsDB GPU (12,195 QPS) beats Faiss HNSW CPU (9,758 QPS) — 1.25× faster
- GPU brute-force = 100% recall (exact search, no approximation)
- GPU upload to VRAM: 17ms — effectively zero build time
- Faiss HNSW CPU is still 14.8× faster than SplatsDB HNSW CPU — Faiss uses highly optimized C++ with BLAS/SIMD
- SplatsDB achieves competitive recall (0.986 vs 0.993)
- SplatsDB build is ~3.6× slower — computes splat parameters (α, κ) during indexing
- SplatsDB's value is not just QPS — it's uncertainty-aware retrieval + knowledge graph + agent memory + spatial memory in one binary
- Full results:
bench-data/benchmark_results_hardware.json
LongMemEval is the standard benchmark for evaluating long-term conversational memory in AI agents. Tests retrieval across 500 questions, each with ~48 sessions and ~490 turns.
Pipeline: Embed all ~24K sessions with all-MiniLM-L6-v2 (384d, GPU RTX 3090) → cosine similarity search → measure if answer session appears in top-k.
Honest methodology note: The recall numbers below were measured with NumPy brute-force cosine similarity (see
longmemeval_full_results.json), not SplatsDB's own HNSW. They validate the retrieval approach (embeddings + cosine), not SplatsDB's search engine specifically. A separate run using SplatsDB's native Rust HNSW (longmemeval_splatdb_native.json) showed recall@10 = 0.304 — see that file for details on why native HNSW recall is lower (session-grouping and ef_search tuning differ).
Session Recall (answer session found in top-k, NumPy brute-force):
| k | Recall |
|---|---|
| 1 | 75.8% |
| 3 | 88.2% |
| 5 | 92.2% |
| 10 | 96.6% |
Per question type (Recall@10):
| Type | Questions | Recall@10 |
|---|---|---|
| knowledge-update | 78 | 100.0% |
| multi-session | 133 | 99.2% |
| single-session-assistant | 56 | 98.2% |
| single-session-preference | 30 | 96.7% |
| temporal-reasoning | 133 | 95.5% |
| single-session-user | 70 | 88.6% |
What this validates: With real sentence embeddings, the retrieval approach achieves 96.6% recall@10 on conversational memory retrieval. The system excels at knowledge-update (100%) and multi-session queries (99.2%). Temporal-reasoning (95.5%) and preference questions (96.7%) — where the original keyword baseline showed 30% and 0% — are dramatically improved by semantic search.
- Full results:
bench-data/longmemeval_full_results.json - Benchmark script:
bench-data/longmemeval_full.py - Previous keyword baseline:
bench-data/longmemeval_baseline_results.json
Combined distance + top-k selection in one GPU pass. Dataset persists in VRAM between queries. Benchmarked on RTX 3090 (24GB VRAM).
| Dataset | Dim | Queries | k | CPU QPS | GPU Persistent QPS | Speedup |
|---|---|---|---|---|---|---|
| 100K | 128 | 1,000 | 10 | 2,941 | 12,195 | 4.1× |
- Upload bandwidth: ~3 GB/s (PCIe)
- Per-query latency with dataset in VRAM: 0.082 ms
- GPU results verified against CPU: identical top-k indices and order
- Custom PTX
l2_topk_kernelcompiled with nvcc 12.4 + MSVC
Distance kernels (kernels/distance.cu — 6 kernels):
float4vectorized loads (640D = 160 × float4 per vector)- Shared memory query cache (avoids redundant global reads per thread)
- Thread-local sorted top-k (max K=32) with shared memory merge
__launch_bounds__(256)for optimal sm_86 occupancy- PTX compiled with
--use_fast_math -O3
Extended kernels (kernels/extended_kernels.cu — 8 kernels, added v2.5):
rotation_gemv_kernel/rotation_gemv_inverse_kernel— batch R·x and R^T·x with float4 loads + shared mem query cacheqjl_batch_sketch_kernel— batch sign(G·x) for QJL quantization (N vectors × G projections)qjl_batch_ip_estimate_kernel— batch inner product estimation from sketches with precomputed proj·querykmeans_assign_kernel— N×K×D assignment in single kernel (KMeans++ hot path)cosine_similarity_kernel— all-pairs cosine sim with tile-based 16×16 upper triangle computationbatch_geodesic_kernel— pairwise arccos distance between matched vector pairslsh_hash_kernel— batch LSH hash sign bits across T tables × K projections
All extended kernels use float4 vectorized loads, shared memory caching, and __launch_bounds__(256). Each has a CPU fallback via Option-returning functions — no CUDA dependency required at runtime.
Rust GPU layer (src/gpu/cuda_extended.rs):
GpuExtended— stateless kernel launcher (no persistent GPU state)- Public API in
gpu/mod.rs:rotation_gemv(),rotation_gemv_inverse(),qjl_batch_sketch(),qjl_batch_ip_estimate(),kmeans_assign(),cosine_similarity_matrix(),batch_geodesic(),lsh_batch_hash() - Auto-fallback: if CUDA unavailable, returns
Noneand caller uses CPU path
The following benchmarks are implemented in benchmarks/benchmark_reproducible.py but have not yet been executed on our hardware. They will be run and results published once the full pipeline is validated.
| Benchmark | Domain | What It Tests | Status |
|---|---|---|---|
| BEIR | 5 IR domains (scifact, trec-covid, fiqa, arguana, nfcorpus) | Standard information retrieval: NDCG@10, Recall@10, MAP across diverse text corpora | ✅ 0.461 NDCG@10 |
| LOCOMO | Long-form conversational memory | Cross-session recall in multi-turn dialogs with temporal/counterfactual/causal reasoning | ✅ 68.2% Recall@10 |
| MemoBench-style | Multi-agent dialog memory | Cross-session recall of agent preferences and facts (synthetic data — labeled accordingly) | ✅ 100% Recall@10 |
BEIR uses the standard evaluation protocol from beir.ai — no per-dataset tuning, no cherry-picking domains. The implementation downloads datasets, encodes with all-MiniLM-L6-v2, builds Faiss index, and computes NDCG@10, Recall@10, MAP using pytrec-eval.
BEIR Results (5 domains, model: all-MiniLM-L6-v2):
| Domain | Corpus | Queries | NDCG@10 | Recall@10 | MAP@10 | QPS |
|---|---|---|---|---|---|---|
| scifact | 5,183 | 300 | 0.645 | 0.783 | 0.596 | 6,990 |
| arguana | 8,674 | 1,406 | 0.502 | 0.790 | 0.412 | 6,416 |
| trec-covid | 171,332 | 50 | 0.473 | 0.013 | 0.011 | 283 |
| fiqa | 57,638 | 648 | 0.369 | 0.441 | 0.291 | 1,031 |
| nfcorpus | 3,633 | 323 | 0.316 | 0.155 | 0.111 | 13,683 |
| Average | 0.461 | 0.437 | 0.284 | 5,681 |
LOCOMO (KimmoZZZ/locomo on HuggingFace) evaluates long-form conversational memory across 19 sessions per conversation, with QA pairs spanning factoid, temporal, counterfactual, and causal categories.
MemoBench-style is a synthetic benchmark (seed=42, deterministic) since the official MemoBench dataset is not yet publicly available. It tests the same capabilities — cross-session agent memory recall — with clearly labeled synthetic data. We will replace it with the official dataset when available.
LOCOMO Results (1,982 questions across 10 conversations):
| Category | Questions | Recall@10 |
|---|---|---|
| cat_5 (session matching) | 446 | 100.0% |
| causal | 841 | 73.7% |
| factoid | 282 | 52.8% |
| counterfactual | 92 | 38.0% |
| temporal | 321 | 31.8% |
| Overall | 1,982 | 68.2% |
MemoBench-style Results (15 questions, 5 agents, 20 sessions):
| k | Recall@k |
|---|---|
| 1 | 93.3% |
| 5 | 100.0% |
| 10 | 100.0% |
Run any benchmark:
python benchmarks/benchmark_reproducible.py --suite beir # BEIR (5 domains)
python benchmarks/benchmark_reproducible.py --suite locomo # LOCOMO
python benchmarks/benchmark_reproducible.py --suite memobench # MemoBench-style
python benchmarks/benchmark_reproducible.py --suite all # EverythingIntegration points (GPU-accelerated hot paths):
clustering.rs— KMeans assignment step: GPU → rayon CPU fallbackgeometry.rs— cosine_similarity_matrix + geodesic_distance_batch: GPU → ndarray CPUquant/rotation.rs— batch rotation GEMV: GPU → sequential CPU
An earlier Python prototype showed 32x speedup with HRM2 vs linear scan on 100K vectors (100K splats, 1K queries, k=64, CPU). Referenced for historical context only. Validated Rust result:
| Method | Latency | QPS | Speedup |
|---|---|---|---|
| Linear scan | 94.79 ms | 10.55 | baseline |
| SplatsDB HRM2 | 0.99 ms | 1,012.77 | 32.4x |
Seven presets cover edge devices to GPU clusters. Each preset configures all subsystems at once — no manual flag toggling required.
| Preset | Use Case | Capacity | Quantization | Graph | Semantic | GPU | HNSW | LSH |
|---|---|---|---|---|---|---|---|---|
default |
General purpose | 100K | 8-bit TurboQuant | ✅ | ✅ RRF | — | — | — |
simple |
Edge / IoT | 10K | — | — | — | — | — | — |
mcp |
AI agent memory | 100K | 8-bit TurboQuant | ✅ | ✅ RRF | Auto | — | — |
advanced |
AI agents | 1M | 4-bit TurboQuant | ✅ | ✅ RRF | Optional | ✅ M=32 | — |
training |
Model research | 500K | — | — | — | Optional | — | — |
distributed |
Multi-node | 10M | 4-bit TurboQuant | ✅ | ✅ RRF | Optional | — | — |
gpu |
CUDA servers | 5M | 4-bit TurboQuant | ✅ | ✅ | ✅ | ✅ M=48 | — |
use splatsdb::config::SplatsDBConfig;
let edge = SplatsDBConfig::simple(None); // Raspberry Pi
let agent = SplatsDBConfig::mcp(None); // AI agent memory (auto GPU)
let heavy = SplatsDBConfig::advanced(Some("cuda")); // Full-featured with GPU
let server = SplatsDBConfig::gpu(None); // Maximum GPU performance
let cluster = SplatsDBConfig::distributed(Some("cpu")); // Multi-node deploymentDevice auto-detection: Pass None to auto-detect (CUDA → Vulkan → CPU). Pass Some("cuda"), Some("vulkan"), or Some("cpu") to force a specific device.
Start
├─ <10K docs, no GPU? → simple
├─ AI agent, need memory? → mcp (auto GPU)
├─ >100K docs, need speed? → advanced (HNSW M=32)
├─ >5M docs + CUDA? → gpu (M=48, VRAM-persistent)
└─ Multi-node? → distributed
SplatsDB provides a comprehensive CLI for all operations. Global options apply to every command.
| Flag | Default | Description |
|---|---|---|
--data-dir <DIR> |
./splatsdb_data |
Storage directory |
--dim <DIM> |
64 |
Vector dimensionality |
--max-splats <N> |
100000 |
Maximum splat capacity |
--backend <BACKEND> |
sqlite |
Metadata storage backend (sqlite or json) |
| Command | Description |
|---|---|
status |
Show store statistics (active splats, entropy, index status) |
search --query "0.1,0.2,..." -k 10 |
Search by comma-separated query vector |
search-file --input query.bin -k 10 |
Search using query vector from binary file |
index --input data.bin --shard default |
Add vectors from binary file |
append --input data.bin |
Incrementally add vectors to existing HNSW graph (no full rebuild) |
save |
Save current state to disk |
load |
Load state from disk |
list |
List all stored shards |
backup --output ./backup/ |
Backup all data to directory |
| Command | Description |
|---|---|
ingest --input data.bin --n-clusters 100 |
KMeans splat centroids via DatasetTransformer |
ingest-hierarchical --input data.bin --n-clusters 10 |
Two-level hierarchical KMeans |
ingest-leader --input data.bin --target-clusters 50 |
O(n) single-pass leader clustering |
| Command | Description |
|---|---|
fused-search --query "0.1,..." -k 10 |
Search all enabled backends with score fusion |
hnsw-search --query "0.1,..." -k 10 |
HNSW index only |
lsh-search --query "0.1,..." -k 10 |
LSH index only |
| Command | Description |
|---|---|
quant-index --algorithm turbo --bits 4 |
Build compressed index (TurboQuant or PolarQuant) |
quant-search --query "0.1,..." --top-k 10 |
Search compressed codes |
quant-status |
Show quantization statistics |
| Command | Description |
|---|---|
gpu-info |
Show CUDA device information and VRAM status |
bench-gpu --n-vectors 100000 --dim 640 |
Benchmark GPU vs CPU search |
bench-gpu-ingest --n-vectors 100000 --dim 640 |
Benchmark full GPU ingest + search pipeline |
| Command | Description |
|---|---|
soc-check |
Check system criticality state |
soc-avalanche --seed 42 |
Trigger avalanche reorganization |
soc-relax --iterations 50 |
Relax the system toward lower energy |
| Command | Description |
|---|---|
doc-add --id my-doc --text "content" |
Add document with metadata |
doc-get --id my-doc |
Retrieve document by ID |
doc-del --id my-doc |
Soft-delete a document |
| Command | Description |
|---|---|
graph-add-doc --text "content" |
Add document node to graph |
graph-add-entity --name "entity" --entity-type person |
Add entity node |
graph-add-relation --source-id 1 --target-id 2 --relation-type MENTIONS |
Add edge |
graph-traverse --text \"query\" --max-depth 3 |
BFS traversal from embedding-matched node |
graph-search --query "search text" --k 10 |
Hybrid graph+vector search |
graph-stats |
Graph statistics |
| Command | Description |
|---|---|
serve --host 0.0.0.0 --port 8199 |
Start HTTP API server |
mcp |
Start MCP server (stdio JSON-RPC 2.0) |
preset-info |
Show which subsystems each preset enables |
| Command | Description |
|---|---|
extract-entities --text "content" --min-score 0.3 |
Extract entities via structural patterns and n-grams |
eval-embeddings --dim 64 --n-queries 10 |
Evaluate embedding quality with synthetic benchmark |
| Command | Description |
|---|---|
lake-list |
List datasets in the data lake |
lake-register --id ds1 --name "My Dataset" --n-vectors 10000 --dim 640 |
Register a dataset in the data lake |
| Command | Description |
|---|---|
bench-hnsw --train data.bin --queries q.bin --gt ground.bin --dim 128 |
HNSW benchmark with recall measurement |
| Command | Description |
|---|---|
spatial-search --query "text" --wing project-x --room auth --hall decisions |
Search with spatial filters (Wing/Room/Hall) |
spatial-info |
Show spatial memory structure (wings, rooms, tunnels) |
SplatsDB includes a built-in Model Context Protocol server for direct integration with AI agents. The MCP server uses stdio transport with JSON-RPC 2.0 and auto-loads the mcp preset with GPU auto-detection.
| Variable | Default | Description |
|---|---|---|
SPLATSDB_DOC_PATH |
~/.hermes/splatsdb_docs.db |
SQLite database path for document persistence |
SPLATSDB_EMBED_URL |
http://127.0.0.1:8788/embed |
Embedding service URL (MiniLM or compatible) |
- No panics: All
.unwrap()/.expect()calls replaced with safe error handling - Input validation: Text length capped at 100KB, entity names at 10KB,
kcapped at 1000 - No info leakage: Mutex poisoning logged to stderr only, generic error returned to client
- Incremental indexing: Uses
hnsw_sync_incremental()instead of full rebuild on every insert - LRU text cache: In-memory document text cache with 10K entry cap and automatic eviction
- Warm start: Progress logging every 100 docs, capped at 50K docs, interruptible by SIGINT
- Graceful shutdown: SIGINT triggers clean exit (completes current request, persists state)
- JSON-RPC validation: Rejects requests with
jsonrpc != "2.0"with proper error code
Add to your AI agent's MCP configuration (e.g., Claude Code, Cursor, Hermes):
{
"mcp_servers": {
"splatsdb": {
"command": "/path/to/splatsdb",
"args": ["mcp"],
"timeout": 30
}
}
}| Tool | Required Params | Optional Params | Description |
|---|---|---|---|
splatsdb_store |
text |
id, category, embedding, wing, room, hall |
Store a memory. Auto-embeds text via SimCos, or accepts pre-computed embedding. Spatial params (wing/room/hall) enable pre-filter search via splatsdb_spatial_search. For production, always provide real embeddings via the embedding field. |
splatsdb_search |
query |
top_k, embedding |
Semantic search. Returns ranked results with similarity scores. |
splatsdb_status |
— | — | Engine status: dimension, doc count, active indexes, quantization state. |
| Tool | Required Params | Optional Params | Description |
|---|---|---|---|
splatsdb_doc_add |
id, text |
metadata (JSON) |
Store document with metadata. Persists to SQLite. Survives restarts. |
splatsdb_doc_get |
id |
— | Retrieve document by ID. Returns text, metadata, timestamps. |
splatsdb_doc_del |
id |
— | Soft-delete a document. |
| Tool | Required Params | Optional Params | Description |
|---|---|---|---|
splatsdb_graph_add_doc |
text |
— | Add document node. Auto-embeds text via SimCos (use real embeddings in production). |
splatsdb_graph_add_entity |
name, entity_type |
— | Add entity node (person, org, location, concept). Auto-embeds name via SimCos (use real embeddings in production). |
splatsdb_graph_add_relation |
source_id, target_id, relation_type |
weight |
Add directed edge between nodes. |
splatsdb_graph_traverse |
text |
max_depth, add_doc |
BFS traversal. Auto-embeds query, finds nearest node, traverses graph. Use --add-doc to add the text as a document first. |
splatsdb_graph_search |
query |
k |
Hybrid search: vector similarity + graph context boost. |
splatsdb_graph_search_entities |
query |
k |
Search entity nodes by embedding similarity. |
splatsdb_graph_stats |
— | — | Node counts, edge counts, entity/document breakdown. |
splatsdb_spatial_search |
query |
wing, room, hall, top_k |
Search with spatial filters. Pre-filters by wing/room/hall metadata before vector search for higher recall. |
splatsdb_spatial_info |
— | — | Show spatial memory structure: wings, rooms, halls, tunnels (auto-detected cross-wing connections). |
Agent: splatsdb_store(text="User prefers dark mode and Spanish language")
→ {"id": "mem_1", "status": "stored"}
Agent: splatsdb_graph_add_entity(name="Brian", entity_type="person")
→ {"entity_type": "person", "name": "Brian", "node_id": 1}
Agent: splatsdb_graph_add_doc(text="Brian works on SplatsDB, a vector search engine")
→ {"node_id": 2, "node_type": "document"}
Agent: splatsdb_graph_add_relation(source_id=2, target_id=1, relation_type="MENTIONS")
→ {"ok": true, "relation_type": "MENTIONS"}
Agent: splatsdb_search(query="user preferences", top_k=5)
→ {"results": [{"id": "mem_1", "score": 0.95, "text": "User prefers dark mode..."}]}
Agent: splatsdb_graph_search(query="who works on vector search", k=3)
→ {"results": [{"content": "Brian works on SplatsDB...", "score": 0.49}]}
The MCP server uses SQLite with WAL mode for document storage. On restart, all documents are automatically reloaded (warm start) with progress logging. HNSW indices use incremental insertion — new vectors are added to the existing graph without rebuilding. Vector indices persist to hnsw_index.bin and are loaded from disk on startup.
Start an HTTP server for integration with Python, Node.js, or any HTTP client:
./target/release/splatsdb serve --host 0.0.0.0 --port 8199 --data-dir ./data| Flag | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
Bind address |
--port |
8199 |
Listen port |
--data-dir |
./splatsdb_data |
Storage directory |
--dim |
64 |
Vector dimensionality |
--max-splats |
100000 |
Max capacity |
--backend |
sqlite |
Metadata backend |
curl http://localhost:8199/health{"status": "ok", "version": "2.5.0"}curl -X POST http://localhost:8199/status{
"n_active": 1500,
"max_splats": 100000,
"dimension": 640,
"has_hnsw": false,
"has_lsh": false,
"has_quantization": true,
"has_semantic_memory": false
}curl -X POST http://localhost:8199/store \
-H "Content-Type: application/json" \
-d '{"text": "my memory text", "category": "notes", "id": "mem_42"}'| Field | Type | Required | Description |
|---|---|---|---|
text |
string | Yes | Text content (auto-hashed to embedding if no embedding) |
embedding |
float[] | No | Pre-computed embedding vector |
category |
string | No | Category tag |
id |
string | No | Custom ID (auto-generated if omitted) |
Response:
{"id": "mem_42", "status": "stored"}curl -X POST http://localhost:8199/search \
-H "Content-Type: application/json" \
-d '{"query": "my search", "top_k": 5}'| Field | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Query text (auto-hashed to embedding if no embedding) |
embedding |
float[] | No | Pre-computed query embedding |
top_k |
int | No | Number of results (default: 10) |
Response:
{
"results": [
{"index": 0, "score": 0.023, "metadata": null},
{"index": 5, "score": 0.145, "metadata": null}
]
}Note: The default HTTP server uses SimCos hash-based pseudo-embeddings (trigram hashing — no semantic understanding). For production use, provide real embeddings from a model (e.g. OpenAI, Cohere, MiniLM, BGE) via the
embeddingfield.
use splatsdb::{SplatsDBConfig, SplatStore};
fn main() {
// Choose a preset (or use SplatsDBConfig::default())
let config = SplatsDBConfig::mcp(None); // AI agent memory, auto GPU
let mut store = SplatStore::new(config);
// Insert vectors
let id = store.insert(&vec![0.1f32; 640]);
// Search
let results = store.search(&vec![0.1f32; 640], 10);
for r in &results {
println!("id={} dist={:.4}", r.index, r.distance);
}
// Build index for fast retrieval
store.build_index();
// Fast approximate search via HRM2
let fast_results = store.find_neighbors_fast(&query.view(), k);
}| Method | Description |
|---|---|
SplatStore::new(config) |
Create store with given configuration |
store.insert(&vec) |
Insert a vector, return its index |
store.insert_with_hnsw(&vec) |
Insert a vector and incrementally add to HNSW graph |
store.add_batch_with_hnsw(&arr) |
Batch insert vectors with incremental HNSW sync |
store.search(&vec, k) |
Exact k-nearest neighbor search |
store.find_neighbors(&row, k) |
Search using ndarray row reference |
store.find_neighbors_fast(&view, k) |
HRM2 approximate search (requires build_index()) |
store.build_index() |
Build HRM2 coarse + fine index (full build) |
store.hnsw_sync_incremental() |
Sync new vectors into HNSW graph (incremental, no rebuild) |
store.hnsw_needs_sync() |
Check if new vectors need HNSW indexing |
store.n_active() |
Number of active splats |
store.hnsw_indexed_count() |
Number of vectors currently in the HNSW graph |
store.entropy() |
Current entropy of the system |
SplatsDB supports CUDA GPU acceleration via custom PTX kernels compiled at build time.
- NVIDIA GPU (Compute Capability 7.0+, tested on RTX 3090 sm_86)
- CUDA Toolkit 12.x
- MSVC
cl.exein PATH (Windows) or GCC (Linux)
cargo build --release --features cuda- Upload: Dataset vectors are copied to GPU VRAM as a flat
floatarray - Persistent: Data stays in VRAM between queries — no re-upload overhead
- Query: Each query launches a CUDA kernel that:
- Loads the query into shared memory
- Each thread block processes a chunk of vectors using
float4loads - Thread-local top-k maintained in registers
- Final merge across thread blocks
- Download: Only the top-k indices and distances are copied back to CPU
Measured on RTX 3090 (24GB VRAM), SIFT-128 100K vectors, k=10. Full results: bench-data/benchmark_results_hardware.json.
| Metric | Value |
|---|---|
| GPU QPS (persistent) | 12,195 |
| CPU QPS (same data) | 2,941 |
| Speedup | 4.1× |
| Per-query latency (GPU) | 0.082 ms |
| Upload time (100K → VRAM) | 17 ms |
GPU QPS measured with dataset resident in VRAM between queries (persistent mode). First-query upload cost is amortized over the query batch. GPU results verified identical to CPU brute-force (100% recall).
# Show CUDA device info
./target/release/splatsdb gpu-info
# Benchmark GPU vs CPU
./target/release/splatsdb bench-gpu --n-vectors 100000 --dim 640 --n-queries 100 --top-k 10
# Full pipeline benchmark (ingest + index + search + GPU)
./target/release/splatsdb bench-gpu-ingest --n-vectors 100000 --dim 640 --n-queries 100SplatsDB includes a built-in knowledge graph that augments vector search with structured relationships.
- Document nodes: Auto-embedded from text content
- Entity nodes: Named entities with types (person, organization, location, concept)
Any string label works. Common patterns:
MENTIONS— document references an entityRELATED_TO— general semantic connectionPART_OF— hierarchical containmentLOCATED_IN— spatial relationship
Hybrid search combines vector similarity with graph context:
- Find top-k similar nodes by embedding cosine similarity
- Boost scores for nodes connected to highly-ranked neighbors
- Return fused ranking
This means: a document that is similar to your query AND is connected to other relevant documents will rank higher than a similar but isolated document.
This is where GraphSplat outperforms plain vector search — it doesn't just find the closest document, it discovers the context around it:
Vector-only search for "database indexing strategy":
→ Doc #47 (score: 0.91) — "Use HNSW for approximate nearest neighbor..."
GraphSplat search for same query (2-hop expansion):
→ Doc #47 (score: 0.91) — "Use HNSW for approximate nearest neighbor..."
→ Entity #3 (score: 0.82) — "HNSW algorithm" [connected via MENTIONS]
→ Doc #12 (score: 0.78) — "HNSW build time tradeoffs: ef_construction..."
[connected to Entity #3 via MENTIONS]
→ Doc #89 (score: 0.74) — "Faiss IVF-PQ comparison with HNSW..."
[connected to Doc #12 via RELATED_TO]
Result: Agent gets the full context — the strategy, the tradeoffs, and the alternatives.
A flat vector search would have returned Doc #47 and stopped.
// The code behind the example above
let results = graph.hybrid_search(&query_embedding, 10);
// For each result, expand 2 hops to discover related context
for result in &results {
let context = graph.traverse(result.node_id, 2); // 2-hop BFS
// Returns: connected docs, entities, relations — full context graph
}This is what LangChain and LlamaIndex try to do with multiple tools (vector store + graph DB + fusion). GraphSplat does it natively in a single query.
use splatsdb::graph_splat::GaussianGraphStore;
let mut graph = GaussianGraphStore::new();
// Add nodes
let doc_id = graph.add_document("SplatsDB is a vector search engine", &embedding)?;
let entity_id = graph.add_entity("SplatsDB", "software", &name_embedding)?;
// Add relation
graph.add_relation(doc_id, entity_id, "MENTIONS", 1.0)?;
// Hybrid search
let results = graph.hybrid_search(&query_embedding, 10);
// BFS traversal
let connected = graph.traverse(entity_id, 3);
// Entity search
let entities = graph.search_entities(&query_embedding, 5);SplatsDB supports data-oblivious vector compression that requires no training data, no codebooks, no fine-tuning.
| Algorithm | Bits | Method | Best For |
|---|---|---|---|
| TurboQuant | 3–8 | Random projection + quantization | General purpose, high throughput |
| PolarQuant | 3–8 | Polar coordinate quantization | Directional similarity |
# Build compressed index
./target/release/splatsdb quant-index --algorithm turbo --bits 4
# Search compressed codes
./target/release/splatsdb quant-search --query "0.1,..." --top-k 10
# Check status
./target/release/splatsdb quant-statusWith 4-bit TurboQuant on 640D vectors:
- Raw: 2,560 bytes per vector
- Compressed: 320 bytes per vector (8x reduction)
- Recall: >95% top-10 recall at 5% search fraction
SplatsDB combines BM25 text search with vector similarity using Reciprocal Rank Fusion (RRF).
- Run BM25 search over document text → get ranking R₁
- Run vector similarity search → get ranking R₂
- Fuse:
score(d) = w_vec × 1/(k + rank_R₂(d)) + w_bm25 × 1/(k + rank_R₁(d))
Default weights: w_vec = 0.6, w_bm25 = 0.4 (configurable per preset).
This gives the best of both worlds: vector search captures semantic meaning, BM25 captures exact keyword matches, and RRF merges them without score normalization issues.
Optional: recent memories can be weighted higher via exponential decay with configurable half-life.
Inspired by MemorySpaces's principle: "Store everything verbatim, never let an LLM decide what to remember."
Most vector DBs store only embeddings — the original text is lost or truncated. SplatsDB's planned verbatim storage keeps three layers per document:
| Layer | Content | Size | Purpose |
|---|---|---|---|
| Splat | μ, α, κ + compressed vector | ~320 bytes (4-bit TurboQuant) | Fast retrieval |
| Summary | Key facts extracted by LLM | ~200 bytes | Quick context for agent decision-making |
| Drawer | Original document verbatim | Variable | Source of truth — never summarized |
How it works in practice:
MCP query → Vector search finds top-5 splats
→ Agent reads summaries (cheap, ~1K tokens)
→ Agent decides: "I need the full detail on doc #3"
→ Drawer lookup: exact original text returned
This is the opposite of how Pinecone/Qdrant handle text — they truncate or discard it. SplatsDB keeps the source of truth because for agent memory, losing why a decision was made kills future recall.
Status: Planned. The building blocks exist (SQLite persistence, BM25 text index, MCP server). Pending: summary extraction pipeline and drawer API.
Vector compression (TurboQuant/PolarQuant) handles embeddings. But the text itself also needs compression for efficient agent memory.
The concept: a compressed representation that any LLM can read natively without a decoder — similar to how MemorySpaces's AAAK achieves ~30x text compression by stripping everything except semantic content.
Original text (1,200 tokens):
"On January 15th, 2025, the team decided to migrate from Faiss to SplatsDB
for the production vector search pipeline. The primary motivation was the
need for uncertainty-aware retrieval and knowledge graph integration.
Brian will lead the migration, targeting completion by end of Q1."
Compressed (~40 tokens, 30x):
"2025-01-15: migrate Faiss→SplatsDB prod. reason: uncertainty+KG.
lead: Brian. target: Q1 end."
Why this matters for SplatsDB:
- TurboQuant gives 8x on vectors → 30x on text is the bigger win for agent memory
- MCP server can return compressed summaries within token budgets
- Agent only fetches full drawer when needed → saves ~90% tokens on average query
Status: Planned. Will be implemented as a post-ingestion compression pass, independent of the vector pipeline.
SplatsDB models the embedding space as an energy landscape:
E(x) = −log(Σᵢ αᵢ · exp(−κᵢ · ‖x − μᵢ‖²))
- Low energy: Well-covered regions — many documents nearby, high confidence
- High energy: Sparse regions — few documents, high uncertainty
- Active learning: Focus labeling effort on high-energy regions
- Exploration: Boltzmann sampling from the energy landscape
- Self-organized criticality (SOC): The system can self-tune via avalanche reorganization and relaxation
# Check current energy state
./target/release/splatsdb soc-check
# Trigger avalanche (reorganize splats for better coverage)
./target/release/splatsdb soc-avalanche --iterations 100
# Relax toward lower energy
./target/release/splatsdb soc-relax --iterations 5026,465 lines of Rust across these modules:
| Module | Source | Description |
|---|---|---|
splats |
src/splats.rs |
Core API — insert, search, batch ops, incremental HNSW, statistics |
hrm2_engine |
src/hrm2_engine.rs |
Two-level hierarchical retrieval (coarse → fine) |
engine |
src/engine.rs |
CPU L2 distance with rayon parallelism |
gpu |
src/gpu/ |
CUDA PTX kernels, GpuIndex with persistent VRAM |
quantization |
src/quantization.rs |
TurboQuant / PolarQuant data-oblivious compression with recall measurement |
clustering |
src/clustering.rs |
KMeans++ with ChaCha8 RNG |
graph_splat |
src/graph_splat.rs |
Knowledge graph overlay with hybrid search |
semantic_memory |
src/semantic_memory.rs |
BM25 + vector RRF fusion with temporal decay |
hnsw_index |
src/hnsw_index.rs |
HNSW approximate nearest neighbor with incremental insertion |
lsh_index |
src/lsh_index.rs |
Locality-sensitive hashing |
energy |
src/energy.rs |
Energy landscape computation |
ebm |
src/ebm/ |
Boltzmann exploration, self-organized criticality |
storage |
src/storage/ |
SQLite persistence (WAL), JSON store |
api_server |
src/api_server.rs |
HTTP REST API (4 endpoints: health, status, store, search) |
mcp_server |
src/mcp_server.rs |
MCP JSON-RPC 2.0 server (18 tools, production-hardened) |
config |
src/config/ |
7 presets, device auto-detection |
cli |
src/cli/ |
Command-line interface (clap) |
| Crate | Purpose | Optional |
|---|---|---|
ndarray |
N-dimensional array operations | No |
rayon |
Data parallelism (CPU search) | No |
rand / rand_chacha / rand_distr |
Random number generation, KMeans++ | No |
serde / serde_json |
Serialization | No |
rusqlite |
SQLite persistence (bundled) | No |
libc |
Signal handling (graceful shutdown) | No |
clap |
CLI argument parsing | No |
regex |
Pattern matching | No |
ureq |
HTTP client (embedding service) | No |
axum / tokio / tower-http |
HTTP API server | No |
parking_lot |
High-performance synchronization primitives | No |
bytemuck |
Zero-cost byte casting for GPU | No |
cudarc |
CUDA GPU kernels | Yes (--features cuda) |
Faiss benchmark comparison: Same hardware, same dataset, honest side-by-side numbers→ See Faiss ComparisonLongMemEval benchmark: Validate agent memory use case with the conversational memory standard→ See LongMemEval Agent Memory BenchmarkSpatial memory structure: Wings/Rooms/Halls/Tunnels**→ See Spatial MemoryGPU CUDA kernels: 14 PTX kernels for RTX 3090→ See GPU Acceleration
- Docker image:
docker run splatsdbfor instant trial without Rust/CUDA setup - Test coverage reporting: CI integration with
tarpaulinorllvm-cov - CI with GPU: GitHub Actions runner with CUDA for integration testing
- Verbatim storage: Store original document text alongside splats for exact recall (alpha — concept defined, implementation pending)
- Text compression (AAAK): ~30× compressed text any LLM reads natively (concept defined, implementation pending)
Inspired by MemorySpaces — organizing memory like a physical space.
SplatsDB's KMeans++ clusters are currently pure geometry — they group vectors by proximity but carry no semantic meaning. The spatial memory architecture maps these clusters to navigable structures:
┌─────────────────────────────────────────────────┐
│ Wing = Project / Persona / Domain │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Room = Theme │ │ Room = Theme │ │
│ │ (auth, │ │ (billing, │ │
│ │ migration) │ │ payments) │ │
│ │ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │ │ Hall = │ │ │ │ Hall = │ │ │
│ │ │ Type │ │ │ │ Type │ │ │
│ │ │ facts │ │ │ │ events │ │ │
│ │ │ decisions│ │ │ │ errors │ │ │
│ │ └──────────┘ │ │ └──────────┘ │ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ └─── Tunnel ─────┘ │
│ (shared room across wings) │
└─────────────────────────────────────────────────┘
| Spatial Concept | SplatsDB Equivalent | Purpose |
|---|---|---|
| Wing | Top-level metadata tag | Scope queries to a project/persona |
| Room | KMeans++ coarse cluster + label | Semantic grouping within a wing |
| Hall | Memory type (fact, decision, event, error) | Filter by what kind of memory |
| Tunnel | GraphSplat edge between wings | Cross-project connections |
Flat vector search searches everything equally. Spatial search filters first:
Query: "auth decisions from project X"
1. Filter by wing: "project-x" → reduces to ~10% of corpus
2. Filter by room: "auth" → reduces to ~2% of corpus
3. Filter by hall: "decisions" → reduces to ~0.5% of corpus
4. Vector search within that subspace → high recall, minimal noise
This is structurally identical to how MemorySpaces achieves +34% retrieval improvement over flat search — you reduce the search space before computing distances, so the signal-to-noise ratio improves dramatically.
When the same room (e.g., "auth") appears in two wings (e.g., "project-x" and "project-y"), a tunnel connects them automatically via GraphSplat:
// Auto-detected: "auth" room exists in both wings
graph.add_relation(wing_x_auth_room, wing_y_auth_room, "TUNNEL", 1.0);
// Query that benefits:
// "How did we handle auth in other projects?" → follows tunnel → cross-project insightThis turns your knowledge graph from a flat adjacency list into a navigable spatial structure — exactly what human memory does naturally.
Spatial memory has an initial implementation. The building blocks exist:
- ✅
SpatialIndexwith Wing/Room/Hall/Tunnel structures (src/spatial.rs) - ✅ Spatial query filter (AND logic on wing + room + hall)
- ✅ Auto-tunnel detection between wings sharing the same room
- ✅ CLI commands:
spatial-search --wing X --room Y --hall Z,spatial-info - ✅ KMeans++ coarse/fine clustering (becomes Rooms)
- ✅ GraphSplat with BFS traversal (becomes Tunnels)
- ✅ Metadata tags on documents (becomes Wings)
- ✅ BM25 + vector hybrid (can filter by hall type)
- ✅ Named cluster labels auto-assigned from document content (TF keyword extraction, EN+ES stopwords)
- ✅ Full search pipeline integration (spatial pre-filter → vector search via
find_neighbors_filtered) - ✅ MCP tools:
splatsdb_spatial_search,splatsdb_spatial_info - ✅ Spatial query API (
search --wing project-x --room auth --hall decisions)
| Rank | Product | Score | Type |
|---|---|---|---|
| 1 | Elasticsearch | 99.51 | Multi-model (not pure vector) |
| 4 | Pinecone | 8.18 | SaaS-only, proprietary |
| 6 | Milvus | 6.39 | Cloud-native, Apache 2.0 |
| 7 | Qdrant | 4.98 | Rust, Apache 2.0 |
| 9 | Weaviate | 4.42 | Go, BSD-3 |
| 11 | Chroma | — | Python+Rust, embedded |
| 16 | LanceDB | — | Rust, embedded, emergent |
| — | SplatsDB | — | Rust, embedded+cluster, GPL-3.0 |
| Product | Free Tier | Paid Tier | Enterprise |
|---|---|---|---|
| Pinecone | Limited | $50/mo min | $500/mo min |
| Zilliz/Milvus Cloud | 5GB, 2.5M vCUs | $99/GB/mo | $155/mo |
| Qdrant Cloud | 1GB RAM | Usage-based | Custom |
| Weaviate Cloud | 14-day trial | $45/mo Flex | $400/mo |
| Chroma Cloud | $0 + usage | $250/mo Team | Custom |
| LanceDB | Free (self-hosted) | — | — |
| SplatsDB | Free forever | — | — |
-
Spatial Memory — Wing/Room/Hall/Tunnel with auto-labeling and auto-tunnel detection. No other vector DB has hierarchical spatial navigation. Qdrant has generic payload filters but no spatial hierarchy. On LongMemEval, the retrieval approach (embeddings + cosine) achieves 96.6% recall@10 (NumPy brute-force baseline; see methodology note in benchmarks section) vs 86.2% best in original paper.
-
MCP Server — 18 tools ready to use with any MCP-compatible agent (Claude, GPT, etc.). Zero glue code. Competitors require custom SDK integrations or REST wrappers.
-
Distributed Sharding — 3 strategies (hash/cluster/geo), load balancing (round-robin/least-loaded/broadcast), RRF result fusion, energy-aware routing, offline sync with retry. All built-in, no external dependencies.
-
Embedded + Cluster — Works as CLI tool, Rust library, MCP server, or distributed cluster. Milvus and Qdrant require a separate server process. Chroma is embedded but limited to single-node.
-
Cost — Free, self-hosted, no usage limits. Pinecone charges $50/mo minimum. Zilliz starts at $99/GB. SplatsDB runs on a $5 VPS.
| Area | Leader | Gap |
|---|---|---|
| Raw QPS (single-node) | Faiss | 4–6x faster (optimized C++ + SIMD) |
| Ecosystem integrations | Milvus, Qdrant | LangChain, LlamaIndex, Haystack, 50+ connectors |
| Managed cloud | Pinecone, Qdrant | Zero-ops deployments, auto-scaling |
| Dataset scale (proven) | Milvus | Billions of vectors (SplatsDB tested to 100K) |
| Multi-tenancy | Qdrant | Native sharding per tenant |
| Maturity | All competitors | Years of production use, large communities |
AI agents with long-term conversational memory, RAG pipelines, and knowledge management — where spatial pre-filtering, MCP integration, and zero-cost self-hosting matter more than raw QPS at billion-scale.
The closest competitor in philosophy is LanceDB (Rust, embedded, Apache 2.0, 9.9k GitHub stars). Key differences: LanceDB uses IVF-PQ (not HNSW), has no spatial memory, no MCP server, no knowledge graph, no distributed sharding. SplatsDB trades ecosystem maturity for deeper agent memory features.
A comprehensive pentest and architecture review was conducted across five attack surfaces: binary file parsing, path traversal, text compression, fuzzing (3 000 iterations), and MCP injection.
| Severity | Issue | Mitigation |
|---|---|---|
| HIGH | OOM crash via malicious .bin file in cli/helpers.rs |
MAX_LOAD_ELEMENTS cap (1 B elements) + MAX_DECOMPRESS_SIZE (100 MB) |
| MEDIUM | Panic on dim=0 vectors |
Input validation rejects zero-dimension vectors |
| LOW | u32 size header in text compression decompressor |
Bounded length check before allocation |
3 vulnerabilities found, 0 critical for data confidentiality.
persistence.rs—copy_dir_recursivenow enforces a depth limit of 128 to prevent path-traversal escalation.mcp_server.rs— Embedding fetch moved outsideMutexguard to eliminate a concurrency deadlock surface.
- API-key authentication is optional by default, enabled via the
SPLATSDB_API_KEYenvironment variable. - API-key comparison uses constant-time equality to prevent timing side-channel attacks.
| Category | Tests | Description |
|---|---|---|
| Integration | 3 | End-to-end security workflows |
| QA validation | 31 | Input validation and edge-case coverage |
| Chaos / stress | 8 | Concurrency, memory pressure, WAL under load |
| Total | 42 | All passing (127 s) |
The 8 chaos tests cover: concurrent stress (10 threads × 1 000 ops), memory pressure, large vectors, rapid-fire writes, interleaved operations, edge cases, WAL under load, and heavy compaction.
All benchmarks can be reproduced independently. Full code in benchmarks/benchmark_reproducible.py.
# Run full suite (ANN-Benchmarks + LongMemEval)
python benchmarks/benchmark_reproducible.py --suite all
# Run single dataset
python benchmarks/benchmark_reproducible.py --suite ann --dataset sift-128-euclidean| Benchmark | What it tests | Status |
|---|---|---|
| ANN-Benchmarks | Vector search quality (SIFT, GloVe, NYTimes) | ✅ Published |
| BEIR | Information retrieval (5 domains) | ✅ Published |
| LOCOMO | Long-form conversational memory | ✅ Published |
| MemoBench (synthetic) | Multi-agent dialog memory | ✅ Published |
| LongMemEval | Agent memory recall | 🔄 Planned |
Standard BEIR protocol — NDCG@10, Recall@10 via pytrec-eval. Same evaluator, same qrels, same queries.
v1 — Baseline (all-MiniLM-L6-v2, dense only)
| Domain | Docs | Queries | NDCG@10 | Recall@10 |
|---|---|---|---|---|
| scifact | 5,183 | 300 | 0.6451 | 0.7833 |
| trec-covid | 171,332 | 50 | 0.4725 | 0.0128 |
| fiqa | 57,638 | 648 | 0.3687 | 0.4413 |
| arguana | 8,674 | 1,406 | 0.5017 | 0.7902 |
| nfcorpus | 3,633 | 323 | 0.3159 | 0.1550 |
| Average | 0.4608 | 0.4365 |
v2 — Improved (BGE-small + BM25 + RRF fusion + entity boost)
Uses SplatsDB's full pipeline: BM25 from bm25_index.rs, RRF from cluster/aggregator.rs, entity extraction from entity_extractor.rs, graph boost from graph_splat.rs.
| Domain | NDCG@10 | vs v1 | Recall@10 | vs v1 |
|---|---|---|---|---|
| scifact | 0.7200 | +0.075 | 0.8452 | +0.064 |
| arguana | 0.5950 | +0.093 | 0.8542 | +0.043 |
| nfcorpus | 0.3371 | -0.005 | 0.1582 | +0.024 |
| Average (3/5) | 0.5507 | +0.054 | 0.6192 | +0.044 |
Note: trec-covid and fiqa v2 results not completed due to BM25 scoring performance on >50K corpora in Python. The Rust implementation would handle this efficiently.
Source: v1 results from
bench-data/beir_results.json(BEIR v1.0, all-MiniLM-L6-v2, standard protocol). v2 results from internal benchmark runs — result file not yet published.
Alternative Approaches — A/B tested on scifact/arguana/nfcorpus
After v2, we tested 4 alternative approaches to identify what actually improves retrieval:
| Approach | NDCG@10 avg | vs Baseline | Verdict |
|---|---|---|---|
| Baseline (BGE-small dense) | 0.5507 | — | Reference |
| Passage-level retrieval | 0.5432 | -0.0075 | ❌ No benefit (short docs) |
| Title-only cross-encoder | 0.1422 | -0.4085 | ❌ Titles ≠ content in BEIR |
| BGE-base (larger model) | 0.5806 | +0.0299 | ✅ Best improvement |
| BM25 + RRF fusion | ~0.55 | ~0 | Neutral on semantic queries |
Key insight: the biggest improvement (+3% NDCG) comes from using a better embedding model, not from retrieval tricks. This confirms the bottleneck is the semantic gap — the embedding model's ability to match query intent with document content.
1,982 questions across 10 conversations, 5 question categories.
| Category | v1 Recall@10 | v2 Recall@10 | Delta |
|---|---|---|---|
| Session matching (cat_5) | 100.0% | 100.0% | = |
| Causal | 73.7% | 77.3% | +3.6% |
| Factoid | 52.8% | 52.5% | -0.3% |
| Temporal | 31.8% | 50.8% | +19.0% |
| Counterfactual | 38.0% | 41.3% | +3.3% |
| Overall | 68.2% | 72.9% | +4.7% |
v2 uses BGE-small + session-level retrieval + BM25 hybrid via RRF. The +19% improvement on temporal queries shows BM25 helps when queries contain specific keywords (dates, names).
We tested whether the knowledge graph helps with multi-hop question answering — questions that require finding MULTIPLE documents and connecting information across them.
Example: "Were Scott Derrickson and Ed Wood of the same nationality?" → Need docs about BOTH people.
| Method | k | SP Recall | All Found | vs Dense |
|---|---|---|---|---|
| Dense BGE-small | 4 | 86.7% | 224/300 | Reference |
| Dense BGE-small | 2 | 71.3% | 144/300 | - |
| Graph 1-hop + dense seed | 2 | 67.0% | 120/300 | -4.3% |
| Graph 2-hop + dense seed | 4 | 83.8% | 209/300 | -2.9% |
| Entity lookup + dense hybrid | 4 | — | 176/300 | -16.0% |
Conclusion: The knowledge graph does NOT improve retrieval, even for multi-hop questions where you'd expect it to help. Dense embeddings with BGE-small are already strong enough to find relevant documents. Graph expansion adds topologically-connected but semantically irrelevant docs.
When the graph IS useful: Exploration and discovery — finding connected topics you didn't search for, tracing entity relationships, content recommendation. These are different tasks from retrieval ranking.
We publish negative results because they're more valuable than positive ones. They tell you what NOT to waste time on.
| Technique | Tested on | NDCG change | Why it failed |
|---|---|---|---|
| Graph traversal | scifact, arguana | 0.0000 | Dense already finds relevant docs; graph expansion adds topologically-connected but irrelevant docs |
| Query expansion via entities | scifact, arguana | -0.0474 | Weighted average of query + entity embeddings dilutes original query signal |
| Full-text cross-encoder | scifact | -0.1511 | BEIR docs > 512 tokens → model truncates → loses key evidence |
| Title-only cross-encoder | scifact, arguana, nfcorpus | -0.4085 | BEIR titles are paper names, not content descriptions; queries match body text |
| Passage-level chunking | scifact, arguana, nfcorpus | -0.0075 | Most BEIR docs are short (avg 200 words); chunking adds noise |
| Graph 1-hop expansion | HotpotQA (300 questions) | -4.3% | Topologically-connected ≠ semantically relevant |
| Graph 2-hop expansion | HotpotQA (300 questions) | -2.9% | More hops = more noise |
| Entity lookup hybrid | HotpotQA (300 questions) | -16.0% | Entities match docs that MENTION the entity, not ABOUT it |
Takeaway: For standard information retrieval, the ranking of improvement levers is:
- Better embedding model (BGE-base > BGE-small > MiniLM) — biggest single gain
- BM25 + RRF fusion — helps keyword-heavy queries, neutral otherwise
- Session/context grouping — helps conversational memory tasks
- Everything else — graph, expansion, chunking, reranking — either neutral or harmful
The knowledge graph remains SplatsDB's unique strength for exploration and discovery (traverse connections between entities), just not for standard query-answer retrieval.
We publish this checklist because the vector search industry has a credibility problem. If you see a vector DB making benchmark claims, ask:
- Is the benchmark code open-source and reproducible?
- Are ground truth labels computed independently (not by the index itself)?
- Is k_search smaller than the default pull size? (If k=50 and you pull 50, recall is trivially 100%)
- Were any questions or queries hard-coded with known-good answers?
- Is the git history transparent (not squashed to hide changes)?
- Are the authors identifiable (not anonymous)?
- Can you reproduce the exact numbers on your own hardware?
If a project can't answer "yes" to all of these, their benchmarks are not trustworthy.
Copyright (c) 2024–2026 Brian Schwabauer
Licensed under the GPL-3.0 License — use commercially, modify, distribute freely. Copyleft — derivatives must also be GPL-3.0.




