Skip to content

Commit

Permalink
Add sparse index support to knowhere with linscan and wand algorithm;…
Browse files Browse the repository at this point in the history
… added related unit test

Signed-off-by: Buqian Zheng <zhengbuqian@gmail.com>
  • Loading branch information
zhengbuqian committed Jan 17, 2024
1 parent 5230b6a commit 950a375
Show file tree
Hide file tree
Showing 12 changed files with 1,377 additions and 2 deletions.
9 changes: 9 additions & 0 deletions include/knowhere/comp/brute_force.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ class BruteForce {
static expected<DataSetPtr>
RangeSearch(const DataSetPtr base_dataset, const DataSetPtr query_dataset, const Json& config,
const BitsetView& bitset);

// Perform row oriented sparse vector brute force search.
static expected<DataSetPtr>
SearchSparse(const DataSetPtr base_dataset, const DataSetPtr query_dataset, const Json& config,
const BitsetView& bitset);

static Status
SearchSparseWithBuf(const DataSetPtr base_dataset, const DataSetPtr query_dataset, sparse::label_t* ids, float* dis,
const Json& config, const BitsetView& bitset);
};

} // namespace knowhere
Expand Down
6 changes: 6 additions & 0 deletions include/knowhere/comp/index_param.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ constexpr const char* INDEX_RAFT_CAGRA = "GPU_RAFT_CAGRA";
constexpr const char* INDEX_HNSW = "HNSW";
constexpr const char* INDEX_DISKANN = "DISKANN";

constexpr const char* INDEX_SPARSE_INVERTED_INDEX = "SPARSE_INVERTED_INDEX";
constexpr const char* INDEX_SPARSE_WAND = "SPARSE_WAND";
} // namespace IndexEnum

namespace meta {
Expand Down Expand Up @@ -122,6 +124,10 @@ constexpr const char* HNSW_M = "M";
constexpr const char* EF = "ef";
constexpr const char* SEED_EF = "seed_ef";
constexpr const char* OVERVIEW_LEVELS = "overview_levels";

// Sparse Params
constexpr const char* DROP_RATIO_BUILD = "drop_ratio_build";
constexpr const char* DROP_RATIO_SEARCH = "drop_ratio_search";
} // namespace indexparam

using MetricType = std::string;
Expand Down
20 changes: 19 additions & 1 deletion include/knowhere/dataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
#include <shared_mutex>
#include <utility>
#include <variant>
#include <vector>

#include "comp/index_param.h"
#include "knowhere/sparse_utils.h"

namespace knowhere {

Expand Down Expand Up @@ -54,7 +56,11 @@ class DataSet : public std::enable_shared_from_this<const DataSet> {
{
auto ptr = std::get_if<3>(&x.second);
if (ptr != nullptr) {
delete[](char*)(*ptr);
if (is_sparse) {
delete[](sparse::SparseRow<float>*)(*ptr);
} else {
delete[](char*)(*ptr);
}
}
}
}
Expand All @@ -78,6 +84,11 @@ class DataSet : public std::enable_shared_from_this<const DataSet> {
this->data_[meta::IDS] = Var(std::in_place_index<2>, ids);
}

/**
* For dense float vector, tensor is a rows * dim float array
* For sparse float vector, tensor is pointer to sparse::Sparse<float>*
* and values in each row should be sorted by column id.
*/
void
SetTensor(const void* tensor) {
std::unique_lock lock(mutex_);
Expand Down Expand Up @@ -202,6 +213,12 @@ class DataSet : public std::enable_shared_from_this<const DataSet> {
this->is_owner = is_owner;
}

void
SetIsSparse(bool is_sparse) {
std::unique_lock lock(mutex_);
this->is_sparse = is_sparse;
}

// deprecated API
template <typename T>
void
Expand All @@ -225,6 +242,7 @@ class DataSet : public std::enable_shared_from_this<const DataSet> {
mutable std::shared_mutex mutex_;
std::map<std::string, Var> data_;
bool is_owner = true;
bool is_sparse = false;
};
using DataSetPtr = std::shared_ptr<DataSet>;
inline DataSetPtr
Expand Down
1 change: 1 addition & 0 deletions include/knowhere/operands.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#ifndef OPERANDS_H
#define OPERANDS_H
#include <math.h>
#include <cstring>

namespace {
union fp32_bits {
Expand Down
225 changes: 225 additions & 0 deletions include/knowhere/sparse_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright (C) 2019-2023 Zilliz. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software valributed under the License
// is valributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under the License.

#pragma once

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>

#include "knowhere/operands.h"

namespace knowhere::sparse {

// integer type in SparseRow
using table_t = uint32_t;
// type used to represent the id of a vector in the index interface.
// this is same as other index types.
using label_t = int64_t;

template <typename T>
struct IdVal {
table_t id;
T val;

IdVal() = default;
IdVal(table_t id, T val) : id(id), val(val) {
}

inline friend bool
operator<(const IdVal& lhs, const IdVal& rhs) {
return lhs.val < rhs.val || (lhs.val == rhs.val && lhs.id < rhs.id);
}
inline friend bool
operator>(const IdVal& lhs, const IdVal& rhs) {
return !(lhs < rhs);
}

inline friend bool
operator==(const IdVal& lhs, const IdVal& rhs) {
return lhs.id == rhs.id && lhs.val == rhs.val;
}
};

template <typename T>
class SparseRow {
static_assert(std::is_same_v<T, fp32>, "SparseRow supports float only");
public:
SparseRow() = default;

SparseRow(const SparseRow& other) {
copy_from(other);
}

SparseRow&
operator=(const SparseRow& other) {
copy_from(other);
return *this;
}

~SparseRow() {
if (own_data && data != nullptr) {
delete[] data;
}
}

size_t
size() const {
return count;
}

size_t
byte_size() const {
return count * (sizeof(table_t) + sizeof(T)) + sizeof(*this);
}

void*
data_ptr() const {
return data;
}

// dim of a sparse vector is the max index + 1, or 0 for an empty vector.
int64_t
dim() const {
if (count == 0)
return 0;
return (*this)[count - 1].id + 1;
}

IdVal<T>
operator[](size_t i) const {
table_t index = *(table_t*)(data + i * (sizeof(table_t) + sizeof(T)));
T value = *(T*)(data + i * (sizeof(table_t) + sizeof(T)) + sizeof(table_t));
return {index, value};
}

void
set_at(size_t i, table_t index, T value) {
*(table_t*)(data + i * (sizeof(table_t) + sizeof(T))) = index;
*(T*)(data + i * (sizeof(table_t) + sizeof(T)) + sizeof(table_t)) = value;
}

float
dot(const SparseRow<T>& other) const {
float product_sum = 0.0f;
size_t i = 0, j = 0;
while (i < count && j < other.count) {
auto [index1, value1] = (*this)[i];
auto [index2, value2] = other[j];

if (index1 < index2) {
++i;
} else if (index1 > index2) {
++j;
} else {
product_sum += value1 * value2;
++i;
++j;
}
}
return product_sum;
}

void
reserve(size_t count) {
if (own_data && data != nullptr) {
delete[] data;
}
data = new uint8_t[count * (sizeof(table_t) + sizeof(T))];
own_data = true;
this->count = count;
}

// performs deep copy of data
void
copy_from(const SparseRow<T>& other) {
reserve(other.count);
memcpy(data, other.data, count * (sizeof(table_t) + sizeof(T)));
}

private:
// data must be sorted by column id. use raw pointer for easy mmap and zero
// copy.
uint8_t* data = nullptr;
size_t count = 0;
bool own_data = false;
};

// When pushing new elements into a MaxMinHeap, only `capacity` elements with the
// largest val are kept. pop()/top() returns the smallest element out of them.
template <typename T>
class MaxMinHeap {
public:
explicit MaxMinHeap(int capacity) : capacity_(capacity), pool_(capacity) {
}
void
push(table_t id, T val) {
if (size_ < capacity_) {
pool_[size_] = {id, val};
size_ += 1;
std::push_heap(pool_.begin(), pool_.begin() + size_, std::greater<IdVal<T>>());
} else if (val > pool_[0].val) {
sift_down(id, val);
}
}
table_t
pop() {
std::pop_heap(pool_.begin(), pool_.begin() + size_, std::greater<IdVal<T>>());
size_ -= 1;
return pool_[size_].id;
}
[[nodiscard]] size_t
size() const {
return size_;
}
[[nodiscard]] bool
empty() const {
return size() == 0;
}
IdVal<T>
top() const {
return pool_[0];
}
[[nodiscard]] bool
full() const {
return size_ == capacity_;
}

private:
void
sift_down(table_t id, T val) {
size_t i = 0;
for (; 2 * i + 1 < size_;) {
size_t j = i;
size_t l = 2 * i + 1, r = 2 * i + 2;
if (pool_[l].val < val) {
j = l;
}
if (r < size_ && pool_[r].val < std::min(pool_[l].val, val)) {
j = r;
}
if (i == j) {
break;
}
pool_[i] = pool_[j];
i = j;
}
pool_[i] = {id, val};
}

size_t size_ = 0, capacity_;
std::vector<IdVal<T>> pool_;
}; // class MaxMinHeap

} // namespace knowhere::sparse
Loading

0 comments on commit 950a375

Please sign in to comment.