Skip to content
6 changes: 3 additions & 3 deletions include/rabitqlib/fastscan/fastscan.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static inline void get_column(
}

/**
* @brief Pack quantization codes, store in blocks, the data orgnization is illustrated in
* @brief Pack quantization codes into blocks; the data organization is illustrated in
* the link and kPerm0. Since we pack codes as 32-sized groups, if the num is not a multiple
* of 32, we have to use some space for these absent data
*
Expand All @@ -97,8 +97,8 @@ inline void pack_codes(
// each batch contain codes for 32 vectors
for (size_t row = 0; row < num_rd; row += kBatchSize) {
// get quantization codes for each column for each batch
// i.e., we get the codes for 8 dims of 32 vectors and re-orgnize the data layout
// based on the shuffle SIMD instruction used during quering
// i.e., we get the codes for 8 dims of 32 vectors and reorganize the data layout
// based on the shuffle SIMD instruction used during querying
for (size_t i = 0; i < cols; ++i) {
get_column(quantization_code, num, cols, row, i, col);
for (size_t j = 0; j < 32; ++j) {
Expand Down
9 changes: 3 additions & 6 deletions include/rabitqlib/index/estimator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ inline void split_batch_estdist(
) {
constexpr size_t kSafeChunkDim = 1024;
ConstBatchDataMap<float> cur_batch(batch_data, padded_dim);
RowMajorArray<int32_t> accu_arr(1, fastscan::kBatchSize);
std::array<int32_t, fastscan::kBatchSize> accu_values{};
RowMajorArrayMap<int32_t> accu_arr(accu_values.data(), 1, fastscan::kBatchSize);
const auto* codes_ptr = cur_batch.bin_code();
const auto* lut_ptr = q_obj.lut();
for (size_t i = 0; i < fastscan::kBatchSize; ++i) {
accu_arr.data()[i] = 0;
}

if (use_hacc) {
std::array<int32_t, fastscan::kBatchSize> accu_res;
size_t remaining_dim = padded_dim;
Expand Down Expand Up @@ -165,7 +162,7 @@ template <typename T, typename TA = uint16_t>
inline void qg_batch_estdist(
const char* batch_data, const BatchQuery<T>& q_obj, size_t padded_dim, T* est_distance
) {
std::vector<TA> accu_res(fastscan::kBatchSize);
std::array<TA, fastscan::kBatchSize> accu_res{};

ConstQGBatchDataMap<T> cur_batch(batch_data, padded_dim);

Expand Down
4 changes: 2 additions & 2 deletions include/rabitqlib/index/ivf/initializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ inline void parallel_for(size_t start, size_t end, size_t numThreads, Function f
}

/**
* @brief For ivf centroids, we need an intializer to get the candidate clusters.
* @brief For IVF centroids, an initializer finds the candidate clusters.
*/
class Initializer {
protected:
Expand Down Expand Up @@ -198,4 +198,4 @@ class HNSWInitializer : public Initializer {

~HNSWInitializer() override { delete alg_hnsw_; }
};
} // namespace rabitqlib::ivf
} // namespace rabitqlib::ivf
68 changes: 39 additions & 29 deletions include/rabitqlib/index/ivf/ivf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <cstddef>
#include <fstream>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>

#include "rabitqlib/defines.hpp"
Expand All @@ -27,18 +29,18 @@
namespace rabitqlib::ivf {
class IVF {
private:
Initializer* initer_ = nullptr; // initializer for find candidate cluster
char* batch_data_ = nullptr; // 1-bit code and factors
char* ex_data_ = nullptr; // code for remaining bits
PID* ids_ = nullptr; // PID of vectors (orgnized by clusters)
size_t num_; // num of data points
size_t dim_; // dimension of data points
size_t padded_dim_; // dimension after padding,
size_t num_cluster_; // num of centroids (clusters)
size_t ex_bits_; // total bits = ex_bits_ + 1
RotatorType type_; // type of rotator
Rotator<float>* rotator_ = nullptr; // Data Rotator
std::vector<Cluster> cluster_lst_; // List of clusters in ivf
std::unique_ptr<Initializer> initer_; // initializer for candidate clusters
char* batch_data_ = nullptr; // 1-bit code and factors
char* ex_data_ = nullptr; // code for remaining bits
PID* ids_ = nullptr; // PID of vectors (organized by clusters)
size_t num_ = 0; // num of data points
size_t dim_ = 0; // dimension of data points
size_t padded_dim_ = 0; // dimension after padding
size_t num_cluster_ = 0; // num of centroids (clusters)
size_t ex_bits_ = 0; // total bits = ex_bits_ + 1
RotatorType type_ = RotatorType::FhtKacRotator; // type of rotator
Rotator<float>* rotator_ = nullptr; // Data Rotator
std::vector<Cluster> cluster_lst_; // List of clusters in ivf
MetricType metric_type_ = rabitqlib::METRIC_L2; // metric type
float (*ip_func_)(const float*, const uint8_t*, size_t) = nullptr;

Expand Down Expand Up @@ -66,10 +68,10 @@ class IVF {
void init_clusters(const std::vector<size_t>&);

void free_memory() {
::delete initer_;
std::free(batch_data_);
std::free(ex_data_);
std::free(ids_);
initer_.reset();
std::free(std::exchange(batch_data_, nullptr));
std::free(std::exchange(ex_data_, nullptr));
std::free(std::exchange(ids_, nullptr));
}

void search_cluster(
Expand All @@ -87,7 +89,7 @@ class IVF {
) const;

public:
explicit IVF() {}
explicit IVF() = default;
explicit IVF(
size_t,
size_t,
Expand Down Expand Up @@ -158,7 +160,7 @@ inline IVF::~IVF() {
*
* @param data Data objects (N*DIM)
* @param centroids Centroid vectors (K*DIM)
* @param clustter_ids Cluster ID for each data objects
* @param cluster_ids Cluster ID for each data object
*/
inline void IVF::construct(
const float* data,
Expand Down Expand Up @@ -196,7 +198,7 @@ inline void IVF::construct(
config = quant::faster_config(padded_dim_, ex_bits_ + 1);
}

num_threads = std::min(num_threads, rabitqlib::total_threads());
num_threads = std::clamp(num_threads, size_t{1}, rabitqlib::total_threads());
/* Quantize each cluster */
#pragma omp parallel for schedule(dynamic) num_threads(num_threads)
for (size_t i = 0; i < num_cluster_; ++i) {
Expand All @@ -211,10 +213,13 @@ inline void IVF::construct(

inline void IVF::allocate_memory(const std::vector<size_t>& cluster_sizes) {
std::cout << "Allocating memory for IVF...\n";
free_memory();
cluster_lst_.clear();

if (num_cluster_ < 20000UL) {
this->initer_ = new FlatInitializer(padded_dim_, num_cluster_);
this->initer_ = std::make_unique<FlatInitializer>(padded_dim_, num_cluster_);
} else {
this->initer_ = new HNSWInitializer(padded_dim_, num_cluster_);
this->initer_ = std::make_unique<HNSWInitializer>(padded_dim_, num_cluster_);
}
this->batch_data_ =
memory::align_allocate<64, char, true>(batch_data_bytes(cluster_sizes));
Expand All @@ -227,7 +232,7 @@ inline void IVF::allocate_memory(const std::vector<size_t>& cluster_sizes) {
}

/**
* @brief intialize the cluster list: finding idx for all data
* @brief Initialize the cluster list by finding each cluster's storage offsets.
*/
inline void IVF::init_clusters(const std::vector<size_t>& cluster_sizes) {
this->cluster_lst_.reserve(num_cluster_);
Expand All @@ -241,8 +246,9 @@ inline void IVF::init_clusters(const std::vector<size_t>& cluster_sizes) {
char* current_batch_data =
batch_data_ + (BatchDataMap<float>::data_bytes(padded_dim_) * added_batches);
char* current_ex_data =
ex_data_ +
(added_vectors * ExDataMap<float>::data_bytes(padded_dim_, ex_bits_));
ex_bits_ > 0 ? ex_data_ + (added_vectors *
ExDataMap<float>::data_bytes(padded_dim_, ex_bits_))
: nullptr;
PID* ids = ids_ + added_vectors;

Cluster cur_cluster(num, current_batch_data, current_ex_data, ids);
Expand All @@ -263,7 +269,7 @@ inline void IVF::quantize_cluster(
) {
size_t num_points = IDs.size();
if (cp.num() != num_points) {
std::cerr << "Size of cluster and IDs are inequivalent\n";
std::cerr << "Cluster size and ID count differ\n";
std::cerr << "Cluster: " << cp.num() << " IDs: " << num_points << '\n';
exit(1);
}
Expand Down Expand Up @@ -298,7 +304,9 @@ inline void IVF::quantize_cluster(
);

batch_data += BatchDataMap<float>::data_bytes(padded_dim_);
ex_data += ExDataMap<float>::data_bytes(padded_dim_, ex_bits_) * n;
if (ex_bits_ > 0) {
ex_data += ExDataMap<float>::data_bytes(padded_dim_, ex_bits_) * n;
}
}
}

Expand Down Expand Up @@ -360,6 +368,7 @@ inline void IVF::load(const char* filename) {
input.read(reinterpret_cast<char*>(&type_), sizeof(type_));
input.read(reinterpret_cast<char*>(&metric_type_), sizeof(metric_type_));

delete rotator_;
rotator_ = choose_rotator<float>(dim_, type_, round_up_to_multiple(dim_, 64));
padded_dim_ = rotator_->size();

Expand All @@ -381,7 +390,6 @@ inline void IVF::load(const char* filename) {
this->rotator_->load(input);

/* Load data */
free_memory();
allocate_memory(cluster_sizes);
this->initer_->load(input, filename);
input.read(batch_data_, static_cast<long>(batch_data_bytes(cluster_sizes)));
Expand Down Expand Up @@ -476,8 +484,10 @@ inline void IVF::search_cluster(
);

batch_data += BatchDataMap<float>::data_bytes(padded_dim_);
ex_data +=
ExDataMap<float>::data_bytes(padded_dim_, ex_bits_) * fastscan::kBatchSize;
if (ex_bits_ > 0) {
ex_data +=
ExDataMap<float>::data_bytes(padded_dim_, ex_bits_) * fastscan::kBatchSize;
}
ids += fastscan::kBatchSize;
}

Expand Down
4 changes: 2 additions & 2 deletions include/rabitqlib/index/lut.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ template <typename T>
class Lut {
static constexpr size_t kNumBits = 8;
static constexpr size_t kNumBitsHacc = 16;
static_assert(std::is_floating_point_v<T>, "T must be an floating type in Lut");
static_assert(std::is_floating_point_v<T>, "T must be a floating-point type in Lut");

private:
size_t table_length_ = 0;
Expand Down Expand Up @@ -62,4 +62,4 @@ class Lut {
[[nodiscard]] T delta() const { return delta_; };
[[nodiscard]] T sum_vl() const { return sum_vl_lut_; };
};
} // namespace rabitqlib
} // namespace rabitqlib
4 changes: 2 additions & 2 deletions include/rabitqlib/index/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class BatchQuery {
[[nodiscard]] T g_add() const { return G_add_; }

void set_g_add(T dist) {
// For L2, dist is the compute by euclidean_sqr()
// For IP, dist is computed by dot_product_dist() i.e., 1 - dot_product()
// For L2, dist is computed by euclidean_sqr().
// For IP, dist is computed by dot_product_dis(), i.e. 1 - dot_product().
G_add_ = dist;
}

Expand Down
30 changes: 27 additions & 3 deletions include/rabitqlib/index/symqg/qg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <fstream>
#include <iostream>
#include <ostream>
#include <stdexcept>
#include <vector>

#include "rabitqlib/defines.hpp"
Expand Down Expand Up @@ -62,6 +63,8 @@ class QuantizedGraph {
size_t row_offset_ = 0; // length of entire row
size_t ef_ = 0;

void validate_configuration() const;

void initialize();

void copy_vectors(const T*);
Expand Down Expand Up @@ -157,12 +160,32 @@ inline QuantizedGraph<T>::QuantizedGraph(
, raw_dist_func_((metric_type == METRIC_IP) ? dot_product_dis<T> : euclidean_sqr<T>)
, metric_type_(metric_type)
, rotator_type_(rotator_type) {
validate_configuration();
initialize();
}

template <typename T>
inline void QuantizedGraph<T>::validate_configuration() const {
if (degree_bound_ == 0 || degree_bound_ % fastscan::kBatchSize != 0) {
throw std::invalid_argument(
"QuantizedGraph degree bound must be a positive multiple of 32"
);
}
if (degree_bound_ >= num_points_) {
throw std::invalid_argument(
"QuantizedGraph degree bound must be smaller than the number of points"
);
}
if (num_points_ > buffer::kSearchBufferMaxPointCount) {
throw std::invalid_argument(
"QuantizedGraph point count exceeds the search-buffer ID limit"
);
}
}

template <typename T>
inline QuantizedGraph<T>::~QuantizedGraph() {
::delete this->rotator_;
delete this->rotator_;
}

template <typename T>
Expand Down Expand Up @@ -225,6 +248,7 @@ inline void QuantizedGraph<T>::load(const char* filename) {

raw_dist_func_ = (metric_type_ == METRIC_IP) ? dot_product_dis<T> : euclidean_sqr<T>;

validate_configuration();
initialize();

/* Data */
Expand Down Expand Up @@ -335,7 +359,7 @@ inline void QuantizedGraph<T>::search(
}

// scan a data row (including data vec and quantization codes for its neighbors)
// store estimated distance & return exact distnace for current vertex
// Store estimated neighbor distances; the caller computes the current vertex exactly.
template <typename T>
void QuantizedGraph<T>::scan_neighbors(
const BatchQuery<T>& q_obj,
Expand Down Expand Up @@ -395,7 +419,7 @@ inline void QuantizedGraph<T>::update_results(
// initialize const offsets & data array
template <typename T>
inline void QuantizedGraph<T>::initialize() {
::delete rotator_;
delete rotator_;

rotator_ = choose_rotator<float>(dim_, rotator_type_, round_up_to_multiple(dim_, 64));
padded_dim_ = rotator_->size();
Expand Down
4 changes: 2 additions & 2 deletions include/rabitqlib/index/symqg/qg_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class QGBuilder {
)
: qg_{index}
, ef_build_{ef_build}
, num_threads_{std::min(num_threads, total_threads())}
, num_threads_{std::max<size_t>(1, std::min(num_threads, total_threads()))}
, num_nodes_{qg_.num_vertices()}
, dim_{qg_.dimension()}
, degree_bound_(qg_.degree_bound())
Expand Down Expand Up @@ -88,7 +88,7 @@ class QGBuilder {

void build(size_t num_iter = 3) {
if (num_iter < 2) {
std::cerr << "The number of iter for building qg should >= 3\n";
std::cerr << "The number of iterations for building QG must be at least 2\n";
exit(1);
}
// for first iterations, we do not need to refine the graph structure
Expand Down
Loading
Loading