Skip to content

[MOD-9557] Fix incorrect vector blob size calculation #665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
25f3c3a
add size_t bg_vector_indexing_count;
meiravgri Apr 15, 2025
ffe9e81
fix datasize in executeInsertJob
meiravgri Apr 18, 2025
64b3d3f
disable neighbours print
meiravgri Apr 27, 2025
94e5789
replace manual blob size calcaulations
meiravgri Apr 28, 2025
3524565
INT8Test::PopulateRandomVector
meiravgri Apr 30, 2025
66a7237
Merge remote-tracking branch 'origin/main' into meiravg_investigate_i…
meiravgri May 1, 2025
80d5cec
build test with VERBOSE=1
meiravgri May 2, 2025
3fe0515
verbose cov
meiravgri May 3, 2025
b097a20
verbose cov fix
meiravgri May 3, 2025
b10dd24
fix
meiravgri May 3, 2025
6b2a89e
add force_copy to maybeCopyToAlignedMem and preprocessQuery
meiravgri May 4, 2025
c71876b
also batch in svs
meiravgri May 4, 2025
07f9ec1
revert redundancies
meiravgri May 4, 2025
a618046
tiered batch iterator doesn't copy the blob
meiravgri May 4, 2025
a038e1d
add
meiravgri May 4, 2025
3662895
revert pull request changes
meiravgri May 4, 2025
284646e
Merge remote-tracking branch 'origin/main' into meiravg_investigate_i…
meiravgri May 4, 2025
5a672ea
move getDataByLabel and getStoredVectorDataByLabel to VecSimIndexAbst…
meiravgri May 5, 2025
2d4e7ca
Merge remote-tracking branch 'origin/main' into meiravg_investigate_i…
meiravgri May 5, 2025
4fa002a
add test_index_test_utils
meiravgri May 6, 2025
0c3d47d
getFlatBufferIndex returns BruteForceIndex
meiravgri May 6, 2025
2495ae6
little test name fix
meiravgri May 6, 2025
1cf9456
small fix
meiravgri May 6, 2025
b4c0f3f
Merge remote-tracking branch 'origin/main' into meiravg_investigate_i…
meiravgri May 6, 2025
1747911
fix range
meiravgri May 7, 2025
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
4 changes: 2 additions & 2 deletions cmake/svs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ if(USE_SVS)
find_package(svs REQUIRED)
set(SVS_LVQ_HEADER "svs/extensions/vamana/lvq.h")
else()
# This file is included from both CMakeLists.txt and python_bindings/CMakeLists.txt
# Set `root` relative to this file, regardless of where it is included from.
# This file is included from both CMakeLists.txt and python_bindings/CMakeLists.txt
# Set `root` relative to this file, regardless of where it is included from.
get_filename_component(root ${CMAKE_CURRENT_LIST_DIR}/.. ABSOLUTE)
add_subdirectory(
${root}/deps/ScalableVectorSearch
Expand Down
25 changes: 8 additions & 17 deletions src/VecSim/algorithms/brute_force/brute_force.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class BruteForceIndex : public VecSimIndexAbstract<DataType, DistType> {
size_t indexSize() const override;
size_t indexCapacity() const override;
std::unique_ptr<RawDataContainer::Iterator> getVectorsIterator() const;
DataType *getDataByInternalId(idType id) const {
return (DataType *)this->vectors->getElement(id);
const DataType *getDataByInternalId(idType id) const {
return reinterpret_cast<const DataType *>(this->vectors->getElement(id));
}
VecSimQueryReply *topKQuery(const void *queryBlob, size_t k,
VecSimQueryParams *queryParams) const override;
Expand Down Expand Up @@ -76,16 +76,6 @@ class BruteForceIndex : public VecSimIndexAbstract<DataType, DistType> {

virtual ~BruteForceIndex() = default;
#ifdef BUILD_TESTS
/**
* @brief Used for testing - store vector(s) data associated with a given label. This function
* copies the vector(s)' data buffer(s) and place it in the output vector
*
* @param label
* @param vectors_output empty vector to be modified, should store the blob(s) associated with
* the label.
*/
virtual void getDataByLabel(labelType label,
std::vector<std::vector<DataType>> &vectors_output) const = 0;
void fitMemory() override {
if (count == 0) {
return;
Expand Down Expand Up @@ -350,12 +340,13 @@ template <typename DataType, typename DistType>
VecSimBatchIterator *
BruteForceIndex<DataType, DistType>::newBatchIterator(const void *queryBlob,
VecSimQueryParams *queryParams) const {
auto *queryBlobCopy =
this->allocator->allocate_aligned(this->dataSize, this->preprocessors->getAlignment());
memcpy(queryBlobCopy, queryBlob, this->dim * sizeof(DataType));
this->preprocessQueryInPlace(queryBlobCopy);
// force_copy == true.
auto queryBlobCopy = this->preprocessQuery(queryBlob, true);

// take ownership of the blob copy and pass it to the batch iterator.
auto *queryBlobCopyPtr = queryBlobCopy.release();
// Ownership of queryBlobCopy moves to BF_BatchIterator that will free it at the end.
return newBatchIterator_Instance(queryBlobCopy, queryParams);
return newBatchIterator_Instance(queryBlobCopyPtr, queryParams);
}

template <typename DataType, typename DistType>
Expand Down
20 changes: 20 additions & 0 deletions src/VecSim/algorithms/brute_force/brute_force_multi.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,30 @@

for (idType id : ids->second) {
auto vec = std::vector<DataType>(this->dim);
// Only copy the vector data (dim * sizeof(DataType)), not any additional metadata like
// the norm
memcpy(vec.data(), this->getDataByInternalId(id), this->dim * sizeof(DataType));
vectors_output.push_back(vec);
}
}

std::vector<std::vector<char>> getStoredVectorDataByLabel(labelType label) const override {
std::vector<std::vector<char>> vectors_output;
auto ids = labelToIdsLookup.find(label);

for (idType id : ids->second) {
// Get the data pointer - need to cast to char* for memcpy
const char *data = reinterpret_cast<const char *>(this->getDataByInternalId(id));

// Create a vector with the full data (including any metadata like norms)
std::vector<char> vec(this->getDataSize());
memcpy(vec.data(), data, this->getDataSize());
vectors_output.push_back(std::move(vec));
}

return vectors_output;
}

Check warning on line 72 in src/VecSim/algorithms/brute_force/brute_force_multi.h

View check run for this annotation

Codecov / codecov/patch

src/VecSim/algorithms/brute_force/brute_force_multi.h#L72

Added line #L72 was not covered by tests

#endif
private:
// inline definitions
Expand Down
17 changes: 17 additions & 0 deletions src/VecSim/algorithms/brute_force/brute_force_single.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,26 @@ class BruteForceIndex_Single : public BruteForceIndex<DataType, DistType> {
auto id = labelToIdLookup.at(label);

auto vec = std::vector<DataType>(this->dim);
// Only copy the vector data (dim * sizeof(DataType)), not any additional metadata like the
// norm
memcpy(vec.data(), this->getDataByInternalId(id), this->dim * sizeof(DataType));
vectors_output.push_back(vec);
}

std::vector<std::vector<char>> getStoredVectorDataByLabel(labelType label) const override {
std::vector<std::vector<char>> vectors_output;
auto id = labelToIdLookup.at(label);

// Get the data pointer - need to cast to char* for memcpy
const char *data = reinterpret_cast<const char *>(this->getDataByInternalId(id));

// Create a vector with the full data (including any metadata like norms)
std::vector<char> vec(this->getDataSize());
memcpy(vec.data(), data, this->getDataSize());
vectors_output.push_back(std::move(vec));

return vectors_output;
}
#endif
protected:
// inline definitions
Expand Down
12 changes: 1 addition & 11 deletions src/VecSim/algorithms/hnsw/hnsw.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,16 +302,6 @@ class HNSWIndex : public VecSimIndexAbstract<DataType, DistType>,
virtual int removeLabel(labelType label) = 0;

#ifdef BUILD_TESTS
/**
* @brief Used for testing - store vector(s) data associated with a given label. This function
* copies the vector(s)' data buffer(s) and place it in the output vector
*
* @param label
* @param vectors_output empty vector to be modified, should store the blob(s) associated with
* the label.
*/
virtual void getDataByLabel(labelType label,
std::vector<std::vector<DataType>> &vectors_output) const = 0;
void fitMemory() override {
if (maxElements > 0) {
idToMetaData.shrink_to_fit();
Expand Down Expand Up @@ -1561,7 +1551,7 @@ void HNSWIndex<DataType, DistType>::insertElementToGraph(idType element_id,
for (auto level = static_cast<int>(max_common_level); level >= 0; level--) {
candidatesMaxHeap<DistType> top_candidates =
searchLayer(curr_element, vector_data, level, efConstruction);
// If the entry point was marked deleted between iterations, we may recieve an empty
// If the entry point was marked deleted between iterations, we may receive an empty
// candidates set.
if (!top_candidates.empty()) {
curr_element = mutuallyConnectNewElement(element_id, top_candidates, level);
Expand Down
31 changes: 25 additions & 6 deletions src/VecSim/algorithms/hnsw/hnsw_multi.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,28 @@

for (idType id : ids->second) {
auto vec = std::vector<DataType>(this->dim);
memcpy(vec.data(), this->getDataByInternalId(id), this->dataSize);
// Only copy the vector data (dim * sizeof(DataType)), not any additional metadata like
// the norm
memcpy(vec.data(), this->getDataByInternalId(id), this->dim * sizeof(DataType));

Check warning on line 79 in src/VecSim/algorithms/hnsw/hnsw_multi.h

View check run for this annotation

Codecov / codecov/patch

src/VecSim/algorithms/hnsw/hnsw_multi.h#L79

Added line #L79 was not covered by tests
vectors_output.push_back(vec);
}
}

std::vector<std::vector<char>> getStoredVectorDataByLabel(labelType label) const override {
std::vector<std::vector<char>> vectors_output;
auto ids = labelLookup.find(label);

for (idType id : ids->second) {
const char *data = this->getDataByInternalId(id);

// Create a vector with the full data (including any metadata like norms)
std::vector<char> vec(this->dataSize);
memcpy(vec.data(), data, this->dataSize);
vectors_output.push_back(std::move(vec));
}

return vectors_output;
}

Check warning on line 98 in src/VecSim/algorithms/hnsw/hnsw_multi.h

View check run for this annotation

Codecov / codecov/patch

src/VecSim/algorithms/hnsw/hnsw_multi.h#L98

Added line #L98 was not covered by tests
#endif
~HNSWIndex_Multi() = default;

Expand Down Expand Up @@ -201,13 +219,14 @@
VecSimBatchIterator *
HNSWIndex_Multi<DataType, DistType>::newBatchIterator(const void *queryBlob,
VecSimQueryParams *queryParams) const {
auto queryBlobCopy =
this->allocator->allocate_aligned(this->dataSize, this->preprocessors->getAlignment());
memcpy(queryBlobCopy, queryBlob, this->dim * sizeof(DataType));
this->preprocessQueryInPlace(queryBlobCopy);
// force_copy == true.
auto queryBlobCopy = this->preprocessQuery(queryBlob, true);

// take ownership of the blob copy and pass it to the batch iterator.
auto *queryBlobCopyPtr = queryBlobCopy.release();
// Ownership of queryBlobCopy moves to HNSW_BatchIterator that will free it at the end.
return new (this->allocator) HNSWMulti_BatchIterator<DataType, DistType>(
queryBlobCopy, this, queryParams, this->allocator);
queryBlobCopyPtr, this, queryParams, this->allocator);
}

/**
Expand Down
28 changes: 22 additions & 6 deletions src/VecSim/algorithms/hnsw/hnsw_single.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,24 @@ class HNSWIndex_Single : public HNSWIndex<DataType, DistType> {
auto id = labelLookup.at(label);

auto vec = std::vector<DataType>(this->dim);
memcpy(vec.data(), this->getDataByInternalId(id), this->dataSize);
// Only copy the vector data (dim * sizeof(DataType)), not any additional metadata like the
// norm
memcpy(vec.data(), this->getDataByInternalId(id), this->dim * sizeof(DataType));
vectors_output.push_back(vec);
}

std::vector<std::vector<char>> getStoredVectorDataByLabel(labelType label) const override {
std::vector<std::vector<char>> vectors_output;
auto id = labelLookup.at(label);
const char *data = this->getDataByInternalId(id);

// Create a vector with the full data (including any metadata like norms)
std::vector<char> vec(this->dataSize);
memcpy(vec.data(), data, this->dataSize);
vectors_output.push_back(std::move(vec));

return vectors_output;
}
#endif
~HNSWIndex_Single() = default;

Expand Down Expand Up @@ -161,13 +176,14 @@ template <typename DataType, typename DistType>
VecSimBatchIterator *
HNSWIndex_Single<DataType, DistType>::newBatchIterator(const void *queryBlob,
VecSimQueryParams *queryParams) const {
auto queryBlobCopy =
this->allocator->allocate_aligned(this->dataSize, this->preprocessors->getAlignment());
memcpy(queryBlobCopy, queryBlob, this->dim * sizeof(DataType));
this->preprocessQueryInPlace(queryBlobCopy);
// force_copy == true.
auto queryBlobCopy = this->preprocessQuery(queryBlob, true);

// take ownership of the blob copy and pass it to the batch iterator.
auto *queryBlobCopyPtr = queryBlobCopy.release();
// Ownership of queryBlobCopy moves to HNSW_BatchIterator that will free it at the end.
return new (this->allocator) HNSWSingle_BatchIterator<DataType, DistType>(
queryBlobCopy, this, queryParams, this->allocator);
queryBlobCopyPtr, this, queryParams, this->allocator);
}

/**
Expand Down
31 changes: 18 additions & 13 deletions src/VecSim/algorithms/hnsw/hnsw_tiered.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class TieredHNSWIndex : public VecSimTieredIndex<DataType, DistType> {
inline void filter_irrelevant_results(VecSimQueryResultContainer &);

public:
TieredHNSW_BatchIterator(void *query_vector,
TieredHNSW_BatchIterator(const void *query_vector,
const TieredHNSWIndex<DataType, DistType> *index,
VecSimQueryParams *queryParams,
std::shared_ptr<VecSimAllocator> allocator);
Expand Down Expand Up @@ -206,11 +206,9 @@ class TieredHNSWIndex : public VecSimTieredIndex<DataType, DistType> {
VecSimDebugInfoIterator *debugInfoIterator() const override;
VecSimBatchIterator *newBatchIterator(const void *queryBlob,
VecSimQueryParams *queryParams) const override {
size_t blobSize = this->frontendIndex->getDim() * sizeof(DataType);
void *queryBlobCopy = this->allocator->allocate(blobSize);
memcpy(queryBlobCopy, queryBlob, blobSize);
// The query blob will be processed and copied by the internal indexes's batch iterator.
return new (this->allocator)
TieredHNSW_BatchIterator(queryBlobCopy, this, queryParams, this->allocator);
TieredHNSW_BatchIterator(queryBlob, this, queryParams, this->allocator);
}
inline void setLastSearchMode(VecSearchMode mode) override {
return this->backendIndex->setLastSearchMode(mode);
Expand Down Expand Up @@ -545,10 +543,11 @@ void TieredHNSWIndex<DataType, DistType>::executeInsertJob(HNSWInsertJob *job) {
HNSWIndex<DataType, DistType> *hnsw_index = this->getHNSWIndex();
// Copy the vector blob from the flat buffer, so we can release the flat lock while we are
// indexing the vector into HNSW index.
auto blob_copy = this->getAllocator()->allocate_unique(this->frontendIndex->getDataSize());

memcpy(blob_copy.get(), this->frontendIndex->getDataByInternalId(job->id),
this->frontendIndex->getDim() * sizeof(DataType));
size_t data_size = this->frontendIndex->getDataSize();
auto blob_copy = this->getAllocator()->allocate_unique(data_size);
// Assuming the size of the blob stored in the frontend index matches the size of the blob
Copy link
Preview

Copilot AI May 6, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider adding an assertion or runtime check to verify that the blob size from the frontend index (getDataSize()) matches the expected size in the HNSW index. This extra check would help catch unexpected mismatches early.

Copilot uses AI. Check for mistakes.

// stored in the HNSW index.
memcpy(blob_copy.get(), this->frontendIndex->getDataByInternalId(job->id), data_size);

this->insertVectorToHNSW<true>(hnsw_index, job->label, blob_copy.get());

Expand Down Expand Up @@ -719,7 +718,7 @@ int TieredHNSWIndex<DataType, DistType>::addVector(const void *blob, labelType l
int ret = 1;
auto hnsw_index = this->getHNSWIndex();
// writeMode is not protected since it is assumed to be called only from the "main thread"
// (that is the thread that is exculusively calling add/delete vector).
// (that is the thread that is exclusively calling add/delete vector).
if (this->getWriteMode() == VecSim_WriteInPlace) {
// First, check if we need to overwrite the vector in-place for single (from both indexes).
if (!this->backendIndex->isMultiValue()) {
Expand Down Expand Up @@ -849,7 +848,7 @@ int TieredHNSWIndex<DataType, DistType>::deleteVector(labelType label) {
// Note that we may remove the same vector that has been removed from the flat index, if it was
// being ingested at that time.
// writeMode is not protected since it is assumed to be called only from the "main thread"
// (that is the thread that is exculusively calling add/delete vector).
// (that is the thread that is exclusively calling add/delete vector).
if (this->getWriteMode() == VecSim_WriteAsync) {
num_deleted_vectors += this->deleteLabelFromHNSW(label);
// Apply ready swap jobs if number of deleted vectors reached the threshold
Expand Down Expand Up @@ -924,9 +923,14 @@ double TieredHNSWIndex<DataType, DistType>::getDistanceFrom_Unsafe(labelType lab

template <typename DataType, typename DistType>
TieredHNSWIndex<DataType, DistType>::TieredHNSW_BatchIterator::TieredHNSW_BatchIterator(
void *query_vector, const TieredHNSWIndex<DataType, DistType> *index,
const void *query_vector, const TieredHNSWIndex<DataType, DistType> *index,
VecSimQueryParams *queryParams, std::shared_ptr<VecSimAllocator> allocator)
: VecSimBatchIterator(query_vector, queryParams ? queryParams->timeoutCtx : nullptr,
// Tiered batch iterator doesn't hold its own copy of the query vector.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is that? Please extend the document here since it is not trivial..

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

// Instead, each internal batch iterators (flat_iterator and hnsw_iterator) create their own
// copies: flat_iterator copy is created during TieredHNSW_BatchIterator construction When
// TieredHNSW_BatchIterator::getNextResults() is called and hnsw_iterator is not initialized, it
// retrieves the blob from flat_iterator
: VecSimBatchIterator(nullptr, queryParams ? queryParams->timeoutCtx : nullptr,
std::move(allocator)),
index(index), flat_results(this->allocator), hnsw_results(this->allocator),
flat_iterator(this->index->frontendIndex->newBatchIterator(query_vector, queryParams)),
Expand Down Expand Up @@ -1192,4 +1196,5 @@ void TieredHNSWIndex<DataType, DistType>::getDataByLabel(
labelType label, std::vector<std::vector<DataType>> &vectors_output) const {
this->getHNSWIndex()->getDataByLabel(label, vectors_output);
}

#endif
27 changes: 19 additions & 8 deletions src/VecSim/algorithms/svs/svs.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,12 @@
return MemoryUtils::unique_blob{const_cast<void *>(original_data), [](void *) {}};
}

const auto data_size = this->dim * sizeof(DataType) * n;
const auto data_size = this->getDataSize() * n;

auto processed_blob =
MemoryUtils::unique_blob{this->allocator->allocate(data_size),
[this](void *ptr) { this->allocator->free_allocation(ptr); }};
// Assuming original data size equals to processed data size
memcpy(processed_blob.get(), original_data, data_size);
// Preprocess each vector in place
for (size_t i = 0; i < n; i++) {
Expand Down Expand Up @@ -435,17 +436,18 @@

VecSimBatchIterator *newBatchIterator(const void *queryBlob,
VecSimQueryParams *queryParams) const override {
auto *queryBlobCopy =
this->allocator->allocate_aligned(this->dataSize, this->preprocessors->getAlignment());
memcpy(queryBlobCopy, queryBlob, this->dim * sizeof(DataType));
this->preprocessQueryInPlace(queryBlobCopy);
// force_copy == true.
Copy link
Preview

Copilot AI May 4, 2025

Choose a reason for hiding this comment

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

[nitpick] Clarify in documentation the rationale for forcing a copy (force_copy == true) of the query blob in the newBatchIterator to make the memory ownership semantics explicit.

Suggested change
// force_copy == true.
// force_copy == true. Forcing a copy ensures that the VecSimBatchIterator takes ownership
// of the query blob's memory and is responsible for freeing it. This avoids potential
// memory management issues and makes ownership semantics explicit.

Copilot uses AI. Check for mistakes.

auto queryBlobCopy = this->preprocessQuery(queryBlob, true);

// take ownership of the blob copy and pass it to the batch iterator.
auto *queryBlobCopyPtr = queryBlobCopy.release();
// Ownership of queryBlobCopy moves to VecSimBatchIterator that will free it at the end.
if (indexSize() == 0) {
return new (this->getAllocator())
NullSVS_BatchIterator(queryBlobCopy, queryParams, this->getAllocator());
NullSVS_BatchIterator(queryBlobCopyPtr, queryParams, this->getAllocator());
} else {
return new (this->getAllocator()) SVS_BatchIterator<impl_type, data_type>(
queryBlobCopy, impl_.get(), queryParams, this->getAllocator());
queryBlobCopyPtr, impl_.get(), queryParams, this->getAllocator());
}
}

Expand Down Expand Up @@ -479,6 +481,15 @@
}

#ifdef BUILD_TESTS
virtual void fitMemory() {};
void fitMemory() override {}
std::vector<std::vector<char>> getStoredVectorDataByLabel(labelType label) const override {
assert(nullptr && "Not implemented");

Check warning on line 486 in src/VecSim/algorithms/svs/svs.h

View check run for this annotation

Codecov / codecov/patch

src/VecSim/algorithms/svs/svs.h#L485-L486

Added lines #L485 - L486 were not covered by tests
Copy link
Preview

Copilot AI May 6, 2025

Choose a reason for hiding this comment

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

[nitpick] If getStoredVectorDataByLabel in SVSIndex is likely to be invoked during tests or in production, consider providing a safe default implementation instead of an assert, or clearly document that it should not be used.

Suggested change
assert(nullptr && "Not implemented");
// This method is not implemented. Returning an empty vector as a safe default.

Copilot uses AI. Check for mistakes.

return {};
}
void getDataByLabel(

Check warning on line 489 in src/VecSim/algorithms/svs/svs.h

View check run for this annotation

Codecov / codecov/patch

src/VecSim/algorithms/svs/svs.h#L489

Added line #L489 was not covered by tests
labelType label,
std::vector<std::vector<svs_details::vecsim_dt<DataType>>> &vectors_output) const override {
assert(nullptr && "Not implemented");

Check warning on line 492 in src/VecSim/algorithms/svs/svs.h

View check run for this annotation

Codecov / codecov/patch

src/VecSim/algorithms/svs/svs.h#L492

Added line #L492 was not covered by tests
}
#endif
};
Loading
Loading