Skip to content

Commit b59cb6c

Browse files
committed
fix: Resolve segfault in filtered Vamana query execution by persisting filter_labels
The filtered Vamana query functionality was experiencing segmentation faults when querying an index loaded from storage. The root cause was that the filter_labels_ data structure (which maps each vector to its label set) was not being persisted to or loaded from TileDB storage. During query execution, filtered_greedy_search_multi_start() accesses filter_labels_[node_id] to check if visited nodes match the query filter. When the index was loaded from storage, filter_labels_ remained empty, causing out-of-bounds access and segfaults. Changes: - Add filter_labels storage to vamana_group.h using CSR-like format: - filter_labels_offsets: offset array (num_vectors + 1 elements) - filter_labels_data: flat array of all label IDs - Implement write logic in vamana_index::write_index() to flatten and persist filter_labels_ to the two arrays - Implement load logic in vamana_index constructor to reconstruct filter_labels_ from the CSR format when opening from storage - Update clear_history_impl() to handle filter label arrays Testing: - C++ unit tests (unit_filtered_vamana) pass - Python test test_filtered_query_equality now passes (previously segfaulted) - Filtered queries work correctly end-to-end This completes the filtered Vamana storage persistence implementation.
1 parent a89611c commit b59cb6c

3 files changed

Lines changed: 114 additions & 3 deletions

File tree

apis/python/src/tiledb/vector_search/ingestion.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,9 +1846,6 @@ def ingest_vamana(
18461846
enumerated_labels.append(labels_set)
18471847

18481848
# Pass enumerated_labels and label_to_enum to train
1849-
print(f"DEBUG: filter_labels has {len(enumerated_labels)} vectors")
1850-
print(f"DEBUG: label_to_enum = {label_to_enum}")
1851-
print(f"DEBUG: First few enumerated_labels: {enumerated_labels[:3]}")
18521849
index.train(
18531850
vectors=data, filter_labels=enumerated_labels, label_to_enum=label_to_enum
18541851
)

src/include/index/vamana_group.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
{"adjacency_scores_array_name", "adjacency_scores"},
6666
{"adjacency_ids_array_name", "adjacency_ids"},
6767
{"adjacency_row_index_array_name", "adjacency_row_index"},
68+
{"filter_labels_offsets_array_name", "filter_labels_offsets"},
69+
{"filter_labels_data_array_name", "filter_labels_data"},
6870

6971
// @todo for ivf_vamana we would also want medoids
7072
// {"medoids_array_name", "medoids"},
@@ -119,6 +121,12 @@ class vamana_index_group : public base_index_group<index_type> {
119121
cached_ctx_, adjacency_ids_uri(), 0, timestamp);
120122
tiledb::Array::delete_fragments(
121123
cached_ctx_, adjacency_row_index_uri(), 0, timestamp);
124+
if (has_filter_metadata()) {
125+
tiledb::Array::delete_fragments(
126+
cached_ctx_, filter_labels_offsets_uri(), 0, timestamp);
127+
tiledb::Array::delete_fragments(
128+
cached_ctx_, filter_labels_data_uri(), 0, timestamp);
129+
}
122130
}
123131

124132
/*
@@ -243,6 +251,18 @@ class vamana_index_group : public base_index_group<index_type> {
243251
[[nodiscard]] auto adjacency_row_index_array_name() const {
244252
return this->array_key_to_array_name("adjacency_row_index_array_name");
245253
}
254+
[[nodiscard]] auto filter_labels_offsets_uri() const {
255+
return this->array_key_to_uri("filter_labels_offsets_array_name");
256+
}
257+
[[nodiscard]] auto filter_labels_offsets_array_name() const {
258+
return this->array_key_to_array_name("filter_labels_offsets_array_name");
259+
}
260+
[[nodiscard]] auto filter_labels_data_uri() const {
261+
return this->array_key_to_uri("filter_labels_data_array_name");
262+
}
263+
[[nodiscard]] auto filter_labels_data_array_name() const {
264+
return this->array_key_to_array_name("filter_labels_data_array_name");
265+
}
246266

247267
void create_default_impl() {
248268
this->init_valid_array_names();
@@ -353,6 +373,31 @@ class vamana_index_group : public base_index_group<index_type> {
353373
adjacency_row_index_uri(),
354374
adjacency_row_index_array_name());
355375

376+
// Create filter_labels arrays (CSR-like format)
377+
// filter_labels_offsets: offset array (num_vectors + 1 elements)
378+
// filter_labels_data: flat array of all label IDs
379+
create_empty_for_vector<uint64_t>(
380+
cached_ctx_,
381+
filter_labels_offsets_uri(),
382+
default_domain,
383+
tile_size,
384+
default_compression);
385+
tiledb_helpers::add_to_group(
386+
write_group,
387+
filter_labels_offsets_uri(),
388+
filter_labels_offsets_array_name());
389+
390+
create_empty_for_vector<uint32_t>(
391+
cached_ctx_,
392+
filter_labels_data_uri(),
393+
default_domain,
394+
tile_size,
395+
default_compression);
396+
tiledb_helpers::add_to_group(
397+
write_group,
398+
filter_labels_data_uri(),
399+
filter_labels_data_array_name());
400+
356401
// Store the metadata if all of the arrays were created successfully
357402
metadata_.store_metadata(write_group);
358403
}

src/include/index/vamana_index.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,37 @@ class vamana_index {
452452
graph_.add_edge(i, adj_ids[j], adj_scores[j]);
453453
}
454454
}
455+
456+
// NEW: Load filter_labels from storage if filtering is enabled
457+
if (filter_enabled_ && num_vectors_ > 0) {
458+
// Read offsets and data arrays
459+
auto filter_labels_offsets = read_vector<uint64_t>(
460+
group_->cached_ctx(),
461+
group_->filter_labels_offsets_uri(),
462+
0,
463+
num_vectors_ + 1,
464+
temporal_policy_);
465+
466+
// Calculate total number of labels from last offset
467+
size_t total_labels = filter_labels_offsets.back();
468+
469+
auto filter_labels_data = read_vector<uint32_t>(
470+
group_->cached_ctx(),
471+
group_->filter_labels_data_uri(),
472+
0,
473+
total_labels,
474+
temporal_policy_);
475+
476+
// Reconstruct filter_labels_ from CSR format
477+
filter_labels_.resize(num_vectors_);
478+
for (size_t i = 0; i < num_vectors_; ++i) {
479+
auto start_offset = filter_labels_offsets[i];
480+
auto end_offset = filter_labels_offsets[i + 1];
481+
for (size_t j = start_offset; j < end_offset; ++j) {
482+
filter_labels_[i].insert(filter_labels_data[j]);
483+
}
484+
}
485+
}
455486
}
456487

457488
explicit vamana_index(const std::string& diskann_index) {
@@ -1161,6 +1192,44 @@ class vamana_index {
11611192
false,
11621193
temporal_policy_);
11631194

1195+
// NEW: Write filter_labels arrays if filtering is enabled
1196+
if (filter_enabled_) {
1197+
// Flatten filter_labels_ into CSR-like format
1198+
// Count total number of labels
1199+
size_t total_labels = 0;
1200+
for (const auto& label_set : filter_labels_) {
1201+
total_labels += label_set.size();
1202+
}
1203+
1204+
auto filter_labels_offsets = Vector<uint64_t>(num_vectors_ + 1);
1205+
auto filter_labels_data = Vector<uint32_t>(total_labels);
1206+
1207+
size_t label_offset = 0;
1208+
for (size_t i = 0; i < num_vectors_; ++i) {
1209+
filter_labels_offsets[i] = label_offset;
1210+
for (uint32_t label : filter_labels_[i]) {
1211+
filter_labels_data[label_offset] = label;
1212+
++label_offset;
1213+
}
1214+
}
1215+
filter_labels_offsets.back() = label_offset;
1216+
1217+
write_vector(
1218+
ctx,
1219+
filter_labels_offsets,
1220+
write_group.filter_labels_offsets_uri(),
1221+
0,
1222+
false,
1223+
temporal_policy_);
1224+
write_vector(
1225+
ctx,
1226+
filter_labels_data,
1227+
write_group.filter_labels_data_uri(),
1228+
0,
1229+
false,
1230+
temporal_policy_);
1231+
}
1232+
11641233
return true;
11651234
}
11661235

0 commit comments

Comments
 (0)