Skip to content

Commit a89611c

Browse files
committed
add: Implement Task 3.1 - filter_labels support in ingestion pipeline
Completes ingestion-side implementation for Filtered-Vamana feature by adding filter_labels parameter support throughout the ingestion pipeline. Key changes: - Add filter_labels parameter to ingest() and ingest_vamana() functions - Implement label enumeration: string labels → uint32 enumeration IDs - Convert Python filter_labels (dict[external_id] -> list[str]) to C++ format (vector<unordered_set<uint32_t>> indexed by vector position) - Update PyBind11 bindings to accept filter_labels and label_to_enum - Update C++ vamana_index::train() to accept and store label_to_enum - Update C++ API layer (IndexVamana, index_base, index_impl) to forward filter parameters - Fix bug: filter_labels wasn't being passed from main ingest() to ingest_vamana() With these changes, users can now ingest vectors with filter labels: ```python ingest( index_type="VAMANA", index_uri=uri, input_vectors=vectors, filter_labels={ 0: ["dataset_A"], 1: ["dataset_B"], # ... } ) The label enumeration and start nodes metadata are now properly written to TileDB storage during index creation. Note: Query-side filtered search encounters a segfault that requires further investigation (separate from this ingestion implementation).
1 parent 9562030 commit a89611c

4 files changed

Lines changed: 80 additions & 12 deletions

File tree

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def ingest(
4949
external_ids: Optional[np.array] = None,
5050
external_ids_uri: Optional[str] = "",
5151
external_ids_type: Optional[str] = None,
52+
filter_labels: Optional[Mapping[Any, Sequence[str]]] = None,
5253
updates_uri: Optional[str] = None,
5354
index_timestamp: Optional[int] = None,
5455
config: Optional[Mapping[str, Any]] = None,
@@ -1682,6 +1683,7 @@ def ingest_vamana(
16821683
size: int,
16831684
batch: int,
16841685
partitions: int,
1686+
filter_labels: Optional[Mapping[Any, Sequence[str]]] = None,
16851687
config: Optional[Mapping[str, Any]] = None,
16861688
verbose: bool = False,
16871689
trace_id: Optional[str] = None,
@@ -1813,7 +1815,46 @@ def ingest_vamana(
18131815
to_temporal_policy(index_timestamp),
18141816
)
18151817
index = vspy.IndexVamana(ctx, index_group_uri)
1816-
index.train(data)
1818+
1819+
# Process filter_labels if provided
1820+
if filter_labels is not None:
1821+
# Build label enumeration: string → uint32
1822+
label_to_enum = {}
1823+
next_enum_id = 0
1824+
for labels_list in filter_labels.values():
1825+
for label_str in labels_list:
1826+
if label_str not in label_to_enum:
1827+
label_to_enum[label_str] = next_enum_id
1828+
next_enum_id += 1
1829+
1830+
# Read the external_ids array to map positions to external_ids
1831+
ids_array_read = tiledb.open(ids_array_uri, mode="r", timestamp=index_timestamp)
1832+
external_ids_ordered = ids_array_read[0:end]["values"]
1833+
ids_array_read.close()
1834+
1835+
# Convert filter_labels to enumerated format
1836+
# C++ expects: vector<unordered_set<uint32_t>> indexed by vector position
1837+
# Python provides: dict[external_id] -> list[label_strings]
1838+
enumerated_labels = []
1839+
for vector_idx in range(end):
1840+
external_id = external_ids_ordered[vector_idx]
1841+
labels_set = set()
1842+
if external_id in filter_labels:
1843+
# Convert string labels to enumeration IDs
1844+
for label_str in filter_labels[external_id]:
1845+
labels_set.add(label_to_enum[label_str])
1846+
enumerated_labels.append(labels_set)
1847+
1848+
# 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]}")
1852+
index.train(
1853+
vectors=data, filter_labels=enumerated_labels, label_to_enum=label_to_enum
1854+
)
1855+
else:
1856+
index.train(vectors=data)
1857+
18171858
index.add(data)
18181859
index.write_index(ctx, index_group_uri, to_temporal_policy(index_timestamp))
18191860

@@ -2570,6 +2611,7 @@ def scale_resources(min_resource, max_resource, max_input_size, input_size):
25702611
size=size,
25712612
batch=input_vectors_batch_size,
25722613
partitions=partitions,
2614+
filter_labels=filter_labels,
25732615
config=config,
25742616
verbose=verbose,
25752617
trace_id=trace_id,

apis/python/src/tiledb/vector_search/type_erased_module.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,15 @@ void init_type_erased_module(py::module_& m) {
421421
})
422422
.def(
423423
"train",
424-
[](IndexVamana& index, const FeatureVectorArray& vectors) {
425-
index.train(vectors);
424+
[](IndexVamana& index,
425+
const FeatureVectorArray& vectors,
426+
const std::vector<std::unordered_set<uint32_t>>& filter_labels,
427+
const std::unordered_map<std::string, uint32_t>& label_to_enum) {
428+
index.train(vectors, filter_labels, label_to_enum);
426429
},
427-
py::arg("vectors"))
430+
py::arg("vectors"),
431+
py::arg("filter_labels") = std::vector<std::unordered_set<uint32_t>>{},
432+
py::arg("label_to_enum") = std::unordered_map<std::string, uint32_t>{})
428433
.def(
429434
"add",
430435
[](IndexVamana& index, const FeatureVectorArray& vectors) {

src/include/api/vamana_index.h

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,14 @@ class IndexVamana {
161161
/**
162162
* @brief Train the index based on the given training set.
163163
* @param training_set
164-
* @param init
164+
* @param filter_labels Optional filter labels for filtered Vamana
165+
* @param label_to_enum Optional label enumeration mapping
165166
*/
166167
// @todo -- infer feature type from input
167-
void train(const FeatureVectorArray& training_set) {
168+
void train(
169+
const FeatureVectorArray& training_set,
170+
const std::vector<std::unordered_set<uint32_t>>& filter_labels = {},
171+
const std::unordered_map<std::string, uint32_t>& label_to_enum = {}) {
168172
if (feature_datatype_ == TILEDB_ANY) {
169173
feature_datatype_ = training_set.feature_type();
170174
} else if (feature_datatype_ != training_set.feature_type()) {
@@ -194,7 +198,7 @@ class IndexVamana {
194198
index_ ? std::make_optional<TemporalPolicy>(index_->temporal_policy()) :
195199
std::nullopt,
196200
distance_metric_);
197-
index_->train(training_set);
201+
index_->train(training_set, filter_labels, label_to_enum);
198202

199203
if (dimensions_ != 0 && dimensions_ != index_->dimensions()) {
200204
throw std::runtime_error(
@@ -341,7 +345,10 @@ class IndexVamana {
341345
struct index_base {
342346
virtual ~index_base() = default;
343347

344-
virtual void train(const FeatureVectorArray& training_set) = 0;
348+
virtual void train(
349+
const FeatureVectorArray& training_set,
350+
const std::vector<std::unordered_set<uint32_t>>& filter_labels = {},
351+
const std::unordered_map<std::string, uint32_t>& label_to_enum = {}) = 0;
345352

346353
virtual void add(const FeatureVectorArray& data_set) = 0;
347354

@@ -396,7 +403,11 @@ class IndexVamana {
396403
: impl_index_(ctx, index_uri, temporal_policy) {
397404
}
398405

399-
void train(const FeatureVectorArray& training_set) override {
406+
void train(
407+
const FeatureVectorArray& training_set,
408+
const std::vector<std::unordered_set<uint32_t>>& filter_labels = {},
409+
const std::unordered_map<std::string, uint32_t>& label_to_enum = {})
410+
override {
400411
using feature_type = typename T::feature_type;
401412
auto fspan = MatrixView<feature_type, stdx::layout_left>{
402413
(feature_type*)training_set.data(),
@@ -408,11 +419,11 @@ class IndexVamana {
408419
if (num_ids(training_set) > 0) {
409420
auto ids = std::span<id_type>(
410421
(id_type*)training_set.ids(), training_set.num_vectors());
411-
impl_index_.train(fspan, ids);
422+
impl_index_.train(fspan, ids, filter_labels, label_to_enum);
412423
} else {
413424
auto ids = std::vector<id_type>(::num_vectors(training_set));
414425
std::iota(ids.begin(), ids.end(), 0);
415-
impl_index_.train(fspan, ids);
426+
impl_index_.train(fspan, ids, filter_labels, label_to_enum);
416427
}
417428
}
418429

src/include/index/vamana_index.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,8 @@ class vamana_index {
486486
void train(
487487
const Array& training_set,
488488
const Vector& training_set_ids,
489-
const std::vector<std::unordered_set<uint32_t>>& filter_labels = {}) {
489+
const std::vector<std::unordered_set<uint32_t>>& filter_labels = {},
490+
const std::unordered_map<std::string, uint32_t>& label_to_enum = {}) {
490491
scoped_timer _{"vamana_index@train"};
491492

492493
// Validate training data
@@ -527,6 +528,15 @@ class vamana_index {
527528
// Store filter labels
528529
filter_labels_ = filter_labels;
529530

531+
// Store label enumeration mapping
532+
label_to_enum_ = label_to_enum;
533+
534+
// Build reverse mapping
535+
enum_to_label_.clear();
536+
for (const auto& [str, id] : label_to_enum_) {
537+
enum_to_label_[id] = str;
538+
}
539+
530540
// Find start nodes (load-balanced) using find_medoid
531541
// find_medoid returns std::unordered_map<uint32_t, size_t>, so convert to
532542
// id_type

0 commit comments

Comments
 (0)