lodedb 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lodedb-0.2.1 → lodedb-0.3.0}/PKG-INFO +43 -12
- {lodedb-0.2.1 → lodedb-0.3.0}/README.md +42 -11
- {lodedb-0.2.1 → lodedb-0.3.0}/pyproject.toml +1 -1
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/__init__.py +1 -1
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/core.py +607 -54
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/runtime_policy.py +46 -0
- lodedb-0.3.0/src/lodedb/engine/wal_store.py +282 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/cli.py +16 -1
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/db.py +32 -4
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/server.py +10 -2
- {lodedb-0.2.1 → lodedb-0.3.0}/LICENSE +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/NOTICE +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/__main__.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/config.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/__init__.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_atomic_io.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_commit_manifest.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_filelock.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_filter_plan.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_lexical.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/_predicate.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/document_text_store.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/embedding_backends.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/gpu_turbovec.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/index.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/lexical_index_store.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/mps_turbovec.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/route_profiles.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/route_registry.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/state_journal_store.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/turbovec_delta_store.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/turbovec_index.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/engine/turbovec_resident.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/graph/__init__.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/graph/_store.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/graph/knowledge_graph.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/__init__.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/backends.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/benchmark.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/doctor.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/__init__.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/langchain.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/llama_index.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/llama_index_graph.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/integrations/mem0.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/mcp_install.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/mcp_server.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/local/presets.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/src/lodedb/py.typed +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/Cargo.lock +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/Cargo.toml +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/Cargo.toml +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/README.md +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/build.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/examples/dump_state.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/examples/kernel_xtest.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/codebook.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/encode.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/error.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/id_map.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/io.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/lib.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/pack.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/rotation.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/src/search.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/codebook.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/concurrent_search.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/distortion.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/encode.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/encoded_rows.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/filtering.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/id_map.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/input_validation.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/io_versioning.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/kernel_correctness.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/lazy_init.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/reconstruction.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/rotation.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/state_sequences.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/swap_remove.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec/tests/tqplus_calibration.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/Cargo.toml +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/README.md +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/build.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/__init__.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/_dedup.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/_persist.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/agno.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/haystack.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/langchain.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/python/turbovec/llama_index.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/src/lib.rs +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_agno.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_dedup.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_filtering.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_haystack.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_id_map.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_index.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_langchain.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_llama_index.py +0 -0
- {lodedb-0.2.1 → lodedb-0.3.0}/third_party/turbovec/turbovec-python/tests/test_security.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lodedb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -36,23 +36,34 @@ Project-URL: Homepage, https://github.com/Egoist-Machines/LodeDB
|
|
|
36
36
|
Project-URL: Issues, https://github.com/Egoist-Machines/LodeDB/issues
|
|
37
37
|
Project-URL: Repository, https://github.com/Egoist-Machines/LodeDB
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
<h1 align="center">LodeDB</h1>
|
|
40
|
+
<p align="center">🔥 the <b>fastest</b> and <i>most compact</i> embedded vector database in the world 🌍</p>
|
|
40
41
|
|
|
41
42
|
[](LICENSE)
|
|
42
43
|
[](pyproject.toml)
|
|
43
44
|
|
|
44
|
-
**A fast, exact embedded vector database for local RAG: in-process, on-disk, no server.**
|
|
45
|
-
|
|
46
45
|
*Built by [Egoist Machines, Inc.](https://egoistmachines.com) - efficient full-stack infrastructure
|
|
47
46
|
for reliable AI systems.*
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
LodeDB is great for local RAG; it's _extremely fast_, exact, in-process, and on-disk. We're the **best drop-in** durable memory backend for **LangChain, LlamaIndex, and mem0** across all metrics that matter: the most
|
|
49
|
+
compact on disk, GPU-accelerated, and sub-millisecond on durable writes. Point any of them at
|
|
50
|
+
LodeDB instead of its default store. Over 17.5k documents, per framework default:
|
|
51
|
+
|
|
52
|
+
| vs the framework's default store | LangChain `InMemoryVectorStore` | LlamaIndex `SimpleVectorStore` | mem0 Qdrant |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| On-disk footprint | **7.2× smaller** (28 vs 199 MB) | **5.3× smaller** (28 vs 145 MB) | **4.6× smaller** (15 vs 70 MB) |
|
|
55
|
+
| Single-query p50 (CPU) | **~410× faster** (0.8 vs 345 ms) | **~500× faster** (0.9 vs 427 ms) | **~25× faster** (0.9 vs 23 ms) |
|
|
56
|
+
| Batched retrieval, 64 (GPU) | **~2,000×** (6,280 vs ~3 qps) | **~2,800×** (5,744 vs ~2 qps) | **~62×** (2,686 vs 43 qps) |
|
|
57
|
+
| Durable add of one memory | **~10,000× faster** (0.8 ms vs 8.6 s) | **~22,000× faster** (0.9 ms vs 19.3 s) | 0.9 vs 0.5 ms (both sub-ms) |
|
|
58
|
+
|
|
59
|
+
[Full benchmark, all backends
|
|
60
|
+
(FAISS, Chroma, Qdrant, LanceDB, sqlite-vec, pgvector), and method.](benchmarks/memory_integrations)
|
|
61
|
+
|
|
62
|
+
Most embedded vector databases stop at the CPU. LodeDB runs the same on-disk index **on the
|
|
63
|
+
GPU** when you have one: batched search hits *24k queries/sec on an A10 and 50k qps on an L40S*,
|
|
51
64
|
2.8× to 4.8× the all-CPU ceiling, with recall unchanged. It also persists changed rows
|
|
52
65
|
incrementally, so a commit stays **sub-millisecond even at 1M vectors**.
|
|
53
66
|
|
|
54
|
-
Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
|
|
55
|
-
|
|
56
67
|
- **GPU-resident batch search**: an fp16 copy of the index lives on the GPU, scored with a
|
|
57
68
|
tiled GEMM plus a streaming top-k (`[gpu]`, Linux/CUDA). [How it works](#gpu-resident-index).
|
|
58
69
|
- **O(changed) persistence**: commits only the rows that changed, 173× to 1,308× faster
|
|
@@ -196,7 +207,8 @@ db.search("E1234", k=5, mode="hybrid") # BM25 + RRF: surfaces it in the top-k
|
|
|
196
207
|
db.search("E1234", k=5, mode="lexical") # BM25 ranking alone, no vector scan
|
|
197
208
|
```
|
|
198
209
|
|
|
199
|
-
|
|
210
|
+
<details>
|
|
211
|
+
<summary><b>Prerequisites</b></summary>
|
|
200
212
|
|
|
201
213
|
`mode="hybrid"` and `mode="lexical"` build a BM25 index over your text, so they need a text
|
|
202
214
|
source enabled when you open the database. `mode="vector"` (the default) needs nothing.
|
|
@@ -210,8 +222,10 @@ source enabled when you open the database. `mode="vector"` (the default) needs n
|
|
|
210
222
|
Either source is enough and you can enable both. `store_text=True` is the default, so hybrid
|
|
211
223
|
works out of the box. With neither source enabled, a hybrid or lexical query raises a clear,
|
|
212
224
|
actionable error rather than silently degrading.
|
|
225
|
+
</details>
|
|
213
226
|
|
|
214
|
-
|
|
227
|
+
<details>
|
|
228
|
+
<summary><b>How it works</b></summary>
|
|
215
229
|
|
|
216
230
|
A `filter` constrains both rankers, so `mode="hybrid"` with a filter returns the true top-k of
|
|
217
231
|
the matching subset. The vector half of a hybrid query runs on the same scan as `mode="vector"`,
|
|
@@ -219,8 +233,10 @@ including the GPU-resident batch scan that serves `search_many`; only the BM25 r
|
|
|
219
233
|
fusion run on the CPU, and the vector kernel and on-disk format are untouched. The serving BM25
|
|
220
234
|
index lives in memory and is maintained incrementally: a small mutation folds just the changed
|
|
221
235
|
chunks into the existing index, so a single `add` never forces a full re-tokenization.
|
|
236
|
+
</details>
|
|
222
237
|
|
|
223
|
-
|
|
238
|
+
<details>
|
|
239
|
+
<summary><b>Durable lexical index (`index_text=True`)</b></summary>
|
|
224
240
|
|
|
225
241
|
By default the BM25 index is rebuilt from the retained raw text, so it needs `store_text=True`
|
|
226
242
|
and is re-tokenized on the first hybrid query after opening. Pass `index_text=True` to persist
|
|
@@ -241,6 +257,7 @@ db.close()
|
|
|
241
257
|
reopened = LodeDB(path="./data", index_text=True, store_text=False)
|
|
242
258
|
reopened.search("E1234", k=5, mode="hybrid") # works after reopen, rebuilt from persisted terms
|
|
243
259
|
```
|
|
260
|
+
</details>
|
|
244
261
|
|
|
245
262
|
## GPU-resident index
|
|
246
263
|
|
|
@@ -339,6 +356,9 @@ your host at `lodedb mcp`:
|
|
|
339
356
|
|
|
340
357
|
```bash
|
|
341
358
|
pip install "lodedb[mcp]"
|
|
359
|
+
|
|
360
|
+
# for coding assistant:
|
|
361
|
+
lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
|
|
342
362
|
```
|
|
343
363
|
|
|
344
364
|
It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
|
|
@@ -351,7 +371,7 @@ the server with `--exclude-text` to return metrics only (this also withdraws `lo
|
|
|
351
371
|
`--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
|
|
352
372
|
query text never leaves the process.
|
|
353
373
|
|
|
354
|
-
### One command
|
|
374
|
+
### One command install
|
|
355
375
|
|
|
356
376
|
`lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
|
|
357
377
|
find the file or hand-write the JSON/TOML:
|
|
@@ -423,6 +443,17 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
|
|
|
423
443
|
- **Durability is `fast` by default.** Commits are *atomic* but not fsync'd. Pass
|
|
424
444
|
`durability="fsync"` (or `--durability fsync` / `LODEDB_DURABILITY=fsync`) to fsync each
|
|
425
445
|
file and its directory on commit for power-loss durability, at some commit-throughput cost.
|
|
446
|
+
- **WAL commit by default for low-latency durable writes.** Each `add`/`remove` appends one
|
|
447
|
+
framed record to a `<key>.wal` log and a full generation is checkpointed periodically, so a
|
|
448
|
+
durable single add costs roughly an order of magnitude less than publishing a whole generation
|
|
449
|
+
per write, into the sqlite-vec/qdrant range (see the comparison up top). The WAL is replayed
|
|
450
|
+
crash-atomically on reopen (a half-written trailing record is discarded), every writable open
|
|
451
|
+
folds it into a clean committed generation, and `close()`/`persist()` checkpoint it. WAL is
|
|
452
|
+
single-writer: a concurrent `open_readonly` reader still loads a consistent committed
|
|
453
|
+
generation, but the last *checkpointed* one, not the writer's in-flight WAL. Pass
|
|
454
|
+
`commit_mode="generation"` (or `LODEDB_COMMIT_MODE=generation`) for the classic path that
|
|
455
|
+
publishes a crash-atomic, MVCC-readable generation on every write; pick it when many
|
|
456
|
+
out-of-process readers must see each write the instant it commits.
|
|
426
457
|
- **Local filesystems only.** The OS advisory lock is unreliable on NFS/SMB.
|
|
427
458
|
|
|
428
459
|
## Limitations
|
|
@@ -1,20 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">LodeDB</h1>
|
|
2
|
+
<p align="center">🔥 the <b>fastest</b> and <i>most compact</i> embedded vector database in the world 🌍</p>
|
|
2
3
|
|
|
3
4
|
[](LICENSE)
|
|
4
5
|
[](pyproject.toml)
|
|
5
6
|
|
|
6
|
-
**A fast, exact embedded vector database for local RAG: in-process, on-disk, no server.**
|
|
7
|
-
|
|
8
7
|
*Built by [Egoist Machines, Inc.](https://egoistmachines.com) - efficient full-stack infrastructure
|
|
9
8
|
for reliable AI systems.*
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
LodeDB is great for local RAG; it's _extremely fast_, exact, in-process, and on-disk. We're the **best drop-in** durable memory backend for **LangChain, LlamaIndex, and mem0** across all metrics that matter: the most
|
|
11
|
+
compact on disk, GPU-accelerated, and sub-millisecond on durable writes. Point any of them at
|
|
12
|
+
LodeDB instead of its default store. Over 17.5k documents, per framework default:
|
|
13
|
+
|
|
14
|
+
| vs the framework's default store | LangChain `InMemoryVectorStore` | LlamaIndex `SimpleVectorStore` | mem0 Qdrant |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| On-disk footprint | **7.2× smaller** (28 vs 199 MB) | **5.3× smaller** (28 vs 145 MB) | **4.6× smaller** (15 vs 70 MB) |
|
|
17
|
+
| Single-query p50 (CPU) | **~410× faster** (0.8 vs 345 ms) | **~500× faster** (0.9 vs 427 ms) | **~25× faster** (0.9 vs 23 ms) |
|
|
18
|
+
| Batched retrieval, 64 (GPU) | **~2,000×** (6,280 vs ~3 qps) | **~2,800×** (5,744 vs ~2 qps) | **~62×** (2,686 vs 43 qps) |
|
|
19
|
+
| Durable add of one memory | **~10,000× faster** (0.8 ms vs 8.6 s) | **~22,000× faster** (0.9 ms vs 19.3 s) | 0.9 vs 0.5 ms (both sub-ms) |
|
|
20
|
+
|
|
21
|
+
[Full benchmark, all backends
|
|
22
|
+
(FAISS, Chroma, Qdrant, LanceDB, sqlite-vec, pgvector), and method.](benchmarks/memory_integrations)
|
|
23
|
+
|
|
24
|
+
Most embedded vector databases stop at the CPU. LodeDB runs the same on-disk index **on the
|
|
25
|
+
GPU** when you have one: batched search hits *24k queries/sec on an A10 and 50k qps on an L40S*,
|
|
13
26
|
2.8× to 4.8× the all-CPU ceiling, with recall unchanged. It also persists changed rows
|
|
14
27
|
incrementally, so a commit stays **sub-millisecond even at 1M vectors**.
|
|
15
28
|
|
|
16
|
-
Fast on a laptop. Faster on a GPU. Exact every time. Never phones home.
|
|
17
|
-
|
|
18
29
|
- **GPU-resident batch search**: an fp16 copy of the index lives on the GPU, scored with a
|
|
19
30
|
tiled GEMM plus a streaming top-k (`[gpu]`, Linux/CUDA). [How it works](#gpu-resident-index).
|
|
20
31
|
- **O(changed) persistence**: commits only the rows that changed, 173× to 1,308× faster
|
|
@@ -158,7 +169,8 @@ db.search("E1234", k=5, mode="hybrid") # BM25 + RRF: surfaces it in the top-k
|
|
|
158
169
|
db.search("E1234", k=5, mode="lexical") # BM25 ranking alone, no vector scan
|
|
159
170
|
```
|
|
160
171
|
|
|
161
|
-
|
|
172
|
+
<details>
|
|
173
|
+
<summary><b>Prerequisites</b></summary>
|
|
162
174
|
|
|
163
175
|
`mode="hybrid"` and `mode="lexical"` build a BM25 index over your text, so they need a text
|
|
164
176
|
source enabled when you open the database. `mode="vector"` (the default) needs nothing.
|
|
@@ -172,8 +184,10 @@ source enabled when you open the database. `mode="vector"` (the default) needs n
|
|
|
172
184
|
Either source is enough and you can enable both. `store_text=True` is the default, so hybrid
|
|
173
185
|
works out of the box. With neither source enabled, a hybrid or lexical query raises a clear,
|
|
174
186
|
actionable error rather than silently degrading.
|
|
187
|
+
</details>
|
|
175
188
|
|
|
176
|
-
|
|
189
|
+
<details>
|
|
190
|
+
<summary><b>How it works</b></summary>
|
|
177
191
|
|
|
178
192
|
A `filter` constrains both rankers, so `mode="hybrid"` with a filter returns the true top-k of
|
|
179
193
|
the matching subset. The vector half of a hybrid query runs on the same scan as `mode="vector"`,
|
|
@@ -181,8 +195,10 @@ including the GPU-resident batch scan that serves `search_many`; only the BM25 r
|
|
|
181
195
|
fusion run on the CPU, and the vector kernel and on-disk format are untouched. The serving BM25
|
|
182
196
|
index lives in memory and is maintained incrementally: a small mutation folds just the changed
|
|
183
197
|
chunks into the existing index, so a single `add` never forces a full re-tokenization.
|
|
198
|
+
</details>
|
|
184
199
|
|
|
185
|
-
|
|
200
|
+
<details>
|
|
201
|
+
<summary><b>Durable lexical index (`index_text=True`)</b></summary>
|
|
186
202
|
|
|
187
203
|
By default the BM25 index is rebuilt from the retained raw text, so it needs `store_text=True`
|
|
188
204
|
and is re-tokenized on the first hybrid query after opening. Pass `index_text=True` to persist
|
|
@@ -203,6 +219,7 @@ db.close()
|
|
|
203
219
|
reopened = LodeDB(path="./data", index_text=True, store_text=False)
|
|
204
220
|
reopened.search("E1234", k=5, mode="hybrid") # works after reopen, rebuilt from persisted terms
|
|
205
221
|
```
|
|
222
|
+
</details>
|
|
206
223
|
|
|
207
224
|
## GPU-resident index
|
|
208
225
|
|
|
@@ -301,6 +318,9 @@ your host at `lodedb mcp`:
|
|
|
301
318
|
|
|
302
319
|
```bash
|
|
303
320
|
pip install "lodedb[mcp]"
|
|
321
|
+
|
|
322
|
+
# for coding assistant:
|
|
323
|
+
lodedb mcp install --client claude-code # or: claude-desktop | cursor | lm-studio | codex | all
|
|
304
324
|
```
|
|
305
325
|
|
|
306
326
|
It exposes `lodedb_add`, `lodedb_search`, `lodedb_remove`, and `lodedb_stats`, plus
|
|
@@ -313,7 +333,7 @@ the server with `--exclude-text` to return metrics only (this also withdraws `lo
|
|
|
313
333
|
`--no-store-text` to keep no text on disk at all. `lodedb_stats` is always metrics-only and raw
|
|
314
334
|
query text never leaves the process.
|
|
315
335
|
|
|
316
|
-
### One command
|
|
336
|
+
### One command install
|
|
317
337
|
|
|
318
338
|
`lodedb mcp install` writes the correct entry to a client's config for you, so you do not have to
|
|
319
339
|
find the file or hand-write the JSON/TOML:
|
|
@@ -385,6 +405,17 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
|
|
|
385
405
|
- **Durability is `fast` by default.** Commits are *atomic* but not fsync'd. Pass
|
|
386
406
|
`durability="fsync"` (or `--durability fsync` / `LODEDB_DURABILITY=fsync`) to fsync each
|
|
387
407
|
file and its directory on commit for power-loss durability, at some commit-throughput cost.
|
|
408
|
+
- **WAL commit by default for low-latency durable writes.** Each `add`/`remove` appends one
|
|
409
|
+
framed record to a `<key>.wal` log and a full generation is checkpointed periodically, so a
|
|
410
|
+
durable single add costs roughly an order of magnitude less than publishing a whole generation
|
|
411
|
+
per write, into the sqlite-vec/qdrant range (see the comparison up top). The WAL is replayed
|
|
412
|
+
crash-atomically on reopen (a half-written trailing record is discarded), every writable open
|
|
413
|
+
folds it into a clean committed generation, and `close()`/`persist()` checkpoint it. WAL is
|
|
414
|
+
single-writer: a concurrent `open_readonly` reader still loads a consistent committed
|
|
415
|
+
generation, but the last *checkpointed* one, not the writer's in-flight WAL. Pass
|
|
416
|
+
`commit_mode="generation"` (or `LODEDB_COMMIT_MODE=generation`) for the classic path that
|
|
417
|
+
publishes a crash-atomic, MVCC-readable generation on every write; pick it when many
|
|
418
|
+
out-of-process readers must see each write the instant it commits.
|
|
388
419
|
- **Local filesystems only.** The OS advisory lock is unreliable on NFS/SMB.
|
|
389
420
|
|
|
390
421
|
## Limitations
|
|
@@ -5,7 +5,7 @@ build-backend = "maturin"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lodedb"
|
|
7
7
|
# Static (maturin reads it here); keep in sync with src/lodedb/__init__.py __version__.
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.3.0"
|
|
9
9
|
description = "Local-first, privacy-by-default embedded vector database. Your data never leaves your machine."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.11"
|