Skip to content

Commit cc14a65

Browse files
committed
Feature - HNSW refactor - vector blocks - [MOD-5302] (#397)
* HNSW refactor 3 - [MOD-5302] (#389) * moved vector_block * make DataBlock available to use in vectors * some general improvements * implement data blocks in HNSW * disabled serializer benchmarks and some tests * more disable * enabled serialization (for current implementation) * added prefetch for range * move generic meta of element to a separated vector * added prefetch for metadata * enabled save and load from bindings * [TMP] return of the old HNSW REVERT ME * fix bm * some changes in prefetch * shortened BM * reverted prefetches to by as before * shorten BM * small improvement for range * packing structs * unrelated performance improvement * fix * revert adding origin hnsw, remove support for v1 and v2 serialization * update for BM file * some fixes and test updates * [TEMP] disable 2 tests so coverage will run * fix flow test * more test fixes * improved tests * fix for bach iterator scan (needs a benchmark) * for benchmark * reverting some temporary changes * more reverting * fix for clang * file name update * another prefetch option * few improvements * some more trying * final change * another update to all but `hnsw.h` * returned `increaseCapacity` responsibility to `addVector` * make `hnsw.h` use blocks * BF comment fix * comment fix * fix some tests * fixed hnsw tests * fixed hnsw-multi tests * fixed almost all tiered HNSW tests * Fix memory bookkeeping tests * Fix memory bookkeeping tests 2 * fixed estimations and their tests * review fixes * fix review fix * more review fixes * move rounding up of initial capacity to a static function * added comments on data blocks * some optimizations (reduce the use of `getDataByInternalId`) --------- Co-authored-by: alon <[email protected]> * HNSW blocks refactor - add lock to graph data struct (#390) * Improved serializing code - [MOD-5372] (#391) * improved serializing code * review fixes * HNSW Refactor - benchmarks - [MOD-5371] (#392) * update benchmark files * updated wget links * publish serialization script * another benchmark cleanup iteration * review fixes * Renaming "meta" variables (#394) * renaming "meta" variables * revert temp change * Optimize Distance Functions - [MOD-5434] (#395) * initial templated with masks implementations * format * tidy up * enabled spaces tests back * changed template type and handle residual first * re-enabled benchmarks (keeping old names) * download fix * improved unit testing * improved spaces benchmarks * verify correctness * some cleanup * give up optimizing dim<16 for safety * aligned serialization links * added lots of comments * added a test and small fix * include opts only on x86 machines * remove AVX512DQ references from the project (not in use) * rename qty to dimension * Update AVX_utils.h comments * Optimize - implement align allocation for vector alignment - [MOD-5433] (#399) * aligning query vector * implement aligned allocation * added alignment hing to VecSimIndexAbstract, used it in block allocation * test fix * review fixes * set default value to the alignment hint (1 - any address is valid) * refactor allocation header to have alignment flag, unify free function * use alignment only on vector blocks * changed default alignment value (0) * updated tests * added missing break * improved comment * removed alignment from allocator test
1 parent 07ad5f4 commit cc14a65

File tree

124 files changed

+3223
-4352
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+3223
-4352
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
/build/
77
/dist/
88
/venv/
9-
/deps/readies/
9+
/deps/
1010
/1/
1111
**/build/
1212

1313
# Ignore benchmark fetched data but not the source file
1414
/tests/benchmark/data/*
1515
!/tests/benchmark/data/hnsw_indices
16+
!/tests/benchmark/data/serializer.py
1617

1718
# Prerequisites
1819
*.d

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ All of the algorithms in this library is designed to work inside RediSearch and
2727
|-----------|--------|---------|-----------------|
2828
| FP32 Internal product |SSE, AVX, AVX512 | No SIMD support | No SIMD support |
2929
| FP32 L2 distance |SSE, AVX, AVX512| No SIMD support | No SIMD support |
30-
| FP64 Internal product |SSE, AVX, AVX512, AVX512DQ | No SIMD support | No SIMD support |
30+
| FP64 Internal product |SSE, AVX, AVX512 | No SIMD support | No SIMD support |
3131
| FP64 L2 distance |SSE, AVX, AVX512 | No SIMD support | No SIMD support |
3232

3333
### Flat (Brute Force)
@@ -92,5 +92,5 @@ tox -e flowenv
9292

9393
# Benchmark
9494

95-
To benchmark the capabilities of this library, follow the instructions in the [benchmarks user guide](docs/benchmarks.md).
96-
If you'd like to create your own benchmarks, you can find more information in the [developer guide](docs/benchmarks_developer.md).
95+
To benchmark the capabilities of this library, follow the instructions in the [benchmarks user guide](docs/benchmarks.md).
96+
If you'd like to create your own benchmarks, you can find more information in the [developer guide](docs/benchmarks_developer.md).

src/VecSim/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ add_library(VectorSimilarity ${VECSIM_LIBTYPE}
1919
index_factories/hnsw_factory.cpp
2020
index_factories/tiered_factory.cpp
2121
index_factories/index_factory.cpp
22-
algorithms/brute_force/vector_block.cpp
2322
algorithms/hnsw/visited_nodes_handler.cpp
2423
vec_sim.cpp
2524
vec_sim_interface.cpp
2625
query_results.cpp
2726
info_iterator.cpp
2827
query_result_struct.cpp
2928
utils/vec_utils.cpp
29+
utils/data_block.cpp
3030
memory/vecsim_malloc.cpp
3131
memory/vecsim_base.cpp
3232
${HEADER_LIST}

src/VecSim/algorithms/brute_force/bfm_batch_iterator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class BFM_BatchIterator : public BF_BatchIterator<DataType, DistType> {
2424
this->scores.reserve(this->index_label_count);
2525
vecsim_stl::unordered_map<labelType, DistType> tmp_scores(this->index_label_count,
2626
this->allocator);
27-
vecsim_stl::vector<VectorBlock *> blocks = this->index->getVectorBlocks();
27+
auto &blocks = this->index->getVectorBlocks();
2828
VecSimQueryResult_Code rc;
2929

3030
idType curr_id = 0;

src/VecSim/algorithms/brute_force/bfs_batch_iterator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class BFS_BatchIterator : public BF_BatchIterator<DataType, DistType> {
2222
inline VecSimQueryResult_Code calculateScores() override {
2323
this->index_label_count = this->index->indexLabelCount();
2424
this->scores.reserve(this->index_label_count);
25-
vecsim_stl::vector<VectorBlock *> blocks = this->index->getVectorBlocks();
25+
auto &blocks = this->index->getVectorBlocks();
2626
VecSimQueryResult_Code rc;
2727

2828
idType curr_id = 0;

src/VecSim/algorithms/brute_force/brute_force.h

Lines changed: 68 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#pragma once
88

9-
#include "vector_block.h"
9+
#include "VecSim/utils/data_block.h"
1010
#include "VecSim/vec_sim_index.h"
1111
#include "VecSim/spaces/spaces.h"
1212
#include "VecSim/utils/vecsim_stl.h"
@@ -29,20 +29,19 @@ template <typename DataType, typename DistType>
2929
class BruteForceIndex : public VecSimIndexAbstract<DistType> {
3030
protected:
3131
vecsim_stl::vector<labelType> idToLabelMapping;
32-
vecsim_stl::vector<VectorBlock *> vectorBlocks;
32+
vecsim_stl::vector<DataBlock> vectorBlocks;
3333
idType count;
3434

3535
public:
3636
BruteForceIndex(const BFParams *params, const AbstractIndexInitParams &abstractInitParams);
3737

3838
size_t indexSize() const override;
3939
size_t indexCapacity() const override;
40-
void increaseCapacity() override;
41-
vecsim_stl::vector<DistType> computeBlockScores(VectorBlock *block, const void *queryBlob,
40+
vecsim_stl::vector<DistType> computeBlockScores(const DataBlock &block, const void *queryBlob,
4241
void *timeoutCtx,
4342
VecSimQueryResult_Code *rc) const;
4443
inline DataType *getDataByInternalId(idType id) const {
45-
return (DataType *)vectorBlocks.at(id / this->blockSize)->getVector(id % this->blockSize);
44+
return (DataType *)vectorBlocks.at(id / this->blockSize).getElement(id % this->blockSize);
4645
}
4746
virtual VecSimQueryResult_List topKQuery(const void *queryBlob, size_t k,
4847
VecSimQueryParams *queryParams) const override;
@@ -56,7 +55,7 @@ class BruteForceIndex : public VecSimIndexAbstract<DistType> {
5655
bool preferAdHocSearch(size_t subsetSize, size_t k, bool initial_check) const override;
5756
inline labelType getVectorLabel(idType id) const { return idToLabelMapping.at(id); }
5857

59-
inline vecsim_stl::vector<VectorBlock *> getVectorBlocks() const { return vectorBlocks; }
58+
inline const vecsim_stl::vector<DataBlock> &getVectorBlocks() const { return vectorBlocks; }
6059
inline const labelType getLabelByInternalId(idType internal_id) const {
6160
return idToLabelMapping.at(internal_id);
6261
}
@@ -72,7 +71,7 @@ class BruteForceIndex : public VecSimIndexAbstract<DistType> {
7271
// without duplicates in tiered index). Caller should hold the flat buffer lock for read.
7372
virtual inline vecsim_stl::set<labelType> getLabelsSet() const = 0;
7473

75-
virtual ~BruteForceIndex();
74+
virtual ~BruteForceIndex() = default;
7675
#ifdef BUILD_TESTS
7776
/**
7877
* @brief Used for testing - store vector(s) data associated with a given label. This function
@@ -93,7 +92,30 @@ class BruteForceIndex : public VecSimIndexAbstract<DistType> {
9392
// Private internal function that implements generic single vector deletion.
9493
virtual void removeVector(idType id);
9594

96-
inline VectorBlock *getVectorVectorBlock(idType id) const {
95+
inline void growByBlock() {
96+
assert(vectorBlocks.size() == 0 || vectorBlocks.back().getLength() == this->blockSize);
97+
vectorBlocks.emplace_back(this->blockSize, this->dataSize, this->allocator,
98+
this->alignment);
99+
idToLabelMapping.resize(idToLabelMapping.size() + this->blockSize);
100+
idToLabelMapping.shrink_to_fit();
101+
resizeLabelLookup(idToLabelMapping.size());
102+
}
103+
104+
inline void shrinkByBlock() {
105+
assert(indexCapacity() > 0); // should not be called when index is empty
106+
107+
// remove last block (should be empty)
108+
assert(vectorBlocks.size() > 0 && vectorBlocks.back().getLength() == 0);
109+
vectorBlocks.pop_back();
110+
111+
// remove a block size of labels.
112+
assert(idToLabelMapping.size() >= this->blockSize);
113+
idToLabelMapping.resize(idToLabelMapping.size() - this->blockSize);
114+
idToLabelMapping.shrink_to_fit();
115+
resizeLabelLookup(idToLabelMapping.size());
116+
}
117+
118+
inline DataBlock &getVectorVectorBlock(idType id) {
97119
return vectorBlocks.at(id / this->blockSize);
98120
}
99121
inline size_t getVectorRelativeIndex(idType id) const { return id % this->blockSize; }
@@ -111,6 +133,7 @@ class BruteForceIndex : public VecSimIndexAbstract<DistType> {
111133
// inline label to id setters that need to be implemented by derived class
112134
virtual inline void replaceIdOfLabel(labelType label, idType new_id, idType old_id) = 0;
113135
virtual inline void setVectorId(labelType label, idType id) = 0;
136+
virtual inline void resizeLabelLookup(size_t new_max_elements) = 0;
114137

115138
virtual inline VecSimBatchIterator *
116139
newBatchIterator_Instance(void *queryBlob, VecSimQueryParams *queryParams) const = 0;
@@ -129,41 +152,35 @@ BruteForceIndex<DataType, DistType>::BruteForceIndex(
129152
: VecSimIndexAbstract<DistType>(abstractInitParams), idToLabelMapping(this->allocator),
130153
vectorBlocks(this->allocator), count(0) {
131154
assert(VecSimType_sizeof(this->vecType) == sizeof(DataType));
132-
this->idToLabelMapping.resize(params->initialCapacity);
133-
}
134-
135-
template <typename DataType, typename DistType>
136-
BruteForceIndex<DataType, DistType>::~BruteForceIndex() {
137-
for (auto &vectorBlock : this->vectorBlocks) {
138-
delete vectorBlock;
139-
}
155+
// Round up the initial capacity to the nearest multiple of the block size.
156+
size_t initialCapacity = RoundUpInitialCapacity(params->initialCapacity, this->blockSize);
157+
this->idToLabelMapping.resize(initialCapacity);
158+
this->vectorBlocks.reserve(initialCapacity / this->blockSize);
140159
}
141160

142161
/******************** Implementation **************/
143162

144163
template <typename DataType, typename DistType>
145164
void BruteForceIndex<DataType, DistType>::appendVector(const void *vector_data, labelType label) {
146-
assert(indexCapacity() > indexSize());
147165
// Give the vector new id and increase count.
148166
idType id = this->count++;
149167

150-
// Get the last vectors block to store the vector in (we assume that it's not full yet).
151-
VectorBlock *vectorBlock = this->vectorBlocks.back();
152-
assert(vectorBlock == getVectorVectorBlock(id));
153-
154-
// add vector data to vectorBlock
155-
vectorBlock->addVector(vector_data);
168+
// Resize the index if needed.
169+
if (indexSize() > indexCapacity()) {
170+
growByBlock();
171+
} else if (id % this->blockSize == 0) {
172+
// If we we didn't reach the initial capacity but the last block is full, add a new block
173+
// only.
174+
this->vectorBlocks.emplace_back(this->blockSize, this->dataSize, this->allocator,
175+
this->alignment);
176+
}
156177

157-
// if idToLabelMapping is full,
158-
// resize and align idToLabelMapping by blockSize
159-
size_t idToLabelMapping_size = this->idToLabelMapping.size();
178+
// Get the last vectors block to store the vector in.
179+
DataBlock &vectorBlock = this->vectorBlocks.back();
180+
assert(&vectorBlock == &getVectorVectorBlock(id));
160181

161-
if (id >= idToLabelMapping_size) {
162-
size_t last_block_vectors_count = id % this->blockSize;
163-
this->idToLabelMapping.resize(
164-
idToLabelMapping_size + this->blockSize - last_block_vectors_count, 0);
165-
this->idToLabelMapping.shrink_to_fit();
166-
}
182+
// add vector data to vectorBlock
183+
vectorBlock.addElement(vector_data);
167184

168185
// add label to idToLabelMapping
169186
setVectorLabel(id, label);
@@ -180,10 +197,10 @@ void BruteForceIndex<DataType, DistType>::removeVector(idType id_to_delete) {
180197
labelType last_idx_label = getVectorLabel(last_idx);
181198

182199
// Get last vector data.
183-
VectorBlock *last_vector_block = vectorBlocks.back();
184-
assert(last_vector_block == getVectorVectorBlock(last_idx));
200+
DataBlock &last_vector_block = vectorBlocks.back();
201+
assert(&last_vector_block == &getVectorVectorBlock(last_idx));
185202

186-
void *last_vector_data = last_vector_block->removeAndFetchLastVector();
203+
void *last_vector_data = last_vector_block.removeAndFetchLastElement();
187204

188205
// If we are *not* trying to remove the last vector, update mapping and move
189206
// the data of the last vector in the index in place of the deleted vector.
@@ -198,27 +215,16 @@ void BruteForceIndex<DataType, DistType>::removeVector(idType id_to_delete) {
198215
replaceIdOfLabel(last_idx_label, id_to_delete, last_idx);
199216

200217
// Get the vectorBlock and the relative index of the deleted id.
201-
VectorBlock *deleted_vectorBlock = getVectorVectorBlock(id_to_delete);
218+
DataBlock &deleted_vectorBlock = getVectorVectorBlock(id_to_delete);
202219
size_t id_to_delete_rel_idx = getVectorRelativeIndex(id_to_delete);
203220

204221
// Put data of last vector inplace of the deleted vector.
205-
deleted_vectorBlock->updateVector(id_to_delete_rel_idx, last_vector_data);
222+
deleted_vectorBlock.updateElement(id_to_delete_rel_idx, last_vector_data);
206223
}
207224

208225
// If the last vector block is emtpy.
209-
if (last_vector_block->getLength() == 0) {
210-
delete last_vector_block;
211-
this->vectorBlocks.pop_back();
212-
213-
// Resize and align the idToLabelMapping.
214-
size_t idToLabel_size = idToLabelMapping.size();
215-
// If the new size is smaller by at least one block comparing to the idToLabelMapping
216-
// align to be a multiplication of block size and resize by one block.
217-
if (this->count + this->blockSize <= idToLabel_size) {
218-
size_t vector_to_align_count = idToLabel_size % this->blockSize;
219-
this->idToLabelMapping.resize(idToLabel_size - this->blockSize - vector_to_align_count);
220-
this->idToLabelMapping.shrink_to_fit();
221-
}
226+
if (last_vector_block.getLength() == 0) {
227+
shrinkByBlock();
222228
}
223229
}
224230

@@ -229,29 +235,23 @@ size_t BruteForceIndex<DataType, DistType>::indexSize() const {
229235

230236
template <typename DataType, typename DistType>
231237
size_t BruteForceIndex<DataType, DistType>::indexCapacity() const {
232-
return this->blockSize * this->vectorBlocks.size();
233-
}
234-
235-
template <typename DataType, typename DistType>
236-
void BruteForceIndex<DataType, DistType>::increaseCapacity() {
237-
size_t vector_bytes_count = this->dim * VecSimType_sizeof(this->vecType);
238-
auto *new_vector_block =
239-
new (this->allocator) VectorBlock(this->blockSize, vector_bytes_count, this->allocator);
240-
this->vectorBlocks.push_back(new_vector_block);
238+
return this->idToLabelMapping.size();
241239
}
242240

243241
// Compute the score for every vector in the block by using the given distance function.
244242
template <typename DataType, typename DistType>
245-
vecsim_stl::vector<DistType> BruteForceIndex<DataType, DistType>::computeBlockScores(
246-
VectorBlock *block, const void *queryBlob, void *timeoutCtx, VecSimQueryResult_Code *rc) const {
247-
size_t len = block->getLength();
243+
vecsim_stl::vector<DistType>
244+
BruteForceIndex<DataType, DistType>::computeBlockScores(const DataBlock &block,
245+
const void *queryBlob, void *timeoutCtx,
246+
VecSimQueryResult_Code *rc) const {
247+
size_t len = block.getLength();
248248
vecsim_stl::vector<DistType> scores(len, this->allocator);
249249
for (size_t i = 0; i < len; i++) {
250250
if (VECSIM_TIMEOUT(timeoutCtx)) {
251251
*rc = VecSim_QueryResult_TimedOut;
252252
return scores;
253253
}
254-
scores[i] = this->dist_func(block->getVector(i), queryBlob, this->dim);
254+
scores[i] = this->distFunc(block.getElement(i), queryBlob, this->dim);
255255
}
256256
*rc = VecSim_QueryResult_OK;
257257
return scores;
@@ -264,7 +264,7 @@ BruteForceIndex<DataType, DistType>::topKQuery(const void *queryBlob, size_t k,
264264

265265
VecSimQueryResult_List rl = {0};
266266
void *timeoutCtx = queryParams ? queryParams->timeoutCtx : NULL;
267-
this->last_mode = STANDARD_KNN;
267+
this->lastMode = STANDARD_KNN;
268268

269269
if (0 == k) {
270270
rl.results = array_new<VecSimQueryResult>(0);
@@ -276,7 +276,7 @@ BruteForceIndex<DataType, DistType>::topKQuery(const void *queryBlob, size_t k,
276276
getNewMaxPriorityQueue();
277277
// For every block, compute its vectors scores and update the Top candidates max heap
278278
idType curr_id = 0;
279-
for (auto vectorBlock : this->vectorBlocks) {
279+
for (auto &vectorBlock : this->vectorBlocks) {
280280
auto scores = computeBlockScores(vectorBlock, queryBlob, timeoutCtx, &rl.code);
281281
if (VecSim_OK != rl.code) {
282282
delete TopCandidates;
@@ -314,7 +314,7 @@ BruteForceIndex<DataType, DistType>::rangeQuery(const void *queryBlob, double ra
314314
VecSimQueryParams *queryParams) const {
315315
auto rl = (VecSimQueryResult_List){0};
316316
void *timeoutCtx = queryParams ? queryParams->timeoutCtx : nullptr;
317-
this->last_mode = RANGE_QUERY;
317+
this->lastMode = RANGE_QUERY;
318318

319319
// Compute scores in every block and save results that are within the range.
320320
auto res_container =
@@ -323,7 +323,7 @@ BruteForceIndex<DataType, DistType>::rangeQuery(const void *queryBlob, double ra
323323
DistType radius_ = DistType(radius);
324324
idType curr_id = 0;
325325
rl.code = VecSim_QueryResult_OK;
326-
for (auto vectorBlock : this->vectorBlocks) {
326+
for (auto &vectorBlock : this->vectorBlocks) {
327327
auto scores = computeBlockScores(vectorBlock, queryBlob, timeoutCtx, &rl.code);
328328
if (VecSim_OK != rl.code) {
329329
break;
@@ -458,7 +458,7 @@ bool BruteForceIndex<DataType, DistType>::preferAdHocSearch(size_t subsetSize, s
458458
}
459459
}
460460
// Set the mode - if this isn't the initial check, we switched mode form batches to ad-hoc.
461-
this->last_mode =
461+
this->lastMode =
462462
res ? (initial_check ? HYBRID_ADHOC_BF : HYBRID_BATCHES_TO_ADHOC_BF) : HYBRID_BATCHES;
463463
return res;
464464
}

src/VecSim/algorithms/brute_force/brute_force_friend_tests.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ INDEX_TEST_FRIEND_CLASS(BruteForceTest_test_delete_swap_block_Test)
1717
INDEX_TEST_FRIEND_CLASS(BruteForceTest_test_dynamic_bf_info_iterator_Test)
1818
INDEX_TEST_FRIEND_CLASS(BruteForceTest_brute_force_zero_minimal_capacity_Test)
1919
INDEX_TEST_FRIEND_CLASS(BruteForceTest_preferAdHocOptimization_Test)
20+
INDEX_TEST_FRIEND_CLASS(IndexAllocatorTest_test_bf_index_block_size_1_Test)
2021
INDEX_TEST_FRIEND_CLASS(BM_VecSimBasics)

src/VecSim/algorithms/brute_force/brute_force_multi.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ class BruteForceIndex_Multi : public BruteForceIndex<DataType, DistType> {
5656

5757
inline void replaceIdOfLabel(labelType label, idType new_id, idType old_id) override;
5858

59+
inline void resizeLabelLookup(size_t new_max_elements) override {
60+
labelToIdsLookup.reserve(new_max_elements);
61+
}
62+
5963
inline bool isLabelExists(labelType label) override {
6064
return labelToIdsLookup.find(label) != labelToIdsLookup.end();
6165
}
@@ -198,7 +202,7 @@ double BruteForceIndex_Multi<DataType, DistType>::getDistanceFrom(labelType labe
198202

199203
DistType dist = std::numeric_limits<DistType>::infinity();
200204
for (auto id : IDs->second) {
201-
DistType d = this->dist_func(this->getDataByInternalId(id), vector_data, this->dim);
205+
DistType d = this->distFunc(this->getDataByInternalId(id), vector_data, this->dim);
202206
dist = (dist < d) ? dist : d;
203207
}
204208

0 commit comments

Comments
 (0)