Skip to content

Implement range search functionality in DynamicVamana and Vamana orchestrators - #473

Draft
rfsaliev wants to merge 1 commit into
rfsaliev/range-searchfrom
rfsaliev/range-search-orchestrator
Draft

rfsaliev wants to merge 1 commit into
rfsaliev/range-searchfrom
rfsaliev/range-search-orchestrator

Conversation

@rfsaliev

Copy link
Copy Markdown
Member

Range search in the Vamana orchestrators

Summary

Exposes range_search, added to the Vamana indexes in the PR #472, through orchestrators svs::Vamana, svs::DynamicVamana and svs::ConcurrentDynamicVamana.

Stacked on the #472.

API

template <typename QueryType>
std::vector<std::vector<Neighbor<size_t>>> range_search(
    data::ConstSimpleDataView<QueryType> queries,
    float radius,
    const VamanaSearchParameters& search_parameters,
    const lib::DefaultPredicate& cancel = lib::Returns(lib::Const<false>())
);

The semantics are those of the index-level range_search:

  • It returns the in-radius neighbors of each query with external IDs, sorted best to worst.
  • A non-zero total buffer capacity in search_parameters caps the number of results per query; zero (or MutableBuffer::unbounded_capacity) means unbounded.
  • The search visits at least the search window, then continues while the best unvisited candidate is within the radius.
  • On cancel, queries in flight keep their partial in-radius results, and queries not yet started return no results.

Changes

  • VamanaInterface (include/svs/orchestrators/vamana.h) gets a new pure virtual, range_search(AnonymousArray<2> queries, float radius, const VamanaSearchParameters&, const lib::DefaultPredicate& cancel). It is appended at the end of the interface, following the existing guidance in that header.
  • VamanaImpl implements it once for all three orchestrators:
    • It dispatches on the query element type over QueryTypes, like search(). Unsupported types throw ANNException.
    • It forwards to impl().range_search(...). A new HasRangeSearch<Impl, Queries> concept guards the call, so an implementation without range_search throws "not supported" at run time instead of failing to compile. All current implementation types (VamanaIndex, MutableVamanaIndex, concurrent::MutableVamanaIndex) provide it.
  • Wrappers:
    • Vamana::range_search and DynamicVamana::range_search are thin templated wrappers that build the AnonymousArray<2>.
    • ConcurrentDynamicVamana inherits the method from DynamicVamana.

Compatibility

  • Additive at the source level. No other class implements VamanaInterface, so the new pure virtual requires no changes elsewhere.
  • The vtable changes. DynamicVamanaInterface derives from VamanaInterface, so its virtual slots shift. Binaries that use orchestrators across a library boundary must be rebuilt against matching headers. ABI stability of the orchestrator interfaces is not a goal, so this is accepted.

Testing

A new header-only helper, tests/utils/range_search.h (svs_test::check_orchestrator_range_search), is used by one new test case per orchestrator:

  • tests/svs/orchestrators/vamana.cpp: "Vamana Range Search"
  • tests/svs/orchestrators/dynamic_vamana.cpp: "DynamicVamana Range Search"
  • tests/svs/orchestrators/concurrent_dynamic_vamana.cpp: "ConcurrentDynamicVamana Range Search"

Each one builds the orchestrator over the standard test dataset with L2 and checks:

  • results are within the radius, sorted, unique, and carry valid IDs;
  • recall against brute force is at least 0.85 with search window 20;
  • the bounded mode never exceeds the buffer capacity and reaches it for some queries;
  • cancelling before the start returns empty results for every query;
  • an unsupported query element type (uint8_t for a float index) throws ANNException.

The recall threshold is lower than in the index-level tests on purpose. On this dataset, recall for about 30 neighbors only rises from 0.88 at window 20 to 0.91 at window 100. These tests check the orchestrator plumbing; the base PR checks recall on the indexes directly.

All [managers] tests pass.

Notes for reviewers

  • The HasRangeSearch concept and the explicit this->impl() are deliberate. Inside the generic dispatch lambda, GCC did not resolve impl(), which VamanaImpl inherits from its dependent base class through a using declaration. An inline requires { impl().range_search(...) } therefore evaluated to false, and the "not supported" branch was taken silently. The new tests fail if that regresses.
  • Follow-ups, not in this PR:
    • Expose range_search in the Python bindings.
    • Expose it in the C and runtime bindings, and switch the runtime range_search from the batch iterator to this API. Bindings that call the method need a prebuilt library that contains it.
    • Expose predicated_search through the orchestrators once filtered search defines the stop predicates it needs. Template stop predicates can't cross the virtual boundary as they are.

@rfsaliev
rfsaliev added this pull request to stack #474 November 13, 2026 14:51
@rfsaliev
rfsaliev force-pushed the rfsaliev/range-search-orchestrator branch from a756143 to 9f75134 Compare November 13, 2026 15:06
@rfsaliev
rfsaliev removed this pull request from stack #474 November 13, 2026 15:07
@rfsaliev
rfsaliev added this pull request to stack #475 November 13, 2026 15:08
@rfsaliev
rfsaliev removed this pull request from stack #475 November 13, 2026 15:10
@rfsaliev
rfsaliev added this pull request to stack #476 November 13, 2026 15:10

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant