Skip to content

Commit 03b845e

Browse files
authored
enhance: skip fully filtered windows in sindi sparse search (#1782)
* enhance: skip fully filtered windows in sindi sparse search Skip the window scoring when the bitset filters out every document in the window. Keep posting cursors in sync when a window is skipped so cumulative posting offsets stay correct for subsequent windows. Signed-off-by: chasingegg <gaoc96@qq.com> * test: extend sindi window filter skip test to BM25 Signed-off-by: chasingegg <gaoc96@qq.com> * chore: trigger ci Signed-off-by: chasingegg <gaoc96@qq.com> --------- Signed-off-by: chasingegg <gaoc96@qq.com>
1 parent d85f708 commit 03b845e

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

src/index/sparse/sindi_inverted_index.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,18 @@ class SindiInvertedIndex : public DimMapInvertedIndex<DataType, AllowIncremental
932932
const uint32_t wnnz_bits = 32 - __builtin_clz(window_size_);
933933
const uint32_t wnnz_mask = (1u << wnnz_bits) - 1;
934934

935+
// Skip a window when every document in it is filtered out by the bitset.
936+
const auto window_all_filtered = [&bitset](size_t docid_start, size_t window_doc_count) {
937+
if (bitset.empty()) {
938+
return false;
939+
}
940+
const size_t end = docid_start + window_doc_count;
941+
if (end > bitset.size()) {
942+
return false;
943+
}
944+
return bitset.range_all_filtered(docid_start, end);
945+
};
946+
935947
// Initialize posting list cursors for each query term
936948
// Cursors track position in posting lists and handle window-by-window iteration
937949
std::vector<PostingCursor> cursors;
@@ -971,6 +983,17 @@ class SindiInvertedIndex : public DimMapInvertedIndex<DataType, AllowIncremental
971983
const size_t docid_start = window_size_ * widx;
972984
const uint32_t curr_window_size =
973985
std::min(window_size_, static_cast<uint32_t>(this->nr_rows_ - docid_start));
986+
if (window_all_filtered(docid_start, curr_window_size)) {
987+
// Keep posting cursors in sync even when the window is skipped, so the
988+
// cumulative posting offsets stay correct for subsequent windows.
989+
for (auto& cur : cursors) {
990+
if (cur.wnnz_buf == nullptr || cur.wnnz_buf_sz == 0) {
991+
continue;
992+
}
993+
cur.advance_window(widx);
994+
}
995+
continue;
996+
}
974997
std::fill_n(wscores_final, curr_window_size, 0);
975998

976999
float curr_max_score = 0.0f;
@@ -1019,6 +1042,17 @@ class SindiInvertedIndex : public DimMapInvertedIndex<DataType, AllowIncremental
10191042
const size_t docid_start = window_size_ * widx;
10201043
const uint32_t curr_window_size =
10211044
std::min(window_size_, static_cast<uint32_t>(this->nr_rows_ - docid_start));
1045+
if (window_all_filtered(docid_start, curr_window_size)) {
1046+
// Keep posting cursors in sync even when the window is skipped, so the
1047+
// cumulative posting offsets stay correct for subsequent windows.
1048+
for (auto& cur : cursors) {
1049+
if (cur.wnnz_buf == nullptr || cur.wnnz_buf_sz == 0) {
1050+
continue;
1051+
}
1052+
cur.advance_window(widx);
1053+
}
1054+
continue;
1055+
}
10221056
std::fill_n(wscores_final, curr_window_size, 0);
10231057

10241058
float curr_max_score = 0.0f;

tests/ut/test_sparse.cc

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,74 @@ TEST_CASE("Test SINDI Index Window Size", "[sparse][sindi]") {
17111711
REQUIRE(recall >= 0.85);
17121712
}
17131713

1714+
TEST_CASE("Test SINDI Index Search with Window Filter Skip", "[sparse][sindi]") {
1715+
auto nb = 3000;
1716+
auto dim = 500;
1717+
auto topk = 10;
1718+
int64_t nq = 5;
1719+
auto doc_sparsity = 0.97f;
1720+
auto query_sparsity = 0.99f;
1721+
constexpr int32_t window_size = 1024;
1722+
1723+
auto metric = GENERATE(knowhere::metric::IP, knowhere::metric::BM25);
1724+
1725+
auto version = knowhere::Version::GetMaximumVersion().VersionNumber();
1726+
auto sparse_dataset_gen = [&](int nr, float sparsity) -> knowhere::DataSetPtr {
1727+
if (metric == knowhere::metric::BM25) {
1728+
return GenSparseDataSetWithMaxVal(nr, dim, sparsity, 256, true);
1729+
}
1730+
return GenSparseDataSet(nr, dim, sparsity);
1731+
};
1732+
auto train_ds = sparse_dataset_gen(nb, doc_sparsity);
1733+
auto query_ds = sparse_dataset_gen(nq, query_sparsity);
1734+
1735+
// Filter out the leading `filtered_docs` documents so that one or more whole windows
1736+
// are skipped. Skipping leading windows exercises the posting-cursor sync path.
1737+
auto filtered_docs = GENERATE(1024, 2048);
1738+
CAPTURE(filtered_docs);
1739+
auto bitset_data = GenerateBitsetWithFirstTbitsSet(nb, filtered_docs);
1740+
knowhere::BitsetView bitset(bitset_data.data(), nb);
1741+
1742+
knowhere::Json build_json;
1743+
build_json[knowhere::meta::DIM] = dim;
1744+
build_json[knowhere::meta::METRIC_TYPE] = metric;
1745+
build_json[knowhere::indexparam::INVERTED_INDEX_ALGO] = "SINDI";
1746+
build_json["sindi_window_size"] = window_size;
1747+
build_json[knowhere::meta::BM25_K1] = 1.2;
1748+
build_json[knowhere::meta::BM25_B] = 0.75;
1749+
build_json[knowhere::meta::BM25_AVGDL] = 100;
1750+
1751+
knowhere::Json search_json;
1752+
search_json[knowhere::meta::TOPK] = topk;
1753+
search_json[knowhere::meta::METRIC_TYPE] = metric;
1754+
search_json[knowhere::meta::BM25_K1] = 1.2;
1755+
search_json[knowhere::meta::BM25_B] = 0.75;
1756+
search_json[knowhere::meta::BM25_AVGDL] = 100;
1757+
1758+
auto expected = knowhere::BruteForce::SearchSparse(train_ds, query_ds, search_json, bitset);
1759+
REQUIRE(expected.has_value());
1760+
1761+
auto idx = knowhere::IndexFactory::Instance()
1762+
.Create<knowhere::sparse_u32_f32>(knowhere::IndexEnum::INDEX_SPARSE_INVERTED_INDEX, version)
1763+
.value();
1764+
REQUIRE(idx.Build(train_ds, build_json) == knowhere::Status::success);
1765+
1766+
auto results = idx.Search(query_ds, search_json, bitset);
1767+
REQUIRE(results.has_value());
1768+
REQUIRE(GetKNNRecall(*expected.value(), *results.value()) >= 0.99f);
1769+
1770+
auto* ids = results.value()->GetIds();
1771+
auto k = results.value()->GetDim();
1772+
for (int64_t i = 0; i < nq; ++i) {
1773+
for (int64_t j = 0; j < k; ++j) {
1774+
if (ids[i * k + j] == -1) {
1775+
break;
1776+
}
1777+
REQUIRE(!bitset.test(ids[i * k + j]));
1778+
}
1779+
}
1780+
}
1781+
17141782
TEST_CASE("Test SINDI Index Search Algo Mismatch", "[sparse][sindi]") {
17151783
auto nb = 500;
17161784
auto dim = 300;

0 commit comments

Comments
 (0)