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
20 changes: 10 additions & 10 deletions src/VecSim/algorithms/hnsw/hnsw.h
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ void HNSWIndex<DataType, DistType>::processCandidate(

elements_tags[candidate_id] = visited_tag;

DistType cur_dist = this->calcDistance(query_data, cur_data);
DistType cur_dist = this->calcDistanceForQuery(cur_data, query_data);
if (lowerBound > cur_dist || top_candidates.size() < ef) {

candidate_set.emplace(-cur_dist, candidate_id);
Expand Down Expand Up @@ -572,7 +572,7 @@ void HNSWIndex<DataType, DistType>::processCandidate(

elements_tags[candidate_id] = visited_tag;

DistType cur_dist = this->calcDistance(query_data, cur_data);
DistType cur_dist = this->calcDistanceForQuery(cur_data, query_data);
if (lowerBound > cur_dist || top_candidates.size() < ef) {
candidate_set.emplace(-cur_dist, candidate_id);

Expand Down Expand Up @@ -629,7 +629,7 @@ void HNSWIndex<DataType, DistType>::processCandidate_RangeSearch(

elements_tags[candidate_id] = visited_tag;

DistType cur_dist = this->calcDistance(query_data, cur_data);
DistType cur_dist = this->calcDistanceForQuery(cur_data, query_data);
if (cur_dist < dyn_range) {
candidate_set.emplace(-cur_dist, candidate_id);

Expand All @@ -647,7 +647,7 @@ void HNSWIndex<DataType, DistType>::processCandidate_RangeSearch(

elements_tags[candidate_id] = visited_tag;

DistType cur_dist = this->calcDistance(query_data, cur_data);
DistType cur_dist = this->calcDistanceForQuery(cur_data, query_data);
if (cur_dist < dyn_range) {
candidate_set.emplace(-cur_dist, candidate_id);

Expand All @@ -674,7 +674,7 @@ HNSWIndex<DataType, DistType>::searchLayer(idType ep_id, const void *data_point,

DistType lowerBound;
if (!isMarkedDeleted(ep_id)) {
DistType dist = this->calcDistance(data_point, getDataByInternalId(ep_id));
DistType dist = this->calcDistanceForQuery(getDataByInternalId(ep_id), data_point);
lowerBound = dist;
top_candidates.emplace(dist, ep_id);
candidate_set.emplace(-dist, ep_id);
Expand Down Expand Up @@ -1219,7 +1219,7 @@ void HNSWIndex<DataType, DistType>::greedySearchLevel(const void *vector_data, s
if (isInProcess(candidate)) {
continue;
}
DistType d = this->calcDistance(vector_data, getDataByInternalId(candidate));
DistType d = this->calcDistanceForQuery(getDataByInternalId(candidate), vector_data);
if (d < curDist) {
curDist = d;
bestCand = candidate;
Expand Down Expand Up @@ -1557,7 +1557,7 @@ void HNSWIndex<DataType, DistType>::insertElementToGraph(idType element_id,
size_t max_common_level;
if (element_max_level < global_max_level) {
max_common_level = element_max_level;
cur_dist = this->calcDistance(vector_data, getDataByInternalId(curr_element));
cur_dist = this->calcDistanceForQuery(getDataByInternalId(curr_element), vector_data);
for (auto level = static_cast<int>(global_max_level);
level > static_cast<int>(element_max_level); level--) {
// this is done for the levels which are above the max level
Expand Down Expand Up @@ -1878,7 +1878,7 @@ idType HNSWIndex<DataType, DistType>::searchBottomLayerEP(const void *query_data
if (curr_element == INVALID_ID)
return curr_element; // index is empty.

DistType cur_dist = this->calcDistance(query_data, getDataByInternalId(curr_element));
DistType cur_dist = this->calcDistanceForQuery(getDataByInternalId(curr_element), query_data);
for (size_t level = max_level; level > 0 && curr_element != INVALID_ID; --level) {
greedySearchLevel<true>(query_data, level, curr_element, cur_dist, timeoutCtx, rc);
}
Expand All @@ -1901,7 +1901,7 @@ HNSWIndex<DataType, DistType>::searchBottomLayer_WithTimeout(idType ep_id, const
if (!isMarkedDeleted(ep_id)) {
// If ep is not marked as deleted, get its distance and set lower bound and heaps
// accordingly
DistType dist = this->calcDistance(data_point, getDataByInternalId(ep_id));
DistType dist = this->calcDistanceForQuery(getDataByInternalId(ep_id), data_point);
lowerBound = dist;
top_candidates->emplace(dist, getExternalLabel(ep_id));
candidate_set.emplace(-dist, ep_id);
Expand Down Expand Up @@ -2009,7 +2009,7 @@ VecSimQueryResultContainer HNSWIndex<DataType, DistType>::searchRangeBottomLayer
dynamic_range_search_boundaries = dynamic_range = ep_dist;
} else {
// If ep is not marked as deleted, get its distance and set ranges accordingly
ep_dist = this->calcDistance(data_point, getDataByInternalId(ep_id));
ep_dist = this->calcDistanceForQuery(getDataByInternalId(ep_id), data_point);
dynamic_range = ep_dist;
if (ep_dist <= radius) {
// Entry-point is within the radius - add it to the results.
Expand Down
12 changes: 6 additions & 6 deletions src/VecSim/algorithms/hnsw/hnsw_batch_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ VecSimQueryReply_Code HNSW_BatchIterator<DataType, DistType>::scanGraphInternal(
this->visitNode(candidate_id);

const char *candidate_data = this->index->getDataByInternalId(candidate_id);
DistType candidate_dist =
this->index->calcDistance(this->getQueryBlob(), (const void *)candidate_data);
DistType candidate_dist = this->index->calcDistanceForQuery(
(const void *)candidate_data, this->getQueryBlob());

candidates.emplace(candidate_dist, candidate_id);
}
Expand All @@ -150,8 +150,8 @@ VecSimQueryReply_Code HNSW_BatchIterator<DataType, DistType>::scanGraphInternal(
this->visitNode(candidate_id);

const char *candidate_data = this->index->getDataByInternalId(candidate_id);
DistType candidate_dist =
this->index->calcDistance(this->getQueryBlob(), (const void *)candidate_data);
DistType candidate_dist = this->index->calcDistanceForQuery(
(const void *)candidate_data, this->getQueryBlob());

candidates.emplace(candidate_dist, candidate_id);
}
Expand All @@ -175,8 +175,8 @@ HNSW_BatchIterator<DataType, DistType>::scanGraph(VecSimQueryReply_Code *rc) {
if (this->getResultsCount() == 0 && this->top_candidates_extras.empty() &&
this->candidates.empty()) {
if (!index->isMarkedDeleted(this->entry_point)) {
this->lower_bound = this->index->calcDistance(
this->getQueryBlob(), this->index->getDataByInternalId(this->entry_point));
this->lower_bound = this->index->calcDistanceForQuery(
this->index->getDataByInternalId(this->entry_point), this->getQueryBlob());
} else {
this->lower_bound = std::numeric_limits<DistType>::max();
}
Expand Down
2 changes: 1 addition & 1 deletion src/VecSim/algorithms/hnsw/hnsw_multi.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ double HNSWIndex_Multi<DataType, DistType>::getDistanceFromInternal(labelType la

// Iterate over the ids and find the minimum distance.
for (auto id : IDs) {
DistType d = this->calcDistance(this->getDataByInternalId(id), vector_data);
DistType d = this->calcDistanceForQuery(this->getDataByInternalId(id), vector_data);
dist = std::fmin(dist, d);
}

Expand Down
2 changes: 1 addition & 1 deletion src/VecSim/algorithms/hnsw/hnsw_single.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ HNSWIndex_Single<DataType, DistType>::getDistanceFromInternal(labelType label,
}
idType id = it->second;

return this->calcDistance(vector_data, this->getDataByInternalId(id));
return this->calcDistanceForQuery(this->getDataByInternalId(id), vector_data);
}

template <typename DataType, typename DistType>
Expand Down
74 changes: 66 additions & 8 deletions src/VecSim/spaces/computer/calculator.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,46 @@
#include "VecSim/memory/vecsim_base.h"
#include "VecSim/spaces/spaces.h"

enum class DistanceMode {
StoredToStored,
StoredToQuery,
};

/**
* A distance callback selected once when an index is constructed.
*
* Stateless calculators populate `stateless_func`, preserving the existing hot path of one
* indirect call to the selected distance kernel. Stateful calculators populate `stateful_func`
* and `context`; this avoids a vtable lookup in the hot path without forcing stateless
* calculators through an adapter.
*/
template <typename DistType>
struct DistanceDispatch {
using stateful_dist_func_t = DistType (*)(const void *context, const void *lhs, const void *rhs,
size_t dim);

spaces::dist_func_t<DistType> stateless_func = nullptr;
stateful_dist_func_t stateful_func = nullptr;
const void *context = nullptr;

static DistanceDispatch stateless(spaces::dist_func_t<DistType> func) {
return {.stateless_func = func};
}

static DistanceDispatch stateful(const void *context, stateful_dist_func_t func) {
return {.stateful_func = func, .context = context};
}

bool isValid() const { return (stateless_func != nullptr) != (stateful_func != nullptr); }

DistType operator()(const void *lhs, const void *rhs, size_t dim) const {
if (stateless_func) {
return stateless_func(lhs, rhs, dim);
}
return stateful_func(context, lhs, rhs, dim);
}
};

// We need this "wrapper" class to hold the DistanceCalculatorInterface in the index, that is not
// templated according to the distance function signature.
template <typename DistType>
Expand All @@ -24,8 +64,12 @@ class IndexCalculatorInterface : public VecsimBaseObject {

virtual DistType calcDistance(const void *v1, const void *v2, size_t dim) const = 0;

// Raw distance function; cached by the index to skip the vtable on the hot path.
virtual spaces::dist_func_t<DistType> getDistFunc() const = 0;
virtual DistType calcDistanceForQuery(const void *candidate_vector, const void *query_vector,
size_t dim) const = 0;

// Called once per mode when constructing the index. The returned dispatch is cached so
// distance calculations in the hot path do not perform virtual calls.
virtual DistanceDispatch<DistType> getDistanceDispatch(DistanceMode mode) const = 0;
};

/**
Expand All @@ -39,26 +83,40 @@ class IndexCalculatorInterface : public VecsimBaseObject {
template <typename DistType, typename DistFuncType>
class DistanceCalculatorInterface : public IndexCalculatorInterface<DistType> {
public:
DistanceCalculatorInterface(std::shared_ptr<VecSimAllocator> allocator, DistFuncType dist_func)
: IndexCalculatorInterface<DistType>(allocator), dist_func(dist_func) {}
DistanceCalculatorInterface(std::shared_ptr<VecSimAllocator> allocator, DistFuncType dist_func,
DistFuncType query_dist_func = nullptr)
: IndexCalculatorInterface<DistType>(allocator), dist_func(dist_func),
query_dist_func(query_dist_func ? query_dist_func : dist_func) {}
virtual DistType calcDistance(const void *v1, const void *v2, size_t dim) const = 0;
virtual DistType calcDistanceForQuery(const void *candidate_vector, const void *query_vector,
size_t dim) const = 0;

protected:
DistFuncType dist_func;
DistFuncType query_dist_func;
};

template <typename DistType>
class DistanceCalculatorCommon
: public DistanceCalculatorInterface<DistType, spaces::dist_func_t<DistType>> {
public:
DistanceCalculatorCommon(std::shared_ptr<VecSimAllocator> allocator,
spaces::dist_func_t<DistType> dist_func)
: DistanceCalculatorInterface<DistType, spaces::dist_func_t<DistType>>(allocator,
dist_func) {}
spaces::dist_func_t<DistType> dist_func,
spaces::dist_func_t<DistType> query_dist_func = nullptr)
: DistanceCalculatorInterface<DistType, spaces::dist_func_t<DistType>>(allocator, dist_func,
query_dist_func) {}

DistType calcDistance(const void *v1, const void *v2, size_t dim) const override {
return this->dist_func(v1, v2, dim);
}

spaces::dist_func_t<DistType> getDistFunc() const override { return this->dist_func; }
DistType calcDistanceForQuery(const void *candidate_vector, const void *query_vector,
size_t dim) const override {
return this->query_dist_func(candidate_vector, query_vector, dim);
}

DistanceDispatch<DistType> getDistanceDispatch(DistanceMode mode) const override {
auto func = mode == DistanceMode::StoredToStored ? this->dist_func : this->query_dist_func;
return DistanceDispatch<DistType>::stateless(func);
}
};
39 changes: 30 additions & 9 deletions src/VecSim/vec_sim_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ struct VecSimIndexAbstract : public VecSimIndexInterface {
RawDataContainer *vectors; // The raw vectors data container.
private:
IndexCalculatorInterface<DistType> *indexCalculator; // Distance calculator.
spaces::dist_func_t<DistType> cachedDistFunc; // Cached dist func, used on the hot path.
PreprocessorsContainerAbstract *preprocessors; // Storage and query preprocessors.
DistanceDispatch<DistType> storedDistanceDispatch;
DistanceDispatch<DistType> queryDistanceDispatch;
PreprocessorsContainerAbstract *preprocessors; // Storage and query preprocessors.

size_t inputBlobSize; // The size of input vectors/queries blob in bytes. May differ from dim *
// sizeof(vecType) when vectors have been externally preprocessed (e.g.,
Expand Down Expand Up @@ -126,13 +127,21 @@ struct VecSimIndexAbstract : public VecSimIndexInterface {
blockSize(params.blockSize ? params.blockSize : DEFAULT_BLOCK_SIZE), lastMode(EMPTY_MODE),
isMulti(params.multi), isDisk(params.isDisk), logCallbackCtx(params.logCtx),
indexCalculator(components.indexCalculator),
cachedDistFunc(components.indexCalculator ? components.indexCalculator->getDistFunc()
: nullptr),
storedDistanceDispatch(
components.indexCalculator
? components.indexCalculator->getDistanceDispatch(DistanceMode::StoredToStored)
: DistanceDispatch<DistType>{}),
queryDistanceDispatch(
components.indexCalculator
? components.indexCalculator->getDistanceDispatch(DistanceMode::StoredToQuery)
: DistanceDispatch<DistType>{}),
preprocessors(components.preprocessors), inputBlobSize(params.inputBlobSize),
storedDataSize(params.storedDataSize) {
assert(VecSimType_sizeof(vecType));
assert(storedDataSize);
assert(inputBlobSize);
assert(indexCalculator == nullptr || storedDistanceDispatch.isValid());
assert(indexCalculator == nullptr || queryDistanceDispatch.isValid());
// DataBlocksContainer holds the persistent storage vectors, so it must honor the storage
// alignment hint (not the query alignment). Note: this only aligns the base address of
// each block; vectors inside a block are packed back-to-back at stride `storedDataSize`,
Expand All @@ -156,16 +165,28 @@ struct VecSimIndexAbstract : public VecSimIndexInterface {
/**
* @brief Calculate the distance between two vectors based on index parameters.
*
* Uses the cached dist func to avoid the indexCalculator vtable on the hot path.
* Uses the cached dispatch to avoid the indexCalculator vtable on the hot path.
*
* @note Precondition: @c cachedDistFunc must be non-null. Subclasses that construct
* this index with a null @c indexCalculator (e.g. SVS, which uses its own
* internal distance kernels) must not call this method.
* @note Subclasses that construct this index with a null @c indexCalculator (e.g. SVS, which
* uses its own internal distance kernels) must not call this method.
*
* @return the distance between the vectors.
*/
DistType calcDistance(const void *vector_data1, const void *vector_data2) const {
return cachedDistFunc(vector_data1, vector_data2, this->dim);
return storedDistanceDispatch(vector_data1, vector_data2, this->dim);
}

/**
* @brief Calculate the distance between a stored candidate vector and a query vector.
* Allows asymmetric distance computation (e.g., for quantized stored vectors).
*
* @note Subclasses that construct this index with a null @c indexCalculator (e.g. SVS, which
* uses its own internal distance kernels) must not call this method.
*
* @return the distance between the candidate and the query.
*/
DistType calcDistanceForQuery(const void *candidate_vector, const void *query_vector) const {
return queryDistanceDispatch(candidate_vector, query_vector, this->dim);
}

/**
Expand Down
Loading
Loading