Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bindings/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
else()
# Links to LTO-enabled static library, requires GCC/G++ 11.2
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3")
set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-10-08-1629.tar.gz"
set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-10-15-1661.tar.gz"
CACHE STRING "URL to download SVS shared library")
else()
# The fallback is correct but slower, so nothing downstream fails and CI
Expand Down
9 changes: 7 additions & 2 deletions bindings/c/src/dispatcher_dynamic_vamana.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,13 @@ svs::DynamicVamana load_stream_dynamic_vamana_index(

// svs_c.h lets the caller drop the stream once loading returns. That holds only while
// assemble copies data out; a view allocator here would leave the index dangling.
return svs::DynamicVamana::assemble<float, data_type>(
*stream, distance, std::move(pool), allocator, graph_allocator
return svs::DynamicVamana::assemble<float>(
*stream,
svs::make_stream_loader<
svs::graphs::SimpleGraph<uint32_t, decltype(graph_allocator)>>(graph_allocator),
svs::make_stream_loader<data_type>(allocator),
distance,
std::move(pool)
);
}

Expand Down
11 changes: 7 additions & 4 deletions bindings/c/src/dispatcher_vamana.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,17 @@ svs::Vamana load_stream_vamana_index(
) {
using value_type = typename DataLoader::allocator_type::value_type;
using data_type = typename DataLoader::data_type;
auto graph_allocator = allocator_builder.build_for_graph<uint32_t>();
auto data_allocator = allocator_builder.build<value_type>();
// svs_c.h lets the caller drop the stream once loading returns. That holds only while
// assemble copies data out; a view allocator here would leave the index dangling.
return svs::Vamana::assemble<float, data_type>(
return svs::Vamana::assemble<float>(
*stream,
svs::make_stream_loader<
svs::graphs::SimpleGraph<uint32_t, decltype(graph_allocator)>>(graph_allocator),
svs::make_stream_loader<data_type>(data_allocator),
distance,
std::move(pool),
allocator_builder.build<value_type>(),
allocator_builder.build_for_graph<uint32_t>()
std::move(pool)
);
}

Expand Down
2 changes: 1 addition & 1 deletion bindings/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
else()
# Links to LTO-enabled static library, requires GCC/G++ 11.2
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11.2" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "11.3")
set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-10-01-1583.tar.gz"
set(SVS_URL "https://github.com/intel/ScalableVectorSearch/releases/download/nightly/svs-shared-library-lto-nightly-2026-10-15-1661.tar.gz"
CACHE STRING "URL to download SVS shared library")
else()
if(SVS_REQUIRE_LTO_ARCHIVE)
Expand Down
16 changes: 9 additions & 7 deletions bindings/cpp/src/dynamic_vamana_index_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -545,18 +545,20 @@ class DynamicVamanaIndexImpl {
);
} else {
using storage_type = storage::StorageType_t<Kind, Alloc>;
using graph_type =
svs::graphs::SimpleGraph<uint32_t, svs::HugepageAllocator<uint32_t>>;
auto threadpool = default_threadpool();

svs::DistanceDispatcher distance_dispatcher(to_svs_distance(metric));

return distance_dispatcher([&](auto&& distance) {
return new svs::DynamicVamana(
svs::DynamicVamana::assemble<float, storage_type>(
stream,
std::forward<decltype(distance)>(distance),
std::move(threadpool)
)
);
return new svs::DynamicVamana(svs::DynamicVamana::assemble<float>(
stream,
svs::StreamLoader<graph_type>{},
svs::StreamLoader<storage_type>{},
std::forward<decltype(distance)>(distance),
std::move(threadpool)
));
});
}
}
Expand Down
7 changes: 4 additions & 3 deletions bindings/cpp/src/vamana_index_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,11 +502,12 @@ class VamanaIndexImpl {
using storage_type = storage::StorageType_t<Kind, Alloc>;
auto threadpool = default_threadpool();

return new svs::Vamana(svs::Vamana::assemble<float, storage_type>(
return new svs::Vamana(svs::Vamana::assemble<float>(
stream,
svs::StreamLoader<svs::Vamana::default_graph_type<storage_type>>{},
svs::make_stream_loader<storage_type>(std::forward<Args>(args)...),
to_svs_distance(metric),
std::move(threadpool),
std::forward<Args>(args)...
std::move(threadpool)
));
}
}
Expand Down
61 changes: 61 additions & 0 deletions include/svs/core/loading.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,69 @@
#pragma once

#include "svs/lib/misc.h"
#include "svs/lib/saveload/load.h"
#include "svs/lib/threads.h"

#include <filesystem>
#include <istream>
#include <tuple>
#include <type_traits>
#include <utility>

namespace svs {

///
/// @brief Selects the element type and loader arguments for one component of an index.
///
/// The orchestrator supplies the stream for native streams, or the unpacked component
/// directory for directory archives.
///
/// @tparam T The type loaded from the component.
/// @tparam Args The types of the arguments forwarded to the load function (e.g.
/// allocators).
///
template <typename T, typename... Args> struct StreamLoader {
Comment thread
ahuber21 marked this conversation as resolved.
using return_type = T;

explicit StreamLoader(Args... args)
: args_{std::move(args)...} {}

T load(std::istream& stream) const {
return std::apply(
[&](const Args&... args) { return lib::load_from_stream<T>(stream, args...); },

@rfsaliev rfsaliev Oct 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if one of Args is not copyable, i.e. has deleted copy constructor?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works so long it's passed as an rvalue. Will add a test case showcasing it. StreamLoader can of course always be extended if more scenarios are needed. For now, everything compiles though.

args_
);
}

T load(const std::filesystem::path& path) const {
return std::apply(
[&](const Args&... args) { return lib::load_from_disk<T>(path, args...); },
args_
);
}

std::tuple<Args...> args_;
};

///
/// @brief Build a ``StreamLoader`` for ``T``, deducing the loader argument types.
///
/// @tparam T The type loaded from the component.
///
/// @param args Arguments forwarded to the load function, stored by value.
///
template <typename T, typename... Args>
StreamLoader<T, std::decay_t<Args>...> make_stream_loader(Args&&... args) {
return StreamLoader<T, std::decay_t<Args>...>{std::forward<Args>(args)...};
}

template <typename T> inline constexpr bool is_stream_loader_v = false;

template <typename T, typename... Args>
inline constexpr bool is_stream_loader_v<StreamLoader<T, Args...>> = true;

} // namespace svs

namespace svs::detail {

///
Expand Down
47 changes: 28 additions & 19 deletions include/svs/orchestrators/concurrent_dynamic_vamana.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ class ConcurrentDynamicVamana : public DynamicVamana {
Distance distance,
ThreadPoolProto threadpool_proto,
bool debug_load_from_static = false
) {
)
requires(!std::is_base_of_v<std::istream, std::remove_cvref_t<ConfigProto>> && !is_stream_loader_v<std::remove_cvref_t<GraphLoader>> && !is_stream_loader_v<std::remove_cvref_t<DataLoader>>)
{
auto threadpool = threads::as_threadpool(std::move(threadpool_proto));
auto make = [&](auto distance_function) {
return ConcurrentDynamicVamana(
Expand Down Expand Up @@ -159,20 +161,31 @@ class ConcurrentDynamicVamana : public DynamicVamana {
///
/// Accepts both the native stream format and the directory-archive format.
///
/// @tparam Data The ``SegmentedBlocked`` dataset type to load.
/// @param stream Stream positioned at the start of a saved index.
/// @param graph_loader ``StreamLoader`` producing a concurrent ``SimpleBlockedGraph``.
/// @param data_loader ``StreamLoader`` producing ``SegmentedBlocked`` storage.
/// @param distance Distance functor or ``svs::DistanceType`` enum.
/// @param threadpool_proto Thread pool or number of threads to use.
/// @param debug_load_from_static Load a static index config with identity IDs.
///
template <
manager::QueryTypeDefinition QueryTypes,
typename Data,
typename GraphLoader,
typename DataLoader,
typename Distance,
typename ThreadPoolProto,
typename... DataLoaderArgs>
typename ThreadPoolProto>
static ConcurrentDynamicVamana assemble(
std::istream& stream,
const GraphLoader& graph_loader,
const DataLoader& data_loader,
Distance distance,
ThreadPoolProto threadpool_proto,
DataLoaderArgs&&... data_args
) {
bool debug_load_from_static = false
)
requires(is_stream_loader_v<GraphLoader> && is_stream_loader_v<DataLoader>)
{
using Graph = typename GraphLoader::return_type;
using Data = typename DataLoader::return_type;
static_assert(
index::vamana::concurrent::is_segmented_blocked_v<
typename Data::allocator_type>,
Expand All @@ -187,16 +200,11 @@ class ConcurrentDynamicVamana : public DynamicVamana {
manager::as_typelist<QueryTypes>(),
index::vamana::concurrent::auto_dynamic_assemble(
stream,
[&]() -> default_graph_type {
return default_graph_type::load(stream);
},
[&]() -> Data {
return lib::load_from_stream<Data>(
stream, SVS_FWD(data_args)...
);
},
[&]() -> Graph { return graph_loader.load(stream); },
[&]() -> Data { return data_loader.load(stream); },
std::move(distance_function),
std::move(threadpool)
std::move(threadpool),
debug_load_from_static
)
);
};
Expand Down Expand Up @@ -225,10 +233,11 @@ class ConcurrentDynamicVamana : public DynamicVamana {

return assemble<QueryTypes>(
config_path,
SVS_LAZY(default_graph_type::load(graph_path)),
lib::load_from_disk<Data>(data_path, SVS_FWD(data_args)...),
SVS_LAZY(graph_loader.load(graph_path)),
data_loader.load(data_path),
std::move(distance),
std::move(threadpool)
std::move(threadpool),
debug_load_from_static
);
}
};
Expand Down
Loading
Loading