-
Notifications
You must be signed in to change notification settings - Fork 638
feat: implement vector index details #6099
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
af877d5
080ac46
eb9afee
36fd159
822b6aa
6269205
d507027
ca6251f
0a8e394
dd70fee
45b9995
aa10e5e
e88ed2f
7c986a9
5e2deaf
2cdfbd2
d361895
eccdfa8
30f814a
4104b25
b8a99fb
1fa9c17
95c616a
e9bb5a5
11bc126
da888fb
5702f43
c3860e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,6 +184,67 @@ message VectorIndex { | |
| VectorMetricType metric_type = 4; | ||
| } | ||
|
|
||
| // Details for vector indexes, stored in the manifest's index_details field. | ||
| message VectorIndexDetails { | ||
| VectorMetricType metric_type = 1; | ||
|
|
||
| // The target number of vectors per partition. | ||
| // 0 means unset. | ||
| uint64 target_partition_size = 2; | ||
|
|
||
| // Optional HNSW index configuration. If set, the index has an HNSW layer. | ||
| optional HnswParameters hnsw_index_config = 3; | ||
|
|
||
| message ProductQuantization { | ||
| uint32 num_bits = 1; | ||
| uint32 num_sub_vectors = 2; | ||
| } | ||
| message ScalarQuantization { | ||
| uint32 num_bits = 1; | ||
| } | ||
| message RabitQuantization { | ||
| enum RotationType { | ||
| FAST = 0; | ||
| MATRIX = 1; | ||
| } | ||
| uint32 num_bits = 1; | ||
| RotationType rotation_type = 2; | ||
| } | ||
|
|
||
| // No quantization; vectors are stored as-is. | ||
| message FlatCompression {} | ||
|
|
||
| oneof compression { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe worth adding The other scalar index has index_version already today. |
||
| ProductQuantization pq = 4; | ||
|
wjones127 marked this conversation as resolved.
|
||
| ScalarQuantization sq = 5; | ||
| RabitQuantization rq = 6; | ||
| FlatCompression flat = 8; | ||
| } | ||
|
|
||
| // The version of the index file format. Useful for maintaining backwards | ||
| // compatibility when introducing breaking changes to the index format. | ||
| // 0 means unset (legacy index). | ||
| uint32 index_version = 7; | ||
|
|
||
| // Runtime hints: optional build preferences that don't affect index structure. | ||
| // Keys use reverse-DNS namespacing (e.g., "lance.ivf.max_iters", "lancedb.accelerator"). | ||
| // Unrecognized keys must be silently ignored by all runtimes. | ||
| map<string, string> runtime_hints = 9; | ||
| } | ||
|
|
||
| // Hierarchical Navigable Small World (HNSW) parameters, used as an optional configuration for IVF indexes. | ||
| message HnswParameters { | ||
| // The maximum number of outgoing edges per node in the HNSW graph. Higher values | ||
| // means more connections, better recall, but more memory and slower builds. | ||
| // Referred to as "M" in the HNSW literature. | ||
| uint32 max_connections = 1; | ||
| // "construction exploration factor": The size of the dynamic list used during | ||
| // index construction. | ||
| uint32 construction_ef = 2; | ||
|
wjones127 marked this conversation as resolved.
|
||
| // The maximum number of levels in the HNSW graph. | ||
| uint32 max_level = 3; | ||
| } | ||
|
|
||
| message JsonIndexDetails { | ||
| string path = 1; | ||
| google.protobuf.Any target_details = 2; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1677,7 +1677,7 @@ def test_describe_vector_index(indexed_dataset: LanceDataset): | |
| info = indexed_dataset.describe_indices()[0] | ||
|
|
||
| assert info.name == "vector_idx" | ||
| assert info.type_url == "/lance.table.VectorIndexDetails" | ||
| assert info.type_url == "/lance.index.pb.VectorIndexDetails" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regrettably, I do not think we can change the type URL for backwards compatibility reasons.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can move that back if it's necessary. |
||
| assert info.index_type == "IVF_PQ" | ||
| assert info.num_rows_indexed == 1000 | ||
| assert info.fields == [0] | ||
|
|
@@ -1688,6 +1688,44 @@ def test_describe_vector_index(indexed_dataset: LanceDataset): | |
| assert info.segments[0].index_version == 1 | ||
| assert info.segments[0].created_at is not None | ||
|
|
||
| details = info.details | ||
| assert details["metric_type"] == "L2" | ||
| assert details["compression"]["type"] == "pq" | ||
| assert details["compression"]["num_bits"] == 8 | ||
| assert details["compression"]["num_sub_vectors"] == 16 | ||
|
|
||
|
|
||
| def test_describe_index_runtime_hints_stored(tmp_path): | ||
| tbl = create_table(nvec=300, ndim=16) | ||
| dataset = lance.write_dataset(tbl, tmp_path) | ||
| dataset = dataset.create_index( | ||
| "vector", | ||
| index_type="IVF_PQ", | ||
| num_partitions=4, | ||
| num_sub_vectors=4, | ||
| max_iters=100, | ||
| sample_rate=512, | ||
| ) | ||
| details = dataset.describe_indices()[0].details | ||
| hints = details.get("runtime_hints", {}) | ||
| assert hints.get("lance.ivf.max_iters") == "100" | ||
| assert hints.get("lance.ivf.sample_rate") == "512" | ||
| assert hints.get("lance.pq.max_iters") == "100" | ||
| assert hints.get("lance.pq.sample_rate") == "512" | ||
|
|
||
|
|
||
| def test_describe_index_runtime_hints_defaults_omitted(tmp_path): | ||
| tbl = create_table(nvec=300, ndim=16) | ||
| dataset = lance.write_dataset(tbl, tmp_path) | ||
| dataset = dataset.create_index( | ||
| "vector", | ||
| index_type="IVF_PQ", | ||
| num_partitions=4, | ||
| num_sub_vectors=4, | ||
| ) | ||
| details = dataset.describe_indices()[0].details | ||
| assert "runtime_hints" not in details | ||
|
|
||
|
|
||
| def test_optimize_indices(indexed_dataset): | ||
| data = create_table() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are the IVF details? was it hierarchical? How many partitions in each stage?.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using
VectorIndexDetailshere for the IVF stage. I suppose I could make it a substruct to make it clearer. The only parameter right now istarget_partition_size.What do you mean by this?