Skip to content

Commit ff0c05f

Browse files
committed
LeanVec primary-only: add StorageKind variants
Add two new StorageKind enum values for primary-only LeanVec: LeanVecLVQ4PrimaryOnly and LeanVecLVQ8PrimaryOnly. These select the new LeanDataset<UsingLVQ<N>, void, ...> partial specialization which omits the secondary (reranking) tier for ~50% memory savings at the cost of reranking accuracy. Vamana-only (IVF support out of scope). - api_defs.h: extend StorageKind enum - svs_runtime_utils.h: extend is_leanvec_storage(), add LeanDatasetPrimaryOnlyType alias, StorageType specializations, new dispatch cases in SVS_DISPATCH_STORAGE_KIND - vamana_index_impl.h, dynamic_vamana_index_leanvec_impl.h: new switch cases in dispatch_leanvec_storage_kind - runtime_test.cpp: write/read round-trip tests for both new kinds
1 parent 70e3d52 commit ff0c05f

5 files changed

Lines changed: 100 additions & 1 deletion

File tree

bindings/cpp/include/svs/runtime/api_defs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ enum class StorageKind {
106106
LeanVec4x4,
107107
LeanVec4x8,
108108
LeanVec8x8,
109+
// Primary-only LeanVec variants: only the lossy primary tier is stored, no
110+
// secondary (reranking) tier. Trades reranking accuracy for ~50% less memory.
111+
LeanVecLVQ4PrimaryOnly,
112+
LeanVecLVQ8PrimaryOnly,
109113
};
110114

111115
enum class ErrorCode {

bindings/cpp/src/dynamic_vamana_index_leanvec_impl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ struct DynamicVamanaIndexLeanVecImpl : public DynamicVamanaIndexImpl {
9595
storage::StorageType<StorageKind::LeanVec8x8, allocator_type>{},
9696
std::forward<Args>(args)...
9797
);
98+
case StorageKind::LeanVecLVQ4PrimaryOnly:
99+
return f(
100+
storage::
101+
StorageType<StorageKind::LeanVecLVQ4PrimaryOnly, allocator_type>{},
102+
std::forward<Args>(args)...
103+
);
104+
case StorageKind::LeanVecLVQ8PrimaryOnly:
105+
return f(
106+
storage::
107+
StorageType<StorageKind::LeanVecLVQ8PrimaryOnly, allocator_type>{},
108+
std::forward<Args>(args)...
109+
);
98110
default:
99111
throw StatusException{
100112
ErrorCode::INVALID_ARGUMENT, "SVS LeanVec storage kind required"};

bindings/cpp/src/svs_runtime_utils.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ inline constexpr bool is_lvq_storage(StorageKind kind) {
114114

115115
inline constexpr bool is_leanvec_storage(StorageKind kind) {
116116
return kind == StorageKind::LeanVec4x4 || kind == StorageKind::LeanVec4x8 ||
117-
kind == StorageKind::LeanVec8x8;
117+
kind == StorageKind::LeanVec8x8 || kind == StorageKind::LeanVecLVQ4PrimaryOnly ||
118+
kind == StorageKind::LeanVecLVQ8PrimaryOnly;
118119
}
119120

120121
inline bool is_supported_storage_kind(StorageKind kind) {
@@ -344,6 +345,24 @@ template <typename Alloc> struct StorageType<StorageKind::LeanVec8x8, Alloc> {
344345
using type = LeanDatasetType<8, 8, allocator_type>;
345346
};
346347

348+
// Primary-only LeanVec: secondary tier type is `void`.
349+
template <
350+
size_t I1,
351+
typename Alloc,
352+
size_t LeanVecDims = svs::Dynamic,
353+
size_t Extent = svs::Dynamic>
354+
using LeanDatasetPrimaryOnlyType =
355+
svs::leanvec::LeanDataset<svs::leanvec::UsingLVQ<I1>, void, LeanVecDims, Extent, Alloc>;
356+
357+
template <typename Alloc> struct StorageType<StorageKind::LeanVecLVQ4PrimaryOnly, Alloc> {
358+
using allocator_type = rebind_extracted_allocator_t<std::byte, Alloc>;
359+
using type = LeanDatasetPrimaryOnlyType<4, allocator_type>;
360+
};
361+
template <typename Alloc> struct StorageType<StorageKind::LeanVecLVQ8PrimaryOnly, Alloc> {
362+
using allocator_type = rebind_extracted_allocator_t<std::byte, Alloc>;
363+
using type = LeanDatasetPrimaryOnlyType<8, allocator_type>;
364+
};
365+
347366
template <svs::leanvec::IsLeanDataset LeanVecStorageType>
348367
struct StorageFactory<LeanVecStorageType> {
349368
using StorageType = LeanVecStorageType;
@@ -394,6 +413,8 @@ auto dispatch_storage_kind(StorageKind kind, F&& f, Args&&... args) {
394413
SVS_DISPATCH_STORAGE_KIND(LeanVec4x4);
395414
SVS_DISPATCH_STORAGE_KIND(LeanVec4x8);
396415
SVS_DISPATCH_STORAGE_KIND(LeanVec8x8);
416+
SVS_DISPATCH_STORAGE_KIND(LeanVecLVQ4PrimaryOnly);
417+
SVS_DISPATCH_STORAGE_KIND(LeanVecLVQ8PrimaryOnly);
397418
default:
398419
throw StatusException(
399420
ErrorCode::INVALID_ARGUMENT, "Unknown or unsupported SVS storage kind"

bindings/cpp/src/vamana_index_impl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,18 @@ struct VamanaIndexLeanVecImpl : public VamanaIndexImpl {
555555
storage::StorageType<StorageKind::LeanVec8x8, allocator_type>{},
556556
std::forward<Args>(args)...
557557
);
558+
case StorageKind::LeanVecLVQ4PrimaryOnly:
559+
return f(
560+
storage::
561+
StorageType<StorageKind::LeanVecLVQ4PrimaryOnly, allocator_type>{},
562+
std::forward<Args>(args)...
563+
);
564+
case StorageKind::LeanVecLVQ8PrimaryOnly:
565+
return f(
566+
storage::
567+
StorageType<StorageKind::LeanVecLVQ8PrimaryOnly, allocator_type>{},
568+
std::forward<Args>(args)...
569+
);
558570
default:
559571
throw StatusException{
560572
ErrorCode::INVALID_ARGUMENT, "SVS LeanVec storage kind required"};

bindings/cpp/tests/runtime_test.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,56 @@ CATCH_TEST_CASE("WriteAndReadIndexSVSVamanaLeanVec4x4", "[runtime]") {
333333
);
334334
}
335335

336+
CATCH_TEST_CASE("WriteAndReadIndexSVSVamanaLeanVecLVQ4PrimaryOnly", "[runtime]") {
337+
const auto& test_data = get_test_data();
338+
auto build_func = [](svs::runtime::v0::DynamicVamanaIndex** index) {
339+
svs::runtime::v0::VamanaIndex::BuildParams build_params{64};
340+
svs::runtime::v0::VamanaIndex::DynamicIndexParams dynamic_index_params{19};
341+
return svs::runtime::v0::DynamicVamanaIndexLeanVec::build(
342+
index,
343+
test_d,
344+
svs::runtime::v0::MetricType::L2,
345+
svs::runtime::v0::StorageKind::LeanVecLVQ4PrimaryOnly,
346+
32,
347+
build_params,
348+
{},
349+
dynamic_index_params
350+
);
351+
};
352+
write_and_read_index<svs::runtime::v0::DynamicVamanaIndex>(
353+
build_func,
354+
test_data,
355+
test_n,
356+
test_d,
357+
svs::runtime::v0::StorageKind::LeanVecLVQ4PrimaryOnly
358+
);
359+
}
360+
361+
CATCH_TEST_CASE("WriteAndReadIndexSVSVamanaLeanVecLVQ8PrimaryOnly", "[runtime]") {
362+
const auto& test_data = get_test_data();
363+
auto build_func = [](svs::runtime::v0::DynamicVamanaIndex** index) {
364+
svs::runtime::v0::VamanaIndex::BuildParams build_params{64};
365+
svs::runtime::v0::VamanaIndex::DynamicIndexParams dynamic_index_params{19};
366+
return svs::runtime::v0::DynamicVamanaIndexLeanVec::build(
367+
index,
368+
test_d,
369+
svs::runtime::v0::MetricType::L2,
370+
svs::runtime::v0::StorageKind::LeanVecLVQ8PrimaryOnly,
371+
32,
372+
build_params,
373+
{},
374+
dynamic_index_params
375+
);
376+
};
377+
write_and_read_index<svs::runtime::v0::DynamicVamanaIndex>(
378+
build_func,
379+
test_data,
380+
test_n,
381+
test_d,
382+
svs::runtime::v0::StorageKind::LeanVecLVQ8PrimaryOnly
383+
);
384+
}
385+
336386
CATCH_TEST_CASE("LeanVecWithTrainingData", "[runtime]") {
337387
const auto& test_data = get_test_data();
338388
// Build LeanVec index with explicit training

0 commit comments

Comments
 (0)