Skip to content
Draft
6 changes: 3 additions & 3 deletions .github/workflows/build-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ jobs:
strategy:
matrix:
build_type: [RelWithDebugInfo]
cxx: [clang++-15]
cxx: [clang++-19]
include:
- cxx: clang++-15
package: llvm@15
- cxx: clang++-19
package: llvm@19
cc_name: clang
cxx_name: clang++
needs_prefix: true
Expand Down
7 changes: 5 additions & 2 deletions bindings/python/include/svs/python/manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ pybind11::tuple py_search(
matrix_view(result_idx), matrix_view(result_dists)
);

svs::index::search_batch_into(self, q_result, query_data.cview());
{
pybind11::gil_scoped_release release;
svs::index::search_batch_into(self, q_result, query_data.cview());
}
return pybind11::make_tuple(result_idx, result_dists);
}

Expand Down Expand Up @@ -86,7 +89,7 @@ void add_threading_interface(pybind11::class_<Manager>& manager) {
"num_threads",
&Manager::get_num_threads,
[](Manager& self, int num_threads) {
self.set_threadpool(svs::threads::DefaultThreadPool(num_threads));
self.set_threadpool(svs::threads::SwitchNativeThreadPool(num_threads));
},
"Read/Write (int): Get and set the number of threads used to process queries."
);
Expand Down
27 changes: 23 additions & 4 deletions bindings/python/src/dynamic_vamana.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,12 @@ void add_points(
"Expected IDs to be the same length as the number of rows in points!"
);
}
index.add_points(data_view(py_data), std::span(ids.data(), ids.size()), reuse_empty);
auto data = data_view(py_data);
auto id_span = std::span(ids.data(), ids.size());
{
py::gil_scoped_release release;
index.add_points(data, id_span, reuse_empty);
}
}

const char* ADD_POINTS_DOCSTRING = R"(
Expand Down Expand Up @@ -381,8 +386,18 @@ void wrap(py::module& m) {

add_dynamic_vamana_properties(vamana);

vamana.def("consolidate", &svs::DynamicVamana::consolidate, CONSOLIDATE_DOCSTRING);
vamana.def("compact", &svs::DynamicVamana::compact, COMPACT_DOCSTRING);
vamana.def(
"consolidate",
&svs::DynamicVamana::consolidate,
py::call_guard<py::gil_scoped_release>(),
CONSOLIDATE_DOCSTRING
);
vamana.def(
"compact",
&svs::DynamicVamana::compact,
py::call_guard<py::gil_scoped_release>(),
COMPACT_DOCSTRING
);

// Reloading
vamana.def(
Expand Down Expand Up @@ -435,7 +450,11 @@ void wrap(py::module& m) {
vamana.def(
"delete",
[](svs::DynamicVamana& index, const py_contiguous_array_t<size_t>& ids) {
index.delete_points(as_span(ids));
auto id_span = as_span(ids);
{
py::gil_scoped_release release;
index.delete_points(id_span);
}
},
py::arg("ids"),
DELETE_DOCSTRING
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/src/python_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class ScopedModuleNameOverride {

} // namespace

PYBIND11_MODULE(_svs, m) {
PYBIND11_MODULE(_svs, m, py::mod_gil_not_used()) {
// Internally, the top level `__init__.py` imports everything from the C++ module named
// `_svs`.
//
Expand Down
22 changes: 16 additions & 6 deletions include/svs/concepts/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@

namespace svs::graphs {

/// Outcome of `MemoryGraph::add_edge(src, dst)`. Distinguishes three cases so callers
/// can route dropped edges (e.g. to a backedge buffer) without a TOCTOU race between a
/// pre-check and the insert.
enum class AddEdgeResult : uint8_t {
Added, // Edge was inserted.
AlreadyExists, // Edge was already present (or self-loop). Not inserted.
Full, // Node's adjacency list is at max_degree. Edge NOT inserted.
};

// clang-format off

///
Expand Down Expand Up @@ -135,12 +144,13 @@ template <ImmutableMemoryGraph G> using index_type_t = typename G::index_type;
/// @code{.cpp}
/// template <typename T>
/// concept MemoryGraph = requires(T& g, const T& const_g) {
/// // Add an edge to the graph.
/// // Must return the out degree of `src` after adding the edge `src -> dst`.
/// // If adding the edge would result in the graph exceeding its maximum degree,
/// // implementations are free to not add this edge.
/// // Add an edge to the graph atomically. Returns an AddEdgeResult indicating:
/// // Added - edge was inserted
/// // AlreadyExists - edge was already present (or self-loop); no insert
/// // Full - node is at max_degree; edge NOT inserted (caller should
/// // route to an overflow buffer if needed)
/// requires requires(index_type_t<T> src, index_type_t<T> dst) {
/// { g.add_edge(src, dst) } -> std::convertible_to<size_t>;
/// { g.add_edge(src, dst) } -> std::convertible_to<AddEdgeResult>;
/// };
///
/// // Completely clear the adjacency list for vertex ``i``.
Expand All @@ -164,7 +174,7 @@ template <typename T>
concept MemoryGraph = requires(T& g, const T& const_g) {
// Adding an edge.
requires requires(index_type_t<T> src, index_type_t<T> dst) {
{ g.add_edge(src, dst) } -> std::convertible_to<size_t>;
{ g.add_edge(src, dst) } -> std::convertible_to<AddEdgeResult>;
};

// Clear adjacency list.
Expand Down
Loading
Loading