BM25 search engine in Rust with Python bindings. All 5 BM25 variants, streaming add/delete/update, pre-filtered search (up to 600x faster), mmap indices, and auto-persistence.
Python
Install
Usage
# Create a persistent index (auto-saves on every mutation)
=
# Search
=
# Pre-filtered search — only score a subset of documents
=
# Batch search — multiple queries at once (2.6x faster on CPU, uses rayon parallelism)
=
# results = [[(doc_id, score), ...], [(doc_id, score), ...]]
# Batch search with per-query subsets
=
# GPU-accelerated search (faster on large indices)
= # raises error if GPU unavailable
= # auto-uploads to GPU on first call
= # batch across multiple GPUs
# Streaming mutations (auto-saved to disk)
# Score a query against arbitrary documents (not in the index)
=
# scores = [0.4821, 0.0]
# Batch scoring — multiple queries against their respective doc lists
=
# Reload later — just point to the same directory
= # loads existing index, ready to search
Constructor
Rust
Add to your project
[]
= "0.1"
Usage
use ;
// Open a persistent index (auto-saves on every mutation)
let mut index = BM25open.unwrap;
// Add documents (returns assigned indices)
let ids = index.add;
// Search — returns Vec<SearchResult> with .index and .score
let results = index.search;
for r in &results
// Pre-filtered search — only score documents in the subset
let results = index.search_filtered;
// Batch search — parallel across CPU cores
let results = index.search_batch;
// GPU-accelerated search — use cuda=true to require CUDA (returns Err if unavailable)
let mut gpu_index = BM25with_options;
// Or: BM25::default().require_cuda()
// Streaming mutations (auto-saved to disk)
index.add;
index.delete;
index.update;
// Score a query against arbitrary documents (not in the index)
let scores = index.score;
// Batch scoring — multiple queries against their respective doc lists
let scores = index.score_batch;
// Reload later — just point to the same directory
let index = BM25open.unwrap; // loads existing index, ready to search
API
// Constructor
BM25new // Documents
Benchmarks
BEIR datasets (log scale). Same or better NDCG@10 than bm25s. CPU: 3.5-6x faster indexing, up to 6x faster batch search. GPU (4× H100): up to 13x faster indexing, 815x faster batch search on MS MARCO 8.8M docs. GPU search has per-query kernel launch overhead, best suited for batch querying on large datasets. Multi-GPU auto-scales with available devices.
Design
bm25s pre-computes all BM25 scores at index time (eager scoring). This makes queries fast but rebuilding the index is required to add or remove documents.
bm25x does lazy scoring -- raw term frequencies go into an inverted index, BM25 scores are computed at query time. So add/delete/update are cheap; no full rebuild needed.
Note: Deleting a document compacts the index -- all documents after it shift down by one. For example, deleting doc 1 from [0, 1, 2] makes old doc 2 become new doc 1.
Pre-filtered search is doc-centric: instead of scanning posting lists, it iterates only the subset and binary-searches each document's term frequency. O(|subset| _ |query_terms| _ log n) instead of O(|posting_list|).
On disk, the index is a flat binary format with mmap'd postings and doc lengths. RAM stays low even for large indices.
Citation
Acknowledgements
Started from bm25s, rebuilt for incremental indexing -- add, delete, and update without rebuilding the whole index.
License
Apache-2.0