BM25 Turbo ⚡
Rust · Python · WASM · CLI
The fastest BM25 scoring engine. Period.
______________________________________________________________________
28217次查询/秒 880万份文件。 8.6ms P50延迟预计算稀疏BM25,具有BMW修剪、内存映射持久性和零拷贝索引加载。专为RAG管道、搜索应用程序和ML工作流而构建。
use bm25_turbo::{BM25Builder, Method};
let index = BM25Builder::new()
.method(Method::Lucene) // Robertson, Lucene, ATIRE, BM25L, BM25+
.k1(1.5).b(0.75)
.build_from_corpus(&[
"Rust is a systems programming language",
"BM25 is a ranking function used in information retrieval",
"Machine learning models benefit from fast retrieval",
])?;
let results = index.search("information retrieval", 10)?;
for (id, score) in results.doc_ids.iter().zip(results.scores.iter()) {
println!("doc {} → {:.4}", id, score);
}为什么选择BM25 Turbo?
大多数BM25库在查询时计算分数——在每次请求时扫描倒排索引。BM25 Turbo采取了相反的方法: 在索引时间预先计算每个BM25分数 查询变成稀疏向量查找,在服务时没有数学运算。
这使得BM25 Turbo在以下情况下成为正确的选择:
- 你 多次查询同一索引 (RAG、重新排名、批量评估)
- 你需要 确定性、可重复性得分 (ML管道、实验)
- 你想要的 最简单的API (3行用于索引,1行用于查询)
- 你正在建造一个 搜索功能 并且不需要完整的搜索服务器
演出
在标准信息检索基准MS MARCO(8841823个文档,509962个查询)上进行了基准测试。启用了BMW修剪。单螺纹。
查询吞吐量
查询延迟
跨语料库大小缩放
| 语料库 | 文档 | P50延迟 | QPS | nDCG@10 |
|---|---|---|---|---|
| 科学事实 | 5183 | 67μs | 682,127 | 0.665 |
| 财务报告 | 57638 | 711μs | 50,812 | 0.254 |
| 马可女士 | 8841823 | 8.6毫秒 | 28,217 | — |
10万个文档下的语料库为亚毫秒级。在数百万个文档语料库上,BMW修剪使延迟与倒排索引引擎保持竞争力,同时保持了批处理工作负载的预先计算的评分优势。
交易
BM25涡轮前部载荷计算: 索引一次,查询数百万次索引速度较慢,因为每个BM25分数都是预先计算并压缩到CSC矩阵中的。但每个后续查询都是稀疏向量查找——在服务时间没有数学运算。
特性
5个BM25评分变体
| 变体 | 公式 | 最适合 |
|---|---|---|
| 罗伯逊 | 经典BM25(霍加皮) | 学术基准 |
| Lucene | Apache Lucene的变体 | 生产搜索(默认) |
| ATIRE | IDF没有+1平滑 | 研究比较 |
| BM25L | 长文档校正 | 具有不同文档长度的语料库 |
| BM25+ | 下限项频率 | 惩罚不匹配项 |
所有变体都支持可调 k1, b,以及 delta 参数。
宝马(Block Max WAND)修剪
在top-k检索过程中跳过非竞争性文档。对于百万文档语料库至关重要:
let mut index = BM25Builder::new()
.build_from_corpus(&corpus)?;
// Build block-max index (one-time cost)
index.build_bmw_index()?;
// Queries now skip non-competitive blocks automatically
let results = index.search_approximate("distributed systems", 10)?;BMW将得分矩阵划分为块,并保持每个块的上限。在查询评估过程中,当整个块的最大可能贡献不能超过当前的第k个最佳分数时,整个块都会被跳过。这将接触的文档数量从数百万减少到数千。
内存映射持久性
将索引保存到磁盘,并使用零拷贝内存映射立即重新加载:
use bm25_turbo::persistence;
use std::path::Path;
// Save (serializes CSC matrix + vocabulary + parameters)
persistence::save(&index, Path::new("my_index.bm25"))?;
// Standard load (deserialize into RAM)
let index = persistence::load(Path::new("my_index.bm25"))?;
// Memory-mapped load (instant, zero-copy, ideal for huge indexes)
let mmap_index = persistence::load_mmap(Path::new("my_index.bm25"))?;加载内存映射索引 微秒 无论大小。操作系统按需分页数据——10GB索引立即开始为查询提供服务,而无需等待读取完整文件。
流式索引器
通过以可配置块处理文档来索引大于可用内存的语料库:
use bm25_turbo::{StreamingBuilder, Method};
let mut builder = StreamingBuilder::new()
.chunk_size(100_000) // Process 100K docs at a time
.method(Method::Lucene);
builder.add_documents(&["doc one", "doc two", "doc three"]);
let index = builder.build()?;峰值内存: O(chunk_size × avg_tokens) 而不是 O(total_corpus).
预写日志(WAL)
在不重建整个索引的情况下添加和删除文档:
use bm25_turbo::wal::WriteAheadLog;
let mut wal = WriteAheadLog::new();
// Incremental updates
index.add_documents(&mut wal, &["new document about Rust"])?;
wal.delete_documents(&[42, 87])?;
// Compact when the WAL grows large
index.compact(&mut wal)?;内置令牌生成器
17种语言词干分析器,可配置停用词删除:
use bm25_turbo::Tokenizer;
use rust_stemmers::Algorithm;
let tokenizer = Tokenizer::builder()
.stemmer(Algorithm::English)
.stopwords(vec!["the".into(), "a".into(), "an".into(), "is".into()])
.build()?;
let tokens = tokenizer.tokenize("Running distributed systems at scale");
// → ["run", "distribut", "system", "scale"]支持的语言:阿拉伯语、丹麦语、荷兰语、英语、芬兰语、法语、德语、匈牙利语、意大利语、挪威语、葡萄牙语、罗马尼亚语、俄语、西班牙语、瑞典语、泰米尔语、土耳其语。
分布式搜索(gRPC)
在多个节点上分割大型索引,并将其作为一个节点进行查询:
use bm25_turbo::distributed::{QueryCoordinator, ShardEndpoint};
// Define shard endpoints (one per machine/core)
let shards = vec![
ShardEndpoint { endpoint: "http://[::1]:50051".into(), shard_id: 0, doc_id_offset: 0 },
ShardEndpoint { endpoint: "http://[::1]:50052".into(), shard_id: 1, doc_id_offset: 500_000 },
];
// Coordinator fans out queries and merges results
let coordinator = QueryCoordinator::connect(shards).await?;
let results = coordinator.query("distributed query", 10).await?;接口
命令行界面
# Index a corpus
bm25-turbo index --input corpus.jsonl --output index.bm25 --field text
# Search
bm25-turbo search --index index.bm25 --query "information retrieval" -k 10
# Start HTTP server
bm25-turbo serve --index index.bm25 --port 8080
# Push/pull from HuggingFace Hub
bm25-turbo push --index index.bm25 --repo username/my-index
bm25-turbo pull --repo username/my-index --output index.bm25支持CSV、JSONL、JSON数组和纯文本(每行一个文档)。如果可能,自动检测格式。
HTTP服务器
$ bm25-turbo serve --index index.bm25 --port 8080# Search
curl -X POST http://localhost:8080/search \
-H "Content-Type: application/json" \
-d '{"query": "machine learning", "k": 10}'
# Health check
curl http://localhost:8080/health
# Index statistics
curl http://localhost:8080/statsMCP服务器(AI代理集成)
通过以下方式将BM25搜索作为AI代理的工具 模型上下文协议:
bm25-turbo mcp --index index.bm25 --port 8080MCP服务器公开了两个工具:
bm25_search--使用可配置的top-k查询索引bm25_index_stats--获取索引元数据(文档计数、词汇大小)
适用于Claude、ChatGPT和任何兼容MCP的代理框架。
Python绑定
from bm25_turbo_python import BM25
# Build an index
engine = BM25(method="lucene", k1=1.5, b=0.75)
engine.index(["Rust is fast", "Python is flexible", "BM25 ranks documents"])
# Search — returns (doc_ids, scores) tuple
doc_ids, scores = engine.search("fast programming", k=5)
for doc_id, score in zip(doc_ids, scores):
print(f" doc {doc_id}: {score:.4f}")
# Save / load
engine.save("my_index.bm25")
engine = BM25.load("my_index.bm25")Wasm(浏览器/边缘)
import init, { WasmBM25 } from 'bm25-turbo-wasm';
await init();
const index = new WasmBM25(
["JavaScript runs everywhere",
"WebAssembly enables near-native performance",
"BM25 is a proven ranking algorithm"],
"lucene", // method (optional)
1.5, // k1 (optional)
0.75 // b (optional)
);
const results = index.search("native performance", 5);
console.log(results); // [{doc_id: 1, score: 0.82}, ...]捆绑包大小:约1.3 MB(gzip压缩:约500 KB)。无需服务器,完全在浏览器中运行。
拥抱脸中心
在上共享和发现BM25索引 拥抱脸中心:
# Push your index
bm25-turbo push --index msmarco.bm25 --repo username/msmarco-bm25-turbo
# Pull someone else's index
bm25-turbo pull --repo username/msmarco-bm25-turbo --output msmarco.bm25通用数据集(MS MARCO、NQ、SciFact、FiQA)的预构建索引可以在团队之间共享,而无需重新索引。
安装
锈
[dependencies]
bm25-turbo = "0.1"命令行界面
cargo install bm25-turbo-clipython
pip install bm25-turbo需要Python 3.9-3.13。为Linux(x86_64,aarch64)、macOS(x86\_ 64,aarch 64)和Windows(x86_14)预构建的轮子。
WASM/npm
npm install bm25-turbo-wasm建筑
┌─────────────────────────────────────────────────────────┐
│ BM25 Turbo │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Tokenizer│ │ Scoring │ │ CSC │ │ BMW │ │
│ │ 17 langs │ │ 5 variants│ │ Matrix │ │ Pruning│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───┬────┘ │
│ │ │ │ │ │
│ ┌────┴──────────────┴─────────────┴─────────────┴────┐ │
│ │ Index Builder / Streaming │ │
│ └────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────────────┴───────────────────────────────┐ │
│ │ Persistence Layer │ │
│ │ Binary · Memory-Mapped · WAL · Hub │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
├──────────┬──────────┬──────────┬──────────┬────────────┤
│ CLI │ HTTP │ MCP │ Python │ WASM │
│ │ Server │ Server │ Bindings │ (npm) │
└──────────┴──────────┴──────────┴──────────┴────────────┘运作原理
- 分词 --通过可配置的停用词删除将文档拆分为带词干的标记
- 得分 --使用所选变量计算每个(学期、文档)对的BM25分数
- 压缩 --将分数存储在压缩稀疏列(CSC)矩阵中(仅限非零条目)
- 服务 --查询是稀疏向量点积:查找查询项的列,累积分数,返回top-k
CSC格式仅存储非零的BM25分数。对于一个包含880万个文档和50万个词汇的语料库,这通常压缩到2-4GB,远小于密集矩阵(约17TB)。
配置
BM25参数
| 参数 | 默认值 | 范围 | 效果 |
|---|---|---|---|
k1 | 1.5 | 0.0-3.0 | 项频率饱和。更高=重复项的权重更大 |
b | 0.75 | 0.0-1.0 | 长度归一化。0=无归一化,1=完全归一化 |
delta | 0.5 | 0.0-inf | BM25L/BM25+下限(仅与这些变体一起使用) |
选择变量
- 从Lucene开始 --这是使用和测试最广泛的变体
- 使用Robertson 如果你需要与学术论文进行精确比较
- 使用BM25L 如果你的语料库有极端的文档长度变化
- 使用BM25+ 如果短查询返回太多不相关的结果
- 使用 ATIRE 如果您正在复制ATIRE的研究结果
基准测试
复制我们的数字
# Clone and build
git clone https://github.com/TheSauceSuite/BM25-Turbo-Rust-Python-WASM-CLI-
cd bm25-turbo
cargo build --release -p bm25-turbo-bench
# Run on SciFact (quick, 5K docs)
cargo run -p bm25-turbo-bench --release --bin beir_bench -- --datasets scifact
# Run on MS MARCO (full, 8.8M docs — requires ~16GB RAM)
cargo run -p bm25-turbo-bench --release --bin beir_bench -- --datasets msmarco --max-queries 1000数据集会自动从以下位置下载 BEIR基准套件。首次运行可能需要几分钟才能下载。
BEIR基准结果
| 数据集 | 文档 | Vocab | 索引时间 | QPS | P50延迟 | nDCG@10 |
|---|---|---|---|---|---|---|
| 科学事实 | 5183 | 19927 | 223毫秒 | 682127 | 67μs | 0.665 |
| 光纤质量保证 | 57638 | 67893 | 1.8秒 | 50812 | 711μs | 0.254 |
| 马科女士 | 8841823 | ~500K | 66分钟 | 28217 | 8.6毫秒 | -- |
MS MARCO nDCG在开发集上进行了测量(6980次查询)。在1000个查询的随机样本上测量的QPS和延迟。所有基准测试在消费者桌面上都是单线程的。
用例
RAG(检索增强生成)
BM25 Turbo是RAG管道中理想的回收阶段。对知识库进行一次索引,然后为每个LLM查询检索相关上下文:
let results = index.search(&user_question, 5)?;
let context = results.doc_ids.iter()
.map(|id| documents[*id as usize].as_str())
.collect::>()
.join("\n\n");
// Feed context to your LLM混合搜索(BM25+嵌入)
将BM25词汇评分与密集嵌入相似性相结合,实现两全其美的检索:
// BM25 lexical retrieval
let bm25_results = index.search(query, 100)?;
// Dense retrieval (from your embedding model)
let dense_results = embedding_index.search(query_embedding, 100)?;
// Reciprocal Rank Fusion
let fused = rrf_merge(&bm25_results, &dense_results, k=60);批量评估
针对机器学习实验的语料库,对数十万个查询进行评分:
for query in &evaluation_queries {
let results = index.search(query, 10)?;
// Compute nDCG, MAP, recall...
}
// At 28K QPS, 500K queries finish in ~18 seconds与其他工具的比较
BM25 Turbo不是全文搜索引擎。 这是一个专注的BM25评分库。如果你需要短语查询、分面搜索或查询DSL,请使用Tantivy或Elasticsearch。如果您需要最快的BM25分数和最简单的API,或者您正在从BM25迁移,需要2000倍以上的速度,请使用BM25 Turbo。
贡献
欢迎投稿!请看 贡献.md 作为指导方针。
# Run tests
cargo test --workspace --exclude bm25-turbo-wasm
# Run clippy
cargo clippy --workspace --exclude bm25-turbo-wasm -- -D warnings
# Build WASM
cd bm25-turbo-wasm && wasm-pack build --target web许可证
BM25 Turbo具有双重许可:
开源——AGPL v3
该软件根据 GNU Affero通用公共许可证v3.0。您可以根据AGPL的条款自由使用、修改和分发此软件。如果将修改后的版本部署为网络服务,则必须在AGPL下向该服务的用户提供修改后版本的完整源代码。
商业许可证
对于无法遵守AGPL的公司和个人(例如,您想在专有软件中使用BM25 Turbo而不开源您的代码),可以使用商业许可证。
商业许可证取消了所有AGPL copyleft义务,包括:
- 在专有/闭源应用程序中使用
- 无需披露您的源代码
- 优先支持
- 定制集成协助
有关许可证查询: alessandrobenigni.com
