Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions bindings/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
target_link_libraries(${TARGET_NAME} PRIVATE
svs::svs
svs_compile_options
svs_native_options
)
if(SVS_EXPERIMENTAL_LINK_STATIC_MKL)
link_mkl_static(${TARGET_NAME})
Expand Down
44 changes: 44 additions & 0 deletions bindings/cpp/include/svs/runtime/api_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,50 @@ enum class StorageKind {
LeanVec8x8,
};

/// Configuration for SSD-backed (memory-mapped) data storage.
///
/// When provided to index assembly methods, data is loaded via memory-mapped
/// files from the specified SSD path instead of being copied into heap memory.
/// This enables zero-copy loading and dramatically reduces memory usage for
/// large datasets.
///
/// For dual-component storage (LVQ4x4, LVQ4x8, LeanVec), primary and
/// secondary/residual data placement can be controlled independently.
/// When either component is on SSD, *both* use the same MMapAllocator
/// (preserving codegen) but with different eviction policies:
/// - on_ssd=true: pages are evicted after mmap, lazily faulted on access.
/// - on_ssd=false: pages stay resident (pre-populated by kernel).
struct SSDConfig {
/// Path to the SSD mount point (e.g., "/mnt/nvme").
/// The runtime uses this as the base directory for memory-mapped files.
/// Must be non-null when primary_on_ssd or secondary_on_ssd is true.
const char* ssd_path = nullptr;

/// Place primary (or only) data component on SSD.
/// For LeanVec this is the reduced-dimension primary data.
/// For dual-level LVQ (LVQ4x4, LVQ4x8) this is the primary quantized data.
/// For single-level LVQ (LVQ4x0, LVQ8x0) this is the only data component.
bool primary_on_ssd = false;

/// Place secondary/residual data component on SSD.
/// For LeanVec this is the full-dimension secondary data.
/// For dual-level LVQ this is the residual correction data.
/// Ignored for single-level LVQ and non-quantized types.
bool secondary_on_ssd = false;

/// Enable primary-only mode for LeanVec storage.
/// When true, only the reduced-dimension primary data is used for both
/// graph traversal and scoring (no reranking with secondary data).
/// This reduces memory usage at the cost of recall accuracy.
/// Ignored for non-LeanVec storage kinds.
bool primary_only = false;

/// Helper: true when any component should use mmap.
bool use_mmap() const {
return (primary_on_ssd || secondary_on_ssd) && ssd_path != nullptr;
}
};

enum class ErrorCode {
SUCCESS = 0,
UNKNOWN_ERROR = 1,
Expand Down
20 changes: 17 additions & 3 deletions bindings/cpp/include/svs/runtime/dynamic_vamana_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,19 @@ struct SVS_RUNTIME_API DynamicVamanaIndex : public VamanaIndex {
DynamicVamanaIndex** index,
std::istream& in,
MetricType metric,
StorageKind storage_kind
StorageKind storage_kind,
bool primary_only = false
) noexcept;

/// Assemble a DynamicVamana index from a saved directory on disk.
/// @see VamanaIndex::assemble_from_directory for details.
static Status assemble_from_directory(
DynamicVamanaIndex** index,
const char* saved_directory,
MetricType metric,
StorageKind storage_kind,
const SSDConfig& ssd_config = {},
const VamanaIndex::SearchParams& default_search_params = {}
) noexcept;

virtual size_t blocksize_bytes() const noexcept = 0;
Expand Down Expand Up @@ -105,7 +117,8 @@ struct SVS_RUNTIME_API DynamicVamanaIndexLeanVec : public DynamicVamanaIndex {
size_t leanvec_dims,
const VamanaIndex::BuildParams& params,
const VamanaIndex::SearchParams& default_search_params,
const VamanaIndex::DynamicIndexParams& dynamic_index_params
const VamanaIndex::DynamicIndexParams& dynamic_index_params,
bool primary_only = false
) noexcept;

// Specialization to build LeanVec-based Vamana index with provided training data
Expand All @@ -128,7 +141,8 @@ struct SVS_RUNTIME_API DynamicVamanaIndexLeanVec : public DynamicVamanaIndex {
const LeanVecTrainingData* training_data,
const VamanaIndex::BuildParams& params,
const VamanaIndex::SearchParams& default_search_params,
const VamanaIndex::DynamicIndexParams& dynamic_index_params
const VamanaIndex::DynamicIndexParams& dynamic_index_params,
bool primary_only = false
) noexcept;
};

Expand Down
26 changes: 24 additions & 2 deletions bindings/cpp/include/svs/runtime/vamana_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,29 @@ struct SVS_RUNTIME_API VamanaIndex {
static Status destroy(VamanaIndex* index) noexcept;

virtual Status save(std::ostream& out) const noexcept = 0;

/// Save the index to a directory with config/, graph/, and data/ subdirectories.
/// The directory must exist. Creates config/, graph/, data/ subdirectories inside it.
virtual Status save_to_directory(const char* directory) const noexcept = 0;

static Status load(
VamanaIndex** index, std::istream& in, MetricType metric, StorageKind storage_kind
) noexcept;

/// Assemble a Vamana index from a saved directory on disk.
///
/// When ssd_config is provided with mode != RAM, data is loaded via
/// memory-mapped files (zero-copy) instead of being copied into RAM.
/// The saved_directory must contain config/, graph/, and data/ subdirectories
/// as produced by VamanaIndex::save() or native-format save utilities.
static Status assemble_from_directory(
VamanaIndex** index,
const char* saved_directory,
MetricType metric,
StorageKind storage_kind,
const SSDConfig& ssd_config = {},
const SearchParams& default_search_params = {}
) noexcept;
};

struct SVS_RUNTIME_API VamanaIndexLeanVec : public VamanaIndex {
Expand All @@ -106,7 +126,8 @@ struct SVS_RUNTIME_API VamanaIndexLeanVec : public VamanaIndex {
StorageKind storage_kind,
size_t leanvec_dims,
const VamanaIndex::BuildParams& params = {},
const VamanaIndex::SearchParams& default_search_params = {}
const VamanaIndex::SearchParams& default_search_params = {},
bool primary_only = false
) noexcept;

// Specialization to build LeanVec-based Vamana index with provided training data
Expand All @@ -117,7 +138,8 @@ struct SVS_RUNTIME_API VamanaIndexLeanVec : public VamanaIndex {
StorageKind storage_kind,
const LeanVecTrainingData* training_data,
const VamanaIndex::BuildParams& params = {},
const VamanaIndex::SearchParams& default_search_params = {}
const VamanaIndex::SearchParams& default_search_params = {},
bool primary_only = false
) noexcept;
};

Expand Down
73 changes: 63 additions & 10 deletions bindings/cpp/src/dynamic_vamana_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ struct DynamicVamanaIndexManagerBase : public DynamicVamanaIndex {
Status save(std::ostream& out) const noexcept override {
return runtime_error_wrapper([&] { impl_->save(out); });
}

Status save_to_directory(const char* directory) const noexcept override {
return runtime_error_wrapper([&] { impl_->save_to_directory(directory); });
}
};
} // namespace

Expand Down Expand Up @@ -205,12 +209,39 @@ Status DynamicVamanaIndex::load(
DynamicVamanaIndex** index,
std::istream& in,
MetricType metric,
StorageKind storage_kind
StorageKind storage_kind,
bool primary_only
) noexcept {
using Impl = DynamicVamanaIndexImpl;
*index = nullptr;
return runtime_error_wrapper([&] {
std::unique_ptr<Impl> impl{Impl::load(in, metric, storage_kind, primary_only)};
*index = new DynamicVamanaIndexManagerBase<Impl>{std::move(impl)};
});
}

Status DynamicVamanaIndex::assemble_from_directory(
DynamicVamanaIndex** index,
const char* saved_directory,
MetricType metric,
StorageKind storage_kind,
const SSDConfig& ssd_config,
const VamanaIndex::SearchParams& default_search_params
) noexcept {
using Impl = DynamicVamanaIndexImpl;
*index = nullptr;
return runtime_error_wrapper([&] {
std::unique_ptr<Impl> impl{Impl::load(in, metric, storage_kind)};
if (saved_directory == nullptr) {
throw StatusException{
ErrorCode::INVALID_ARGUMENT, "saved_directory must not be null"};
}
std::unique_ptr<Impl> impl{Impl::assemble_from_directory(
std::filesystem::path{saved_directory}, metric, storage_kind, ssd_config
)};
if (is_specified(default_search_params.search_window_size) ||
is_specified(default_search_params.search_buffer_capacity)) {
impl->set_default_search_params(default_search_params);
}
*index = new DynamicVamanaIndexManagerBase<Impl>{std::move(impl)};
});
}
Expand Down Expand Up @@ -271,7 +302,8 @@ Status DynamicVamanaIndexLeanVec::build(
size_t leanvec_dims,
const DynamicVamanaIndex::BuildParams& params,
const DynamicVamanaIndex::SearchParams& default_search_params,
const DynamicVamanaIndex::DynamicIndexParams& dynamic_index_params
const DynamicVamanaIndex::DynamicIndexParams& dynamic_index_params,
bool primary_only
) noexcept {
using Impl = DynamicVamanaIndexLeanVecImpl;
*index = nullptr;
Expand All @@ -288,7 +320,8 @@ Status DynamicVamanaIndexLeanVec::build(
leanvec_dims,
params,
default_search_params,
dynamic_index_params
dynamic_index_params,
primary_only
);
*index = new DynamicVamanaIndexManagerBase<Impl>{std::move(impl)};
});
Expand All @@ -303,7 +336,8 @@ Status DynamicVamanaIndexLeanVec::build(
const LeanVecTrainingData* training_data,
const DynamicVamanaIndex::BuildParams& params,
const DynamicVamanaIndex::SearchParams& default_search_params,
const DynamicVamanaIndex::DynamicIndexParams& dynamic_index_params
const DynamicVamanaIndex::DynamicIndexParams& dynamic_index_params,
bool primary_only
) noexcept {
using Impl = DynamicVamanaIndexLeanVecImpl;
*index = nullptr;
Expand All @@ -322,24 +356,43 @@ Status DynamicVamanaIndexLeanVec::build(
training_data_impl,
params,
default_search_params,
dynamic_index_params
dynamic_index_params,
primary_only
);
*index = new DynamicVamanaIndexManagerBase<Impl>{std::move(impl)};
});
}

#else // SVS_RUNTIME_HAVE_LVQ_LEANVEC
// LeanVec storage kind is not supported in this build configuration
Status DynamicVamanaIndexLeanVec::
build(DynamicVamanaIndex**, size_t, MetricType, StorageKind, size_t, const DynamicVamanaIndex::BuildParams&, const DynamicVamanaIndex::SearchParams&, const DynamicVamanaIndex::DynamicIndexParams&) noexcept {
Status DynamicVamanaIndexLeanVec::build(
DynamicVamanaIndex**,
size_t,
MetricType,
StorageKind,
size_t,
const DynamicVamanaIndex::BuildParams&,
const DynamicVamanaIndex::SearchParams&,
const DynamicVamanaIndex::DynamicIndexParams&,
bool
) noexcept {
return Status(
ErrorCode::NOT_IMPLEMENTED,
"DynamicVamanaIndexLeanVec is not supported in this build configuration."
);
}

Status DynamicVamanaIndexLeanVec::
build(DynamicVamanaIndex**, size_t, MetricType, StorageKind, const LeanVecTrainingData*, const DynamicVamanaIndex::BuildParams&, const DynamicVamanaIndex::SearchParams&, const DynamicVamanaIndex::DynamicIndexParams&) noexcept {
Status DynamicVamanaIndexLeanVec::build(
DynamicVamanaIndex**,
size_t,
MetricType,
StorageKind,
const LeanVecTrainingData*,
const DynamicVamanaIndex::BuildParams&,
const DynamicVamanaIndex::SearchParams&,
const DynamicVamanaIndex::DynamicIndexParams&,
bool
) noexcept {
return Status(
ErrorCode::NOT_IMPLEMENTED,
"DynamicVamanaIndexLeanVec is not supported in this build configuration."
Expand Down
Loading
Loading