zeusdb-vector-database 0.6.0__tar.gz → 0.7.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.
Files changed (71) hide show
  1. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/NOTICE +9 -15
  2. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/PKG-INFO +34 -20
  3. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/README.md +33 -19
  4. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/pyproject.toml +1 -5
  5. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/src/zeusdb_vector_database/__init__.py +1 -1
  6. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/src/zeusdb_vector_database/vector_database.py +7 -5
  7. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/Cargo.lock +4 -369
  8. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/Cargo.toml +10 -12
  9. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/distance.rs +817 -311
  10. zeusdb_vector_database-0.7.0/vdb-core/src/graph/dump.rs +1642 -0
  11. zeusdb_vector_database-0.7.0/vdb-core/src/graph/levels.rs +129 -0
  12. zeusdb_vector_database-0.7.0/vdb-core/src/graph/mod.rs +758 -0
  13. zeusdb_vector_database-0.7.0/vdb-core/src/graph/mutable/insert.rs +538 -0
  14. zeusdb_vector_database-0.7.0/vdb-core/src/graph/mutable/mod.rs +1590 -0
  15. zeusdb_vector_database-0.7.0/vdb-core/src/graph/structure.rs +802 -0
  16. zeusdb_vector_database-0.7.0/vdb-core/src/graph/test_graph.rs +116 -0
  17. zeusdb_vector_database-0.7.0/vdb-core/src/graph/traverse.rs +320 -0
  18. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/construct.rs +2 -0
  19. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/graph_guard_tests.rs +127 -137
  20. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/insert.rs +63 -14
  21. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/mod.rs +36 -20
  22. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/persist.rs +4 -8
  23. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/stats.rs +168 -15
  24. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/training.rs +5 -3
  25. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/lib.rs +3 -0
  26. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/logging.rs +3 -3
  27. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/persistence.rs +20 -18
  28. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/pq.rs +7 -5
  29. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/rerank.rs +50 -13
  30. zeusdb_vector_database-0.7.0/vdb-core/src/rng.rs +171 -0
  31. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/test_vectors.rs +3 -3
  32. zeusdb_vector_database-0.6.0/LICENSES/hnsw_rs-Apache-2.0.txt +0 -176
  33. zeusdb_vector_database-0.6.0/LICENSES/hnsw_rs-MIT.txt +0 -25
  34. zeusdb_vector_database-0.6.0/vdb-core/src/graph.rs +0 -950
  35. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/.cargo-ok +0 -1
  36. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/.cargo_vcs_info.json +0 -6
  37. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/.gitignore +0 -9
  38. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/Cargo.lock +0 -1296
  39. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/Cargo.toml +0 -153
  40. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/Cargo.toml.orig +0 -119
  41. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/Changes.md +0 -59
  42. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/LICENSE-APACHE +0 -13
  43. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/LICENSE-MIT +0 -25
  44. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/README.md +0 -168
  45. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/ZEUSDB-PATCH.md +0 -595
  46. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/ann-glove25-angular.rs +0 -220
  47. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/ann-mnist-784-euclidean.rs +0 -162
  48. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/ann-sift1m-128-euclidean.rs +0 -196
  49. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/levensthein.rs +0 -63
  50. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/random.rs +0 -80
  51. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/utils/annhdf5.rs +0 -233
  52. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/examples/utils/mod.rs +0 -3
  53. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/api.rs +0 -94
  54. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/datamap.rs +0 -456
  55. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/filter.rs +0 -24
  56. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/flatten.rs +0 -200
  57. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/hnsw.rs +0 -2023
  58. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/hnswio.rs +0 -1749
  59. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/lib.rs +0 -36
  60. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/libext.rs +0 -1240
  61. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/src/prelude.rs +0 -11
  62. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/tests/deallocation_test.rs +0 -34
  63. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/tests/filtertest.rs +0 -266
  64. zeusdb_vector_database-0.6.0/vendor/hnsw_rs/tests/serpar.rs +0 -328
  65. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/LICENSE +0 -0
  66. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/src/zeusdb_vector_database/logging_config.py +0 -0
  67. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/src/zeusdb_vector_database/py.typed +0 -0
  68. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/conversion.rs +0 -0
  69. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/filter.rs +0 -0
  70. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/input.rs +0 -0
  71. {zeusdb_vector_database-0.6.0 → zeusdb_vector_database-0.7.0}/vdb-core/src/hnsw_index/search.rs +0 -0
@@ -6,21 +6,15 @@ This product includes software developed by:
6
6
  - The Python Software Foundation (https://www.python.org/psf/)
7
7
  - The Rust Project Developers (https://www.rust-lang.org/)
8
8
  - The PyO3 Project (https://pyo3.rs/)
9
- - Jean-pierreBoth (https://crates.io/crates/hnsw_rs/)
10
-
11
- The hnsw_rs crate, version 0.3.4, is vendored in source form at vendor/hnsw_rs
12
- and carries seven ZeusDB patches, one to the reverse link layer assignment, one
13
- seeding level assignment so index builds are reproducible, one guarding the
14
- neighbour list overflow pop against stranding a point's last inbound link, one
15
- correcting the filtered search path so it cannot panic or return an empty
16
- result set, one correcting the per-layer capacity reservation so a declared
17
- index size does not commit hundreds of times the memory it needs, one
18
- correcting the level scale a reload installs, and one putting three modules
19
- ZeusDB never reaches behind default-off cargo features. See
20
- vendor/hnsw_rs/ZEUSDB-PATCH.md for the patch record. hnsw_rs is dual licensed
21
- under MIT or Apache-2.0.
22
-
23
- Third-party components may be licensed under their respective terms. See LICENSES/ for full license texts.
9
+
10
+ Releases up to and including 0.6.0 were built on a vendored copy of a Rust HNSW
11
+ crate by Jean-Pierre Both, dual licensed under MIT or Apache-2.0. From 0.7.0 the
12
+ graph structure, its traversal, its insert, its level stream and its on-disk
13
+ format are ZeusDB's own, and neither that crate nor its source is part of this
14
+ package. ZeusDB's graph reproduces the behaviour of that implementation, and the
15
+ acknowledgement is kept here for that reason.
16
+
17
+ Third-party components may be licensed under their respective terms.
24
18
 
25
19
  Licensed under the Apache License, Version 2.0 (the "License");
26
20
  you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zeusdb-vector-database
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Requires-Dist: numpy>=2.2.6,<3.0.0
@@ -35,7 +35,7 @@ Project-URL: Repository, https://github.com/zeusdb/zeusdb-vector-database
35
35
  <td><strong>Meta</strong></td>
36
36
  <td>
37
37
  <a href="https://pypi.org/project/zeusdb-vector-database/"><img src="https://img.shields.io/pypi/v/zeusdb-vector-database?label=PyPI&color=blue"></a>&nbsp;
38
- <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%7C3.11%7C3.12%7C3.13-blue?logo=python&logoColor=ffdd54"></a>&nbsp;
38
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%7C3.11%7C3.12%7C3.13%7C3.14-blue?logo=python&logoColor=ffdd54"></a>&nbsp;
39
39
  <a href="https://github.com/zeusdb/zeusdb-vector-database/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg"></a>&nbsp;
40
40
  <a href="https://www.rust-lang.org"><img src="https://img.shields.io/badge/Powered%20by-Rust-black?logo=rust&logoColor=white" alt="Powered by Rust"></a>&nbsp;
41
41
  <a href="https://github.com/ZeusDB"><img src="https://github.com/user-attachments/assets/e140d900-1160-4eaa-85c0-2b3507a5f0f5" alt="ZeusDB"></a>&nbsp;
@@ -643,17 +643,21 @@ storage_mode_description: raw_only
643
643
 
644
644
  `get_stats()` returns a `str` to `str` map. It also carries `dimension`, `space`, `m`, `ef_construction`, `expected_size`, `index_type`, `raw_vectors_stored`, `quantized_codes_stored` and `storage_mode`, plus training and compression fields once quantization is configured.
645
645
 
646
- It is also where the memory figures live, on every index rather than only on a quantized one. `graph_memory_mb` is the HNSW graph, `raw_vectors_memory_mb` is the raw vector store and `total_memory_mb` is the sum of everything below. On a quantized index `quantized_codes_memory_mb` scales with the record count, while `codebook_memory_mb` and `sdc_table_memory_mb` are fixed by `dim`, `subvectors` and `bits` and do not move as records arrive. `raw_vectors_retained` states the mode's policy: `none_once_trained` for `quantized_only`, whose raw vectors are released when training completes, and `all_records` for `quantized_with_raw`.
646
+ It is also where the memory figures live, on every index rather than only on a quantized one. `graph_memory_mb` is the HNSW graph, `raw_vectors_memory_mb` is the raw vector store, `index_bookkeeping_memory_mb` is the hash tables that find a record, and `total_memory_mb` is the sum of everything below. On a quantized index `quantized_codes_memory_mb` scales with the record count, while `codebook_memory_mb` and `sdc_table_memory_mb` are fixed by `dim`, `subvectors` and `bits` and do not move as records arrive. `raw_vectors_retained` states the mode's policy: `none_once_trained` for `quantized_only`, whose raw vectors are released when training completes, and `all_records` for `quantized_with_raw`.
647
647
 
648
648
  **The graph is usually the largest of those figures, and on a memory optimized index it is nearly all of it.** It owns a second copy of every point on top of its neighbour lists, its sixteen per point layer headers and the counters around them. At 50,000 records of `dim=1536` a trained `quantized_only` index reports 98.1 MB of graph against 2.3 MB of codes and 7.5 MB of fixed tables.
649
649
 
650
- **`total_memory_mb` is not the resident set and it does not claim to be.** It prices the five structures above. The id maps, the metadata map, the hash table slots and the allocator's own headers and fragmentation sit outside it, and together they run at roughly 1,500 bytes per record whatever the dimension. Measured on three loaded indexes of 50,000 real 1,536-dimensional embeddings, one process each:
650
+ `index_bookkeeping_memory_mb` is what the index spends on finding a record rather than on holding one. Five hash tables carry a record, being `id_map`, `rev_map`, the metadata map, the raw vector store and the code store, and two of them carry a second copy of its id. It is proportional to the record count and independent of the dimension, at 265 bytes per record with no quantization, 334 under `quantized_with_raw` and 265 under `quantized_only` at 50,000 records.
651
651
 
652
- | storage mode | `total_memory_mb` | resident | ratio |
653
- |---|---:|---:|---:|
654
- | no quantization | 692.4 MB | 805.9 MiB | 1.16x |
655
- | `quantized_with_raw` | 401.2 MB | 474.8 MiB | 1.18x |
656
- | `quantized_only` | 107.8 MB | 181.4 MiB | 1.68x |
652
+ **`total_memory_mb` is not the resident set and it does not claim to be.** It prices the six structures above, meaning the bytes the index asked the allocator for. What it leaves out is the allocator itself, being its own block headers, its rounding and its fragmentation, and that is a property of the platform rather than something the index holds. Measured on three loaded indexes of 50,000 real 1,536-dimensional embeddings, one process each, with the interpreter's own resident set subtracted:
653
+
654
+ | storage mode | `total_memory_mb` | resident | share reported | unpriced |
655
+ |---|---:|---:|---:|---:|
656
+ | no quantization | 705.1 MB | 805.4 MiB | 0.88 | 2,103 B per record |
657
+ | `quantized_with_raw` | 415.9 MB | 473.3 MiB | 0.88 | 1,202 B per record |
658
+ | `quantized_only` | 119.7 MB | 181.0 MiB | 0.66 | 1,285 B per record |
659
+
660
+ The unpriced share is almost all allocator overhead on the graph, which asks for six small blocks per point. It runs 1.25 times the graph figure with no quantization, where the per point data block is 6,144 bytes and rounding is a small share of it, and 1.63 and 1.67 times under the two quantized modes, where that block is 48 bytes. That is why `quantized_only` reports the smallest share of its resident set.
657
661
 
658
662
  Size infrastructure from the resident column rather than the reported one. The reported figure is what the index holds; the process holds that plus what the allocator takes to hold it.
659
663
 
@@ -1030,13 +1034,22 @@ The relation is curved as well as sublinear. On dbpedia-openai the calibration m
1030
1034
 
1031
1035
  What the calibration asks for at 100,000 records, against what the data needs and against the fixed 2%. Every figure is measured on a built index through the ordinary search path, 1,000 queries at `top_k=10` against exact ground truth. The requirement is the smallest fetch on that same index reaching recall at 10 of 0.99, read off a sweep of the explicit `rerank` argument:
1032
1036
 
1033
- | dataset | calibrated fetch | measured requirement | the old fixed fetch | recall, calibrated | recall, fixed |
1034
- |---|---:|---:|---:|---:|---:|
1035
- | dbpedia-openai | 776 | 750 | 2,000 | 0.9905 | 0.9954 |
1036
- | glove-100 | 7,749 | 4,500 | 2,000 | 0.9962 | 0.9723 |
1037
- | sift-128 | 596 | 450 | 2,000 | 0.9941 | 1.0000 |
1037
+ | dataset | records | calibrated fetch | measured requirement | ratio | recall at 10 | mean query |
1038
+ |---|---:|---:|---:|---:|---:|---:|
1039
+ | dbpedia-openai | 50,000 | 554 | 860 | 0.64 | 0.9883 | 3.33 ms |
1040
+ | glove-100 | 50,000 | 6,534 | 3,060 | 2.14 | 0.9982 | 35.79 ms |
1041
+ | sift-128 | 50,000 | 465 | 370 | 1.26 | 0.9953 | 1.88 ms |
1042
+ | dbpedia-openai | 100,000 | 747 | 620 | 1.20 | 0.9907 | 5.02 ms |
1043
+ | glove-100 | 100,000 | 11,439 | 4,620 | 2.48 | 0.9985 | 140.08 ms |
1044
+ | sift-128 | 100,000 | 656 | 450 | 1.46 | 0.9968 | 3.19 ms |
1038
1045
 
1039
- The calibration clears the requirement on all three. **It does not land on it and it is not meant to**, since the measurement is taken on a tenth of the records with queries drawn from the corpus, where your queries will not be.
1046
+ **The margin is not the same on the three, and glove-100 carries most of it.** The rule is one measurement per index scaled by two constants that are the same everywhere, being a safety factor of 1.75 and a bias of 0.15 on the fitted exponent. Taking the fitted exponent alone with neither constant, the extrapolation to 100,000 records asks for 4,626 candidates on glove-100 against a requirement of 4,620, which is 1.001 times what that index needs. On the other two it asks for 0.49 and 0.59 times what they need. The constants exist for those two, and on glove-100 they are margin over a fetch that was already right.
1047
+
1048
+ Neither constant can come down without costing recall elsewhere. The constant that would bring glove-100 to the 1.20 ratio dbpedia-openai has at 100,000 records takes the dbpedia-openai fetch from 747 to 359, and a sweep of that same index reads recall 0.9787 at 360 candidates. dbpedia-openai also sits under 0.99 at 10,000, 25,000 and 50,000 records, at 0.9878, 0.9800 and 0.9883, so it has nothing to give back. What separates the datasets is the ratio between the requirement on a built index and the depth the calibration measures in the code ordering over its training sample, and the training sample cannot measure that ratio because the graph does not exist yet when it runs.
1049
+
1050
+ **If query time on glove-like data matters more to you than the last thousandth of recall, name `rerank` yourself.** On glove-100 at 50,000 records a fetch of 3,060 reads recall 0.9900 at 18.14 ms against 0.9982 at 35.79 ms, so half the query time buys back 0.008 of recall.
1051
+
1052
+ The two tables below carry fetch figures measured before the codebook seeding this release ships, which moved the calibrated fetch upward on glove-100. Read their ratios as the shape rather than as current figures.
1040
1053
 
1041
1054
  **On data with no resolvable structure no fetch works.** Once the group the codes cannot separate is smaller than `top_k`, the true top ten span groups, the distances between groups differ in the fourth decimal, and nothing reaches them. At 5,000 clusters over 25,000 records, being five records to a cluster, recall at 10 reaches 0.917 at a fetch of half the corpus, and uniform points on the sphere reach 0.859. Measure recall on your own data before you rely on quantization.
1042
1055
 
@@ -1154,7 +1167,7 @@ print("saved:", sorted(os.listdir("my_index.zdb")))
1154
1167
 
1155
1168
  *Output, with the progress lines omitted*
1156
1169
  ```
1157
- saved: ['config.json', 'hnsw_index.hnsw.data', 'hnsw_index.hnsw.graph', 'manifest.json', 'mappings.bin', 'metadata.json', 'vectors.bin']
1170
+ saved: ['config.json', 'hnsw_index.zdbgraph', 'manifest.json', 'mappings.bin', 'metadata.json', 'vectors.bin']
1158
1171
  ```
1159
1172
 
1160
1173
  <br />
@@ -1224,7 +1237,7 @@ print("saved:", sorted(os.listdir("quantized_index.zdb")))
1224
1237
  quantization active: True
1225
1238
  quantization active after load: True
1226
1239
  storage mode after load: quantized_active
1227
- saved: ['config.json', 'hnsw_index.hnsw.data', 'hnsw_index.hnsw.graph', 'manifest.json', 'mappings.bin', 'metadata.json', 'pq_centroids.bin', 'pq_codes.bin', 'quantization.json', 'vectors.bin']
1240
+ saved: ['config.json', 'hnsw_index.zdbgraph', 'manifest.json', 'mappings.bin', 'metadata.json', 'pq_centroids.bin', 'pq_codes.bin', 'quantization.json', 'vectors.bin']
1228
1241
  ```
1229
1242
 
1230
1243
  <br/>
@@ -1242,11 +1255,12 @@ my_index.zdb/
1242
1255
  ├── quantization.json # PQ configuration (if enabled)
1243
1256
  ├── pq_centroids.bin # Trained centroids (if PQ trained)
1244
1257
  ├── pq_codes.bin # Quantized codes (if PQ active)
1245
- ├── hnsw_index.hnsw.graph # HNSW graph structure
1246
- └── hnsw_index.hnsw.data # HNSW graph payload
1258
+ └── hnsw_index.zdbgraph # HNSW graph structure and payload
1247
1259
  ```
1248
1260
 
1249
- `manifest.json` lists both graph files under `files_included`. The load path restores the saved graph rather than rebuilding it, so both are required to reopen a directory holding records.
1261
+ `manifest.json` lists the graph file under `files_included`. The load path restores the saved graph rather than rebuilding it, so the file is required to reopen a directory holding records.
1262
+
1263
+ A directory saved by 0.6.0 or earlier holds `hnsw_index.hnsw.graph` and `hnsw_index.hnsw.data` instead. Opening it still works: the graph is rebuilt once from the stored records, and the next `.save()` writes the single file.
1250
1264
 
1251
1265
  <br/>
1252
1266
 
@@ -14,7 +14,7 @@
14
14
  <td><strong>Meta</strong></td>
15
15
  <td>
16
16
  <a href="https://pypi.org/project/zeusdb-vector-database/"><img src="https://img.shields.io/pypi/v/zeusdb-vector-database?label=PyPI&color=blue"></a>&nbsp;
17
- <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%7C3.11%7C3.12%7C3.13-blue?logo=python&logoColor=ffdd54"></a>&nbsp;
17
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%7C3.11%7C3.12%7C3.13%7C3.14-blue?logo=python&logoColor=ffdd54"></a>&nbsp;
18
18
  <a href="https://github.com/zeusdb/zeusdb-vector-database/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg"></a>&nbsp;
19
19
  <a href="https://www.rust-lang.org"><img src="https://img.shields.io/badge/Powered%20by-Rust-black?logo=rust&logoColor=white" alt="Powered by Rust"></a>&nbsp;
20
20
  <a href="https://github.com/ZeusDB"><img src="https://github.com/user-attachments/assets/e140d900-1160-4eaa-85c0-2b3507a5f0f5" alt="ZeusDB"></a>&nbsp;
@@ -622,17 +622,21 @@ storage_mode_description: raw_only
622
622
 
623
623
  `get_stats()` returns a `str` to `str` map. It also carries `dimension`, `space`, `m`, `ef_construction`, `expected_size`, `index_type`, `raw_vectors_stored`, `quantized_codes_stored` and `storage_mode`, plus training and compression fields once quantization is configured.
624
624
 
625
- It is also where the memory figures live, on every index rather than only on a quantized one. `graph_memory_mb` is the HNSW graph, `raw_vectors_memory_mb` is the raw vector store and `total_memory_mb` is the sum of everything below. On a quantized index `quantized_codes_memory_mb` scales with the record count, while `codebook_memory_mb` and `sdc_table_memory_mb` are fixed by `dim`, `subvectors` and `bits` and do not move as records arrive. `raw_vectors_retained` states the mode's policy: `none_once_trained` for `quantized_only`, whose raw vectors are released when training completes, and `all_records` for `quantized_with_raw`.
625
+ It is also where the memory figures live, on every index rather than only on a quantized one. `graph_memory_mb` is the HNSW graph, `raw_vectors_memory_mb` is the raw vector store, `index_bookkeeping_memory_mb` is the hash tables that find a record, and `total_memory_mb` is the sum of everything below. On a quantized index `quantized_codes_memory_mb` scales with the record count, while `codebook_memory_mb` and `sdc_table_memory_mb` are fixed by `dim`, `subvectors` and `bits` and do not move as records arrive. `raw_vectors_retained` states the mode's policy: `none_once_trained` for `quantized_only`, whose raw vectors are released when training completes, and `all_records` for `quantized_with_raw`.
626
626
 
627
627
  **The graph is usually the largest of those figures, and on a memory optimized index it is nearly all of it.** It owns a second copy of every point on top of its neighbour lists, its sixteen per point layer headers and the counters around them. At 50,000 records of `dim=1536` a trained `quantized_only` index reports 98.1 MB of graph against 2.3 MB of codes and 7.5 MB of fixed tables.
628
628
 
629
- **`total_memory_mb` is not the resident set and it does not claim to be.** It prices the five structures above. The id maps, the metadata map, the hash table slots and the allocator's own headers and fragmentation sit outside it, and together they run at roughly 1,500 bytes per record whatever the dimension. Measured on three loaded indexes of 50,000 real 1,536-dimensional embeddings, one process each:
629
+ `index_bookkeeping_memory_mb` is what the index spends on finding a record rather than on holding one. Five hash tables carry a record, being `id_map`, `rev_map`, the metadata map, the raw vector store and the code store, and two of them carry a second copy of its id. It is proportional to the record count and independent of the dimension, at 265 bytes per record with no quantization, 334 under `quantized_with_raw` and 265 under `quantized_only` at 50,000 records.
630
630
 
631
- | storage mode | `total_memory_mb` | resident | ratio |
632
- |---|---:|---:|---:|
633
- | no quantization | 692.4 MB | 805.9 MiB | 1.16x |
634
- | `quantized_with_raw` | 401.2 MB | 474.8 MiB | 1.18x |
635
- | `quantized_only` | 107.8 MB | 181.4 MiB | 1.68x |
631
+ **`total_memory_mb` is not the resident set and it does not claim to be.** It prices the six structures above, meaning the bytes the index asked the allocator for. What it leaves out is the allocator itself, being its own block headers, its rounding and its fragmentation, and that is a property of the platform rather than something the index holds. Measured on three loaded indexes of 50,000 real 1,536-dimensional embeddings, one process each, with the interpreter's own resident set subtracted:
632
+
633
+ | storage mode | `total_memory_mb` | resident | share reported | unpriced |
634
+ |---|---:|---:|---:|---:|
635
+ | no quantization | 705.1 MB | 805.4 MiB | 0.88 | 2,103 B per record |
636
+ | `quantized_with_raw` | 415.9 MB | 473.3 MiB | 0.88 | 1,202 B per record |
637
+ | `quantized_only` | 119.7 MB | 181.0 MiB | 0.66 | 1,285 B per record |
638
+
639
+ The unpriced share is almost all allocator overhead on the graph, which asks for six small blocks per point. It runs 1.25 times the graph figure with no quantization, where the per point data block is 6,144 bytes and rounding is a small share of it, and 1.63 and 1.67 times under the two quantized modes, where that block is 48 bytes. That is why `quantized_only` reports the smallest share of its resident set.
636
640
 
637
641
  Size infrastructure from the resident column rather than the reported one. The reported figure is what the index holds; the process holds that plus what the allocator takes to hold it.
638
642
 
@@ -1009,13 +1013,22 @@ The relation is curved as well as sublinear. On dbpedia-openai the calibration m
1009
1013
 
1010
1014
  What the calibration asks for at 100,000 records, against what the data needs and against the fixed 2%. Every figure is measured on a built index through the ordinary search path, 1,000 queries at `top_k=10` against exact ground truth. The requirement is the smallest fetch on that same index reaching recall at 10 of 0.99, read off a sweep of the explicit `rerank` argument:
1011
1015
 
1012
- | dataset | calibrated fetch | measured requirement | the old fixed fetch | recall, calibrated | recall, fixed |
1013
- |---|---:|---:|---:|---:|---:|
1014
- | dbpedia-openai | 776 | 750 | 2,000 | 0.9905 | 0.9954 |
1015
- | glove-100 | 7,749 | 4,500 | 2,000 | 0.9962 | 0.9723 |
1016
- | sift-128 | 596 | 450 | 2,000 | 0.9941 | 1.0000 |
1016
+ | dataset | records | calibrated fetch | measured requirement | ratio | recall at 10 | mean query |
1017
+ |---|---:|---:|---:|---:|---:|---:|
1018
+ | dbpedia-openai | 50,000 | 554 | 860 | 0.64 | 0.9883 | 3.33 ms |
1019
+ | glove-100 | 50,000 | 6,534 | 3,060 | 2.14 | 0.9982 | 35.79 ms |
1020
+ | sift-128 | 50,000 | 465 | 370 | 1.26 | 0.9953 | 1.88 ms |
1021
+ | dbpedia-openai | 100,000 | 747 | 620 | 1.20 | 0.9907 | 5.02 ms |
1022
+ | glove-100 | 100,000 | 11,439 | 4,620 | 2.48 | 0.9985 | 140.08 ms |
1023
+ | sift-128 | 100,000 | 656 | 450 | 1.46 | 0.9968 | 3.19 ms |
1017
1024
 
1018
- The calibration clears the requirement on all three. **It does not land on it and it is not meant to**, since the measurement is taken on a tenth of the records with queries drawn from the corpus, where your queries will not be.
1025
+ **The margin is not the same on the three, and glove-100 carries most of it.** The rule is one measurement per index scaled by two constants that are the same everywhere, being a safety factor of 1.75 and a bias of 0.15 on the fitted exponent. Taking the fitted exponent alone with neither constant, the extrapolation to 100,000 records asks for 4,626 candidates on glove-100 against a requirement of 4,620, which is 1.001 times what that index needs. On the other two it asks for 0.49 and 0.59 times what they need. The constants exist for those two, and on glove-100 they are margin over a fetch that was already right.
1026
+
1027
+ Neither constant can come down without costing recall elsewhere. The constant that would bring glove-100 to the 1.20 ratio dbpedia-openai has at 100,000 records takes the dbpedia-openai fetch from 747 to 359, and a sweep of that same index reads recall 0.9787 at 360 candidates. dbpedia-openai also sits under 0.99 at 10,000, 25,000 and 50,000 records, at 0.9878, 0.9800 and 0.9883, so it has nothing to give back. What separates the datasets is the ratio between the requirement on a built index and the depth the calibration measures in the code ordering over its training sample, and the training sample cannot measure that ratio because the graph does not exist yet when it runs.
1028
+
1029
+ **If query time on glove-like data matters more to you than the last thousandth of recall, name `rerank` yourself.** On glove-100 at 50,000 records a fetch of 3,060 reads recall 0.9900 at 18.14 ms against 0.9982 at 35.79 ms, so half the query time buys back 0.008 of recall.
1030
+
1031
+ The two tables below carry fetch figures measured before the codebook seeding this release ships, which moved the calibrated fetch upward on glove-100. Read their ratios as the shape rather than as current figures.
1019
1032
 
1020
1033
  **On data with no resolvable structure no fetch works.** Once the group the codes cannot separate is smaller than `top_k`, the true top ten span groups, the distances between groups differ in the fourth decimal, and nothing reaches them. At 5,000 clusters over 25,000 records, being five records to a cluster, recall at 10 reaches 0.917 at a fetch of half the corpus, and uniform points on the sphere reach 0.859. Measure recall on your own data before you rely on quantization.
1021
1034
 
@@ -1133,7 +1146,7 @@ print("saved:", sorted(os.listdir("my_index.zdb")))
1133
1146
 
1134
1147
  *Output, with the progress lines omitted*
1135
1148
  ```
1136
- saved: ['config.json', 'hnsw_index.hnsw.data', 'hnsw_index.hnsw.graph', 'manifest.json', 'mappings.bin', 'metadata.json', 'vectors.bin']
1149
+ saved: ['config.json', 'hnsw_index.zdbgraph', 'manifest.json', 'mappings.bin', 'metadata.json', 'vectors.bin']
1137
1150
  ```
1138
1151
 
1139
1152
  <br />
@@ -1203,7 +1216,7 @@ print("saved:", sorted(os.listdir("quantized_index.zdb")))
1203
1216
  quantization active: True
1204
1217
  quantization active after load: True
1205
1218
  storage mode after load: quantized_active
1206
- saved: ['config.json', 'hnsw_index.hnsw.data', 'hnsw_index.hnsw.graph', 'manifest.json', 'mappings.bin', 'metadata.json', 'pq_centroids.bin', 'pq_codes.bin', 'quantization.json', 'vectors.bin']
1219
+ saved: ['config.json', 'hnsw_index.zdbgraph', 'manifest.json', 'mappings.bin', 'metadata.json', 'pq_centroids.bin', 'pq_codes.bin', 'quantization.json', 'vectors.bin']
1207
1220
  ```
1208
1221
 
1209
1222
  <br/>
@@ -1221,11 +1234,12 @@ my_index.zdb/
1221
1234
  ├── quantization.json # PQ configuration (if enabled)
1222
1235
  ├── pq_centroids.bin # Trained centroids (if PQ trained)
1223
1236
  ├── pq_codes.bin # Quantized codes (if PQ active)
1224
- ├── hnsw_index.hnsw.graph # HNSW graph structure
1225
- └── hnsw_index.hnsw.data # HNSW graph payload
1237
+ └── hnsw_index.zdbgraph # HNSW graph structure and payload
1226
1238
  ```
1227
1239
 
1228
- `manifest.json` lists both graph files under `files_included`. The load path restores the saved graph rather than rebuilding it, so both are required to reopen a directory holding records.
1240
+ `manifest.json` lists the graph file under `files_included`. The load path restores the saved graph rather than rebuilding it, so the file is required to reopen a directory holding records.
1241
+
1242
+ A directory saved by 0.6.0 or earlier holds `hnsw_index.hnsw.graph` and `hnsw_index.hnsw.data` instead. Opening it still works: the graph is rebuilt once from the stored records, and the next `.save()` writes the single file.
1229
1243
 
1230
1244
  <br/>
1231
1245
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "zeusdb-vector-database"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "Blazing-fast vector DB with real-time similarity search and metadata filtering."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -36,15 +36,11 @@ python-source = "src"
36
36
  manifest-path = "vdb-core/Cargo.toml"
37
37
  module-name = "zeusdb_vector_database" # <-- matches Rust crate name
38
38
  bindings = "pyo3" # <-- explicitly state to maturin that it's a PyO3 project
39
- # vendor/** must ship in the sdist, or an sdist build cannot resolve the
40
- # patched hnsw_rs path dependency declared in vdb-core/Cargo.toml.
41
39
  include = [
42
40
  "LICENSE",
43
41
  "NOTICE",
44
42
  "README.md",
45
43
  "src/**",
46
- { path = "vendor/**/*", format = "sdist" },
47
- { path = "LICENSES/*", format = "sdist" },
48
44
  ]
49
45
 
50
46
  [project.optional-dependencies]
@@ -5,7 +5,7 @@
5
5
  """
6
6
  ZeusDB Vector Database Module
7
7
  """
8
- __version__ = "0.6.0"
8
+ __version__ = "0.7.0"
9
9
 
10
10
  # STEP 1: Configure logging FIRST, before importing anything that uses Rust.
11
11
  from .logging_config import _auto_configure_logging
@@ -196,10 +196,11 @@ class VectorDatabase:
196
196
  Under 'quantized_only' the records collected for training are held at
197
197
  full width only until training completes, then released. get_stats()
198
198
  reports the codes, the raw vectors, the codebook, the centroid
199
- distance table and the graph, and sums them under total_memory_mb.
200
- That sum is what the index holds rather than what the process holds,
201
- since the id maps, the metadata map and the allocator's own headers
202
- sit outside it at roughly 1,500 bytes a record. Recall falls sharply,
199
+ distance table, the graph and the hash tables that find a record,
200
+ and sums them under total_memory_mb. That sum is what the index
201
+ asked the allocator for rather than what the process holds, since
202
+ the allocator's own headers and its rounding sit outside it at
203
+ between 1,200 and 2,100 bytes a record. Recall falls sharply,
203
204
  not slightly. Only 'quantized_with_raw' can recover it, by reranking
204
205
  against the raw vectors it keeps. Training triggers automatically on
205
206
  the first .add() call that reaches the training_size threshold, and
@@ -438,7 +439,8 @@ class VectorDatabase:
438
439
  "holds 0.69 times an unquantized index at 10,000 records and 0.59 "
439
440
  "times at 100,000. "
440
441
  "get_stats() reports the memory the codes, the raw vectors, the codebook, "
441
- "the centroid distance table and the graph hold once records are loaded, "
442
+ "the centroid distance table, the graph and the index bookkeeping hold "
443
+ "once records are loaded, "
442
444
  "and sums them under total_memory_mb. This mode is required for rerank "
443
445
  "and for exact vector reconstruction.",
444
446
  UserWarning,