chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:42 +08:00
commit be3ef883e1
1214 changed files with 431743 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
include(${PROJECT_ROOT_DIR}/cmake/bazel.cmake)
include(${PROJECT_ROOT_DIR}/cmake/option.cmake)
cc_binary(
NAME txt2vecs
STRICT PACKED
SRCS txt2vecs.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags core_framework zvec_ailego
)
cc_binary(
NAME local_builder
STRICT PACKED
SRCS local_builder.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags yaml-cpp magic_enum core_framework core_metric core_quantizer core_utility core_knn_flat core_knn_flat_sparse core_knn_hnsw core_knn_hnsw_sparse core_knn_hnsw_rabitq core_knn_vamana core_knn_cluster core_knn_ivf core_interface core_knn_diskann
)
cc_binary(
NAME recall
STRICT PACKED
SRCS recall.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags yaml-cpp magic_enum core_framework core_metric core_quantizer core_utility core_knn_flat core_knn_flat_sparse core_knn_hnsw core_knn_hnsw_sparse core_knn_hnsw_rabitq core_knn_vamana core_knn_cluster core_knn_ivf roaring core_interface core_knn_diskann
)
cc_binary(
NAME bench
STRICT PACKED
SRCS bench.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags yaml-cpp magic_enum core_framework core_metric core_quantizer core_utility core_knn_flat core_knn_flat_sparse core_knn_hnsw core_knn_hnsw_sparse core_knn_hnsw_rabitq core_knn_vamana core_knn_cluster core_knn_ivf roaring core_interface core_knn_diskann
)
cc_binary(
NAME recall_original
STRICT PACKED
SRCS recall_original.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags yaml-cpp magic_enum core_framework core_metric core_quantizer core_utility core_knn_flat core_knn_flat_sparse core_knn_hnsw core_knn_hnsw_sparse core_knn_hnsw_rabitq core_knn_vamana core_knn_cluster core_knn_ivf roaring core_interface core_knn_diskann
)
cc_binary(
NAME bench_original
STRICT PACKED
SRCS bench_original.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags yaml-cpp magic_enum core_framework core_metric core_quantizer core_utility core_knn_flat core_knn_flat_sparse core_knn_hnsw core_knn_hnsw_sparse core_knn_hnsw_rabitq core_knn_vamana core_knn_cluster core_knn_ivf roaring core_interface core_knn_diskann
)
cc_binary(
NAME local_builder_original
STRICT PACKED
SRCS local_builder_original.cc
INCS ${PROJECT_ROOT_DIR}/src/core/
LIBS gflags yaml-cpp magic_enum core_framework core_metric core_quantizer core_utility core_knn_flat core_knn_flat_sparse core_knn_hnsw core_knn_hnsw_sparse core_knn_hnsw_rabitq core_knn_vamana core_knn_cluster core_knn_ivf core_interface core_knn_diskann
)
+150
View File
@@ -0,0 +1,150 @@
# Benchmarking scripts
This directory contains benchmarking scripts and reproducing steps.
## COHERE experiments
### Getting COHERE Data
Please download the COHERE 10M dataset to cohere_large_10m as follows:
```bash
... ...
neighbors.parquet
shuffle_train-00-of-10.parquet
shuffle_train-01-of-10.parquet
shuffle_train-02-of-10.parquet
shuffle_train-03-of-10.parquet
shuffle_train-04-of-10.parquet
shuffle_train-05-of-10.parquet
shuffle_train-06-of-10.parquet
shuffle_train-07-of-10.parquet
shuffle_train-08-of-10.parquet
shuffle_train-09-of-10.parquet
scalar_labels.parquet
test.parquet
```
For convenience, we prepared a docker image with cohere bench datasets: registry.cn-hongkong.cr.aliyuncs.com/zvec/cohere-bench-data.
You can run a container as follows:
```bash
docker run -it --net=host -d -e DEBUG_MODE=true --user root --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -v /home/zvec/:/home/zvec/ -w /home/zvec --name=cohere_bench zvec-registry.cn-hongkong.cr.aliyuncs.com/zvec/cohere-bench-data:0.0.1 bash
docker exec -it cohere_bench bash
```
The datasets locate at /tmp/cohere/
### Preparing Environment
Clone code and init:
```bash
$ git clone git@github.com:alibaba/zvec.git
$ cd zvec
$ git submodule update --init
```
Build source code:
```
$ cd /home/zvec/workspace/zvec
$ mkdir build
$ cd build
$ cmake -DENABLE_SKYLAKE=ON -DCMAKE_BUILD_TYPE=Release ..
```
### Converting Dataset
Export vector data using python script:
```bash
$ mkdir 10m.output
$ python3 convert_cohere_parquet.py
```
Convert vector data to binary formatted file.
```bash
/home/zvec/workspace/zvec/bin/txt2vecs -input=cohere_train_vector_10m.txt --output=cohere_train_vector_10m.zvec.vecs --dimension=768
```
We've also prepared preprocessed binary formatted files, which can be found in the container below:
```bash
root@iZj6caifjouj5yu8xgsiysZ:/home/zvec# ls -al /tmp/cohere/*zvec
/tmp/cohere/cohere_large_10m_zvec:
total 30204572
drwxr-xr-x 2 root root 4096 Feb 5 13:12 .
drwxr-xr-x 6 root root 4096 Feb 6 03:38 ..
-rw-r--r-- 1 root root 8664837 Feb 5 13:06 cohere_test_vector_10m.1000.new.txt
-rw-r--r-- 1 root root 30920004295 Feb 5 13:04 cohere_train_vector_10m.new.zvec.vecs
-rw-r--r-- 1 root root 792835 Feb 5 13:05 neighbors.txt
/tmp/cohere/cohere_medium_1m_zvec:
total 3028688
drwxr-xr-x 2 root root 4096 Feb 5 13:14 .
drwxr-xr-x 6 root root 4096 Feb 6 03:38 ..
-rw-r--r-- 1 root root 8661108 Feb 5 13:07 cohere_test_vector_1m.1000.new.txt
-rw-r--r-- 1 root root 3092004295 Feb 5 13:08 cohere_train_vector_1m.new.zvec.vecs
-rw-r--r-- 1 root root 692969 Feb 5 13:08 neighbors.txt
```
### Preparing Bench Config
Prepare Build Config
```yaml
BuilderCommon:
BuilderClass: HnswStreamer
BuildFile: /tmp/cohere/cohere_large_10m_zvec/cohere_train_vector_10m.zvec.vecs
NeedTrain: true
TrainFile: /tmp/cohere/cohere_large_10m_zvec/cohere_train_vector_10m.zvec.vecs
DumpPath: /home/zvec/bench/config/cohere_train_vector_10m.dump.index
IndexPath: /home/zvec/bench/config/cohere_train_vector_10m.index
ConverterName: CosineInt8Converter
MetricName: Cosine
ThreadCount: 16
BuilderParams:
proxima.general.builder.thread_count: !!int 16
proxima.hnsw.builder.thread_count: !!int 16
```
Prepare Search Config
```yaml
SearcherCommon:
SearcherClass: HnswStreamer
IndexPath: /home/zvec/bench/config/cohere_train_vector_10m.index
TopK: 1,10,50,100
QueryFile: /tmp/cohere/cohere_large_10m_zvec/cohere_test_vector_1000.new.txt
QueryType: float
QueryFirstSep: ";"
QuerySecondSep: " "
GroundTruthFile: /tmp/cohere/cohere_large_10m_zvec/neighbors.txt
RecallThreadCount: 1
BenchThreadCount: 16
BenchIterCount: 1000000000
CompareById: true
SearcherParams:
proxima.hnsw.streamer.ef: !!int 250
```
### Building Index
Conduct Build
```bash
$ /home/zvec/workspace/zvec/build/bin/local_build_original ./build.yaml
```
### Performing Bench
Conduct Recall
```bash
$ /home/zvec/workspace/zvec/build/bin/recall_original ./search.yaml
```
Conduct Bench
```bash
$ /home/zvec/workspace/zvec/build/bin/bench_original ./search.yaml
```
+911
View File
@@ -0,0 +1,911 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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.
#include "bench_result.h"
#include "helper.h"
static bool g_debug_mode = 0;
//------------------------------------------------------------
// Bench
//------------------------------------------------------------
enum RetrievalMode { RM_UNDEFINED = 0, RM_DENSE = 1, RM_SPARSE = 2 };
enum FilterMode { FM_UNDEFINED = 0, FM_NONE = 1, FM_TAG = 2 };
template <typename T>
class Bench {
public:
Bench(size_t threads, size_t bench_secs, size_t batch_count,
RetrievalMode &retrieval_mode, FilterMode filter_mode)
: threads_(threads),
bench_secs_(bench_secs),
batch_count_(batch_count),
retrieval_mode_{retrieval_mode},
filter_mode_{filter_mode} {
if (threads_ == 0) {
pool_ = make_shared<ThreadPool>(false);
threads_ = pool_->count();
cout << "Using cpu count as thread pool count[" << threads_ << "]"
<< endl;
} else {
pool_ = make_shared<ThreadPool>(threads_, false);
cout << "Using thread pool count[" << threads_ << "]" << endl;
}
if (batch_count_ < 1) {
batch_count_ = 1;
}
}
static void stop(int signo) {
if (STOP_NOW) {
exit(signo);
}
STOP_NOW = true;
cout << "\rTrying to stop. press [Ctrl+C] again kill immediately." << endl
<< flush;
}
bool load_query(const std::string &query_file, const std::string &first_sep,
const std::string &second_sep) {
TxtInputReader<T> reader;
vector<vector<T>> queries;
vector<SparseData<T>> sparse_data;
vector<vector<uint64_t>> taglists;
if (!reader.load_query(query_file, first_sep, second_sep, queries,
sparse_data, taglists)) {
LOG_ERROR("Load query error");
return false;
}
if (batch_count_ == 1) {
batch_queries_ = queries;
for (size_t i = 0; i < sparse_data.size(); ++i) {
vector<uint32_t> sparse_count;
sparse_count.push_back(sparse_data[i].count);
batch_sparse_counts_.push_back(sparse_count);
batch_sparse_indices_.push_back(sparse_data[i].indices);
batch_sparse_features_.push_back(sparse_data[i].features);
}
for (size_t i = 0; i < taglists.size(); ++i) {
vector<vector<uint64_t>> new_taglists;
new_taglists.push_back(taglists[i]);
batch_taglists_.push_back(std::move(new_taglists));
}
} else {
size_t num_batch = (queries.size() + batch_count_ - 1) / batch_count_;
size_t idx = 0;
for (size_t n = 0; n < num_batch; ++n) {
vector<T> batch_query;
vector<uint32_t> batch_sparse_count;
vector<uint32_t> batch_sparse_indices;
vector<T> batch_sparse_feature;
vector<vector<uint64_t>> batch_taglists;
for (size_t i = 0; i < batch_count_; ++i) {
for (size_t k = 0; k < queries[idx].size(); ++k) {
batch_query.push_back(queries[idx][k]);
}
batch_sparse_count.push_back(sparse_data[idx].count);
for (size_t k = 0; k < sparse_data[idx].indices.size(); ++k) {
batch_sparse_indices.push_back(sparse_data[idx].indices[k]);
}
for (size_t k = 0; k < sparse_data[idx].features.size(); ++k) {
batch_sparse_feature.push_back(sparse_data[idx].features[k]);
}
if (taglists.size() > idx) {
batch_taglists.push_back(taglists[idx]);
}
idx = (idx + 1) % queries.size();
}
batch_queries_.push_back(batch_query);
batch_sparse_counts_.push_back(batch_sparse_count);
batch_sparse_indices_.push_back(batch_sparse_indices);
batch_sparse_features_.push_back(batch_sparse_feature);
batch_taglists_.push_back(batch_taglists);
}
}
dim_ = queries[0].size();
if (typeid(T) == typeid(float)) {
qmeta_.set_meta(IndexMeta::DataType::DT_FP32, dim_);
} else if (typeid(T) == typeid(int8_t)) {
qmeta_.set_meta(IndexMeta::DataType::DT_INT8, dim_);
} else {
LOG_ERROR("unsupported type");
return false;
}
cout << "Load query done!" << endl;
return true;
}
void run(core_interface::Index::Pointer index,
core_interface::BaseIndexQueryParam::Pointer query_param,
int max_iter, int topk) {
// Check
if (batch_queries_.size() == 0) {
return;
}
query_param_ = query_param;
query_param_->topk = topk;
query_param_->is_linear = false;
// Do bench
signal(SIGINT, stop);
bench_result_.mark_start();
auto start_time = Monotime::MilliSeconds();
for (size_t i = 0; i < threads_; ++i) {
pool_->execute(this, &Bench<T>::start_bench, index, max_iter, &STOP_NOW);
}
while (!pool_->is_finished()) {
this_thread::sleep_for(chrono::milliseconds(1));
if (Monotime::MilliSeconds() - start_time > bench_secs_ * 1000) {
STOP_NOW = true;
}
}
pool_->wait_finish();
bench_result_.mark_end();
bench_result_.print();
}
void set_tag_lists(const std::vector<std::vector<uint64_t>> &id_to_tags_list,
const std::vector<uint64_t> &tag_key_list) {
id_to_tags_list_ = id_to_tags_list;
tag_key_list_ = tag_key_list;
}
private:
void start_bench(core_interface::Index::Pointer index, size_t max_iter,
const bool *is_stop) {
size_t thread_index = pool_->indexof_this();
size_t i = thread_index;
for (; i < max_iter && !*is_stop; i += threads_) {
int idx = i % batch_queries_.size();
// prefilter
FilterResultCache filter_cache;
std::shared_ptr<IndexFilter> filter_ptr = nullptr;
if (filter_mode_ == FM_TAG) {
if (batch_taglists_[idx].size() != 1) {
LOG_ERROR("query tag list not equal to one!");
return;
}
int ret = filter_cache.filter(id_to_tags_list_, batch_taglists_[idx][0],
tag_key_list_);
if (ret != 0) {
LOG_ERROR("prefilter failed, idx: %d", idx);
return;
}
auto filterFunc = [&](uint64_t key) { return filter_cache.find(key); };
filter_ptr = std::make_shared<IndexFilter>();
filter_ptr->set(filterFunc);
}
auto query_param = query_param_->Clone();
query_param->filter = filter_ptr;
// Do knn_search
uint64_t start = Monotime::MicroSeconds();
int ret;
if (retrieval_mode_ == RM_DENSE) {
if (batch_count_ == 1) {
ret = do_knn_search<T>(index, batch_queries_[idx], query_param);
} else {
ret = do_knn_search_batch<T>(index, batch_queries_[idx], query_param);
}
if (ret != 0) {
LOG_ERROR("Failed to knn search, ret=%d %s", ret,
IndexError::What(ret));
return;
}
} else {
std::string mode = retrieval_mode_ == 1 ? "Dense" : "Sparse";
LOG_ERROR("unsupported retrieval mode: %s", mode.c_str());
}
uint64_t end = Monotime::MicroSeconds();
// Do sample
bench_result_.add_time(batch_count_, end - start);
}
}
template <typename U>
typename std::enable_if<
std::is_same<float, U>::value || std::is_same<int8_t, U>::value ||
std::is_same<uint32_t, U>::value || std::is_same<uint64_t, U>::value,
int>::type
do_knn_search(core_interface::Index::Pointer index, const vector<U> &query,
core_interface::BaseIndexQueryParam::Pointer query_param) {
core_interface::DenseVector dense_query;
dense_query.data = query.data();
core_interface::VectorData query_data;
query_data.vector = dense_query;
core_interface::SearchResult search_result;
int ret = index->Search(query_data, query_param, &search_result);
if (ret < 0) {
return ret;
}
if (search_result.doc_list_.empty()) {
LOG_ERROR("Search results is empty");
}
return 0;
}
template <typename U>
typename std::enable_if<
std::is_same<float, U>::value || std::is_same<int8_t, U>::value ||
std::is_same<uint32_t, U>::value || std::is_same<uint64_t, U>::value,
int>::type
do_knn_search_batch(
core_interface::Index::Pointer index, const vector<U> &query,
core_interface::BaseIndexQueryParam::Pointer query_param) {
// For batch search, we search each query separately
size_t qnum = query.size() / dim_;
for (size_t i = 0; i < qnum; ++i) {
core_interface::DenseVector dense_query;
dense_query.data = query.data() + i * dim_;
core_interface::VectorData query_data;
query_data.vector = dense_query;
core_interface::SearchResult search_result;
int ret = index->Search(query_data, query_param, &search_result);
if (ret < 0) {
return ret;
}
if (search_result.doc_list_.empty()) {
LOG_ERROR("Search results is empty for batch query %zu", i);
}
}
return 0;
}
private:
IndexQueryMeta qmeta_{};
size_t threads_;
size_t bench_secs_;
size_t batch_count_;
size_t dim_;
shared_ptr<ThreadPool> pool_;
core_interface::BaseIndexQueryParam::Pointer query_param_;
vector<vector<T>> batch_queries_;
vector<vector<uint32_t>> batch_sparse_counts_;
vector<vector<uint32_t>> batch_sparse_indices_;
vector<vector<T>> batch_sparse_features_;
vector<vector<vector<uint64_t>>> batch_taglists_;
// Tag lists for filtering
std::vector<std::vector<uint64_t>> id_to_tags_list_;
std::vector<uint64_t> tag_key_list_;
BenchResult bench_result_;
RetrievalMode retrieval_mode_{RM_UNDEFINED};
FilterMode filter_mode_{FM_NONE};
static bool STOP_NOW;
};
template <typename T>
bool Bench<T>::STOP_NOW = false;
//------------------------------------------------------------
// Sparse Bench
//------------------------------------------------------------
template <typename T>
class SparseBench {
public:
SparseBench(size_t threads, size_t bench_secs, size_t batch_count,
FilterMode filter_mode)
: threads_(threads),
bench_secs_(bench_secs),
batch_count_(batch_count),
filter_mode_{filter_mode} {
if (threads_ == 0) {
pool_ = make_shared<ThreadPool>(false);
threads_ = pool_->count();
cout << "Using cpu count as thread pool count[" << threads_ << "]"
<< endl;
} else {
pool_ = make_shared<ThreadPool>(threads_, false);
cout << "Using thread pool count[" << threads_ << "]" << endl;
}
if (batch_count_ < 1) {
batch_count_ = 1;
}
}
static void stop(int signo) {
if (STOP_NOW) {
exit(signo);
}
STOP_NOW = true;
cout << "\rTrying to stop. press [Ctrl+C] again kill immediately." << endl
<< flush;
}
bool load_query(const std::string &query_file, const std::string &first_sep,
const std::string &second_sep) {
TxtInputReader<T> reader;
vector<vector<T>> queries;
vector<SparseData<T>> sparse_data;
vector<vector<uint64_t>> taglists;
if (!reader.load_query(query_file, first_sep, second_sep, queries,
sparse_data, taglists)) {
LOG_ERROR("Load query error");
return false;
}
linear_sparse_data_ = sparse_data;
if (batch_count_ == 1) {
for (size_t i = 0; i < sparse_data.size(); ++i) {
vector<uint32_t> sparse_count;
sparse_count.push_back(sparse_data[i].count);
batch_sparse_counts_.push_back(sparse_count);
batch_sparse_indices_.push_back(sparse_data[i].indices);
batch_sparse_features_.push_back(sparse_data[i].features);
}
for (size_t i = 0; i < taglists.size(); ++i) {
vector<vector<uint64_t>> new_taglists;
new_taglists.push_back(taglists[i]);
batch_taglists_.push_back(std::move(new_taglists));
}
} else {
size_t num_batch = (queries.size() + batch_count_ - 1) / batch_count_;
size_t idx = 0;
for (size_t n = 0; n < num_batch; ++n) {
vector<uint32_t> batch_sparse_count;
vector<uint32_t> batch_sparse_indices;
vector<T> batch_sparse_feature;
vector<vector<uint64_t>> batch_taglists;
for (size_t i = 0; i < batch_count_; ++i) {
batch_sparse_count.push_back(sparse_data[idx].count);
for (size_t k = 0; k < sparse_data[idx].indices.size(); ++k) {
batch_sparse_indices.push_back(sparse_data[idx].indices[k]);
}
for (size_t k = 0; k < sparse_data[idx].features.size(); ++k) {
batch_sparse_feature.push_back(sparse_data[idx].features[k]);
}
if (taglists.size() > idx) {
batch_taglists.push_back(taglists[idx]);
}
idx = (idx + 1) % queries.size();
}
batch_sparse_counts_.push_back(batch_sparse_count);
batch_sparse_indices_.push_back(batch_sparse_indices);
batch_sparse_features_.push_back(batch_sparse_feature);
batch_taglists_.push_back(batch_taglists);
}
}
if (typeid(T) == typeid(float)) {
qmeta_.set_data_type(IndexMeta::DataType::DT_FP32);
} else if (typeid(T) == typeid(int8_t)) {
qmeta_.set_data_type(IndexMeta::DataType::DT_INT8);
} else {
LOG_ERROR("unsupported type");
return false;
}
cout << "Load query done!" << endl;
return true;
}
void run(core_interface::Index::Pointer index,
core_interface::BaseIndexQueryParam::Pointer query_param,
int max_iter, int topk) {
// Check
if (batch_sparse_counts_.size() == 0) {
return;
}
query_param_ = query_param;
query_param_->topk = topk;
query_param_->is_linear = false;
// Do bench
signal(SIGINT, stop);
bench_result_.mark_start();
auto start_time = Monotime::MilliSeconds();
for (size_t i = 0; i < threads_; ++i) {
pool_->execute(this, &SparseBench<T>::start_bench, index, max_iter,
&STOP_NOW);
}
while (!pool_->is_finished()) {
this_thread::sleep_for(chrono::milliseconds(1));
if (Monotime::MilliSeconds() - start_time > bench_secs_ * 1000) {
STOP_NOW = true;
}
}
pool_->wait_finish();
bench_result_.mark_end();
bench_result_.print();
}
void set_tag_lists(const std::vector<std::vector<uint64_t>> &id_to_tags_list,
const std::vector<uint64_t> &tag_key_list) {
id_to_tags_list_ = id_to_tags_list;
tag_key_list_ = tag_key_list;
}
private:
void start_bench(core_interface::Index::Pointer index, size_t max_iter,
const bool *is_stop) {
size_t thread_index = pool_->indexof_this();
size_t i = thread_index;
size_t sparse_query_size = batch_sparse_indices_.size();
for (; i < max_iter && !*is_stop; i += threads_) {
int idx = i % sparse_query_size;
// prefilter
FilterResultCache filter_cache;
std::shared_ptr<IndexFilter> filter_ptr = nullptr;
if (filter_mode_ == FM_TAG) {
if (batch_taglists_[idx].size() != 1) {
LOG_ERROR("query tag list not equal to one!");
return;
}
int ret = filter_cache.filter(id_to_tags_list_, batch_taglists_[idx][0],
tag_key_list_);
if (ret != 0) {
LOG_ERROR("prefilter failed, idx: %d", idx);
return;
}
auto filterFunc = [&](uint64_t key) { return filter_cache.find(key); };
filter_ptr = std::make_shared<IndexFilter>();
filter_ptr->set(filterFunc);
}
auto query_param = query_param_->Clone();
query_param->filter = filter_ptr;
// Do knn_search
uint64_t start = Monotime::MicroSeconds();
int ret;
if (batch_count_ == 1) {
if (batch_sparse_counts_[idx].size() != 1) {
LOG_ERROR("Sparse count size should be 1, since batch count is 1");
return;
}
ret = do_knn_search<T>(index, batch_sparse_counts_[idx][0],
batch_sparse_indices_[idx],
batch_sparse_features_[idx], query_param);
} else {
ret = do_knn_search_batch<T>(
index, batch_sparse_counts_[idx], batch_sparse_indices_[idx],
batch_sparse_features_[idx], idx, query_param);
}
if (ret != 0) {
LOG_ERROR("Failed to sparse knn search, ret=%d %s", ret,
IndexError::What(ret));
return;
}
uint64_t end = Monotime::MicroSeconds();
// Do sample
bench_result_.add_time(batch_count_, end - start);
}
}
// sparse search - single query
template <typename U>
typename std::enable_if<std::is_same<float, U>::value, int>::type
do_knn_search(core_interface::Index::Pointer index,
const uint32_t sparse_count,
const vector<uint32_t> &sparse_indices,
const vector<U> &sparse_feature,
core_interface::BaseIndexQueryParam::Pointer query_param) {
core_interface::SparseVector sparse_query;
sparse_query.count = sparse_count;
sparse_query.indices = sparse_indices.data();
sparse_query.values = sparse_feature.data();
core_interface::VectorData query_data;
query_data.vector = sparse_query;
core_interface::SearchResult search_result;
int ret = index->Search(query_data, query_param, &search_result);
if (ret < 0) {
return ret;
}
if (search_result.doc_list_.empty()) {
LOG_ERROR("Search results is empty");
}
return 0;
}
template <typename U>
typename std::enable_if<std::is_same<int8_t, U>::value ||
std::is_same<uint32_t, U>::value ||
std::is_same<uint64_t, U>::value,
int>::type
do_knn_search(core_interface::Index::Pointer /*index*/,
const uint32_t /*sparse_count*/,
const vector<uint32_t> & /*sparse_indices*/,
const vector<U> & /*sparse_feature*/,
core_interface::BaseIndexQueryParam::Pointer /*query_param*/) {
return IndexError_Unsupported;
}
// sparse search - batch
template <typename U>
typename std::enable_if<std::is_same<float, U>::value, int>::type
do_knn_search_batch(
core_interface::Index::Pointer index,
const vector<uint32_t> &sparse_count,
const vector<uint32_t> & /*sparse_indices*/,
const vector<U> & /*sparse_feature*/, size_t batch_idx,
core_interface::BaseIndexQueryParam::Pointer query_param) {
// For batch search, search each query separately
for (size_t i = 0; i < sparse_count.size(); ++i) {
size_t query_idx = batch_idx * batch_count_ + i;
if (query_idx >= linear_sparse_data_.size()) {
break;
}
const auto &single_sparse = linear_sparse_data_[query_idx];
core_interface::SparseVector sparse_query;
sparse_query.count = single_sparse.count;
sparse_query.indices = single_sparse.indices.data();
sparse_query.values = single_sparse.features.data();
core_interface::VectorData query_data;
query_data.vector = sparse_query;
core_interface::SearchResult search_result;
int ret = index->Search(query_data, query_param, &search_result);
if (ret < 0) {
return ret;
}
if (search_result.doc_list_.empty()) {
LOG_ERROR("Search results is empty for batch query %zu", i);
}
}
return 0;
}
template <typename U>
typename std::enable_if<std::is_same<int8_t, U>::value ||
std::is_same<uint32_t, U>::value ||
std::is_same<uint64_t, U>::value,
int>::type
do_knn_search_batch(
core_interface::Index::Pointer /*index*/,
const vector<uint32_t> & /*sparse_count*/,
const vector<uint32_t> & /*sparse_indices*/,
const vector<U> & /*sparse_feature*/, size_t /*batch_idx*/,
core_interface::BaseIndexQueryParam::Pointer /*query_param*/) {
return IndexError_Unsupported;
}
private:
IndexQueryMeta qmeta_{};
size_t threads_;
size_t bench_secs_;
size_t batch_count_;
core_interface::BaseIndexQueryParam::Pointer query_param_;
shared_ptr<ThreadPool> pool_;
vector<SparseData<T>> linear_sparse_data_;
vector<vector<uint32_t>> batch_sparse_counts_;
vector<vector<uint32_t>> batch_sparse_indices_;
vector<vector<T>> batch_sparse_features_;
vector<vector<vector<uint64_t>>> batch_taglists_;
// Tag lists for filtering
std::vector<std::vector<uint64_t>> id_to_tags_list_;
std::vector<uint64_t> tag_key_list_;
FilterMode filter_mode_{FM_NONE};
BenchResult bench_result_;
static bool STOP_NOW;
};
template <typename T>
bool SparseBench<T>::STOP_NOW = false;
bool check_config(YAML::Node &config_node) {
auto common = config_node["IndexCommon"];
if (!common) {
LOG_ERROR("Can not find [IndexCommon] in config");
return false;
}
if (!common["IndexConfig"]) {
LOG_ERROR("Can not find [IndexConfig] in config");
return false;
}
if (!common["IndexPath"]) {
LOG_ERROR("Can not find [IndexPath] in config");
return false;
}
if (!common["TopK"]) {
LOG_ERROR("Can not find [TopK] in config");
return false;
}
if (!common["QueryFile"]) {
LOG_ERROR("Can not find [QueryFile] in config");
return false;
}
auto query_config = config_node["QueryConfig"];
if (!query_config) {
LOG_ERROR("Can not find [QueryConfig] in config");
return false;
}
if (!query_config["QueryParam"]) {
LOG_ERROR("Can not find [QueryConfig.QueryParam] in config");
return false;
}
return true;
}
void usage(void) {
cout << "Usage: bench CONFIG.yaml [plugin file path]" << endl;
}
int bench(std::string &query_type, size_t thread_count, size_t batch_count,
size_t top_k, string query_file, string &first_sep,
string &second_sep, size_t bench_secs, size_t iter_count,
core_interface::Index::Pointer index,
core_interface::BaseIndexQueryParam::Pointer query_param,
string &index_dir, RetrievalMode retrieval_mode,
FilterMode filter_mode) {
if (filter_mode == FM_TAG && batch_count > 1) {
LOG_ERROR("filter mode can not be run in batch mode");
return -1;
}
std::vector<std::vector<uint64_t>> id_to_tags_list;
std::vector<uint64_t> tag_key_list;
// Load tag lists if available
load_taglists(index_dir, id_to_tags_list, tag_key_list);
if (query_type == "float") {
Bench<float> bench(thread_count, bench_secs, batch_count, retrieval_mode,
filter_mode);
bench.load_query(query_file, first_sep, second_sep);
bench.set_tag_lists(id_to_tags_list, tag_key_list);
bench.run(index, query_param, iter_count, top_k);
} else if (query_type == "int8") {
Bench<int8_t> bench(thread_count, bench_secs, batch_count, retrieval_mode,
filter_mode);
bench.load_query(query_file, first_sep, second_sep);
bench.set_tag_lists(id_to_tags_list, tag_key_list);
bench.run(index, query_param, iter_count, top_k);
} else if (query_type == "binary") {
Bench<uint32_t> bench(thread_count, bench_secs, batch_count, retrieval_mode,
filter_mode);
bench.load_query(query_file, first_sep, second_sep);
bench.set_tag_lists(id_to_tags_list, tag_key_list);
bench.run(index, query_param, iter_count, top_k);
} else if (query_type == "binary64") {
Bench<uint64_t> bench(thread_count, bench_secs, batch_count, retrieval_mode,
filter_mode);
bench.load_query(query_file, first_sep, second_sep);
bench.set_tag_lists(id_to_tags_list, tag_key_list);
bench.run(index, query_param, iter_count, top_k);
} else {
LOG_ERROR("Can not recognize type: %s", query_type.c_str());
}
return 0;
}
int bench_sparse(std::string &query_type, size_t thread_count,
size_t batch_count, size_t top_k, string query_file,
string &first_sep, string &second_sep, size_t bench_secs,
size_t iter_count, core_interface::Index::Pointer index,
core_interface::BaseIndexQueryParam::Pointer query_param,
string &index_dir, FilterMode filter_mode) {
if (filter_mode == FM_TAG && batch_count > 1) {
LOG_ERROR("filter mode can not be run in batch mode");
return -1;
}
std::vector<std::vector<uint64_t>> id_to_tags_list;
std::vector<uint64_t> tag_key_list;
// Load tag lists if available
load_taglists(index_dir, id_to_tags_list, tag_key_list);
if (query_type == "float") {
SparseBench<float> bench(thread_count, bench_secs, batch_count,
filter_mode);
bench.load_query(query_file, first_sep, second_sep);
bench.set_tag_lists(id_to_tags_list, tag_key_list);
bench.run(index, query_param, iter_count, top_k);
} else if (query_type == "int8") {
SparseBench<int8_t> bench(thread_count, bench_secs, batch_count,
filter_mode);
bench.load_query(query_file, first_sep, second_sep);
bench.set_tag_lists(id_to_tags_list, tag_key_list);
bench.run(index, query_param, iter_count, top_k);
} else {
LOG_ERROR("Can not recognize type: %s", query_type.c_str());
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
usage();
return -1;
}
IndexPluginBroker broker;
std::string error;
for (int i = 2; i < argc; ++i) {
if (!broker.emplace(argv[i], &error)) {
LOG_ERROR("Failed to load plugin: %s (%s)", argv[i], error.c_str());
return -1;
}
}
YAML::Node config_node;
try {
config_node = YAML::LoadFile(argv[1]);
} catch (...) {
LOG_ERROR("Load YAML file[%s] failed!", argv[1]);
return -1;
}
if (!check_config(config_node)) {
return -1;
}
auto config_common = config_node["IndexCommon"];
map<string, int> LOG_LEVEL = {{"debug", IndexLogger::LEVEL_DEBUG},
{"info", IndexLogger::LEVEL_INFO},
{"warn", IndexLogger::LEVEL_WARN},
{"error", IndexLogger::LEVEL_ERROR},
{"fatal", IndexLogger::LEVEL_FATAL}};
string log_level = config_common["LogLevel"]
? config_common["LogLevel"].as<string>()
: "debug";
transform(log_level.begin(), log_level.end(), log_level.begin(), ::tolower);
if (LOG_LEVEL.find(log_level) != LOG_LEVEL.end()) {
IndexLoggerBroker::SetLevel(LOG_LEVEL[log_level]);
zvec::ailego::LoggerBroker::SetLevel(LOG_LEVEL[log_level]);
}
// Calculate Bench
size_t thread_count = config_common["BenchThreadCount"]
? config_common["BenchThreadCount"].as<uint64_t>()
: 0;
size_t iter_count = config_common["BenchIterCount"]
? config_common["BenchIterCount"].as<uint64_t>()
: 10000;
size_t batch_count = config_common["BenchBatchCount"]
? config_common["BenchBatchCount"].as<uint64_t>()
: 0;
g_debug_mode = config_common["DebugMode"]
? config_common["DebugMode"].as<bool>()
: false;
string topk_str = config_common["TopK"].as<string>();
RetrievalMode retrieval_mode{RM_DENSE};
if (config_common["RetrievalMode"]) {
std::string retrieval_mode_str =
config_common["RetrievalMode"].as<string>();
if (retrieval_mode_str == "dense") {
retrieval_mode = RM_DENSE;
} else if (retrieval_mode_str == "sparse") {
retrieval_mode = RM_SPARSE;
}
}
FilterMode filter_mode{FM_NONE};
if (config_common["FilterMode"]) {
std::string filter_mode_str = config_common["FilterMode"].as<string>();
if (filter_mode_str == "tag") {
filter_mode = FM_TAG;
}
}
vector<int32_t> topk_values;
StringHelper::Split(topk_str, ",", &topk_values);
size_t top_k = *topk_values.rbegin();
string query_file = config_common["QueryFile"].as<string>();
string first_sep = config_common["QueryFirstSep"]
? config_common["QueryFirstSep"].as<string>()
: ";";
string second_sep = config_common["QuerySecondSep"]
? config_common["QuerySecondSep"].as<string>()
: " ";
string query_type = config_common["QueryType"]
? config_common["QueryType"].as<string>()
: "float";
size_t bench_secs = config_common["BenchSecs"]
? config_common["BenchSecs"].as<uint64_t>()
: 60;
string index_dir = config_common["IndexPath"].as<string>();
core_interface::Index::Pointer index;
core_interface::BaseIndexQueryParam::Pointer query_param;
if (0 !=
parse_and_load_index_param(config_node, index_dir, index, query_param)) {
LOG_ERROR("Failed to parse and load index param");
return -1;
}
if (retrieval_mode == RM_SPARSE) {
bench_sparse(query_type, thread_count, batch_count, top_k, query_file,
first_sep, second_sep, bench_secs, iter_count, index,
query_param, index_dir, filter_mode);
cout << "Bench Sparse done." << endl;
} else {
bench(query_type, thread_count, batch_count, top_k, query_file, first_sep,
second_sep, bench_secs, iter_count, index, query_param, index_dir,
retrieval_mode, filter_mode);
cout << "Bench done." << endl;
}
// Cleanup
index->Close();
return 0;
}
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <stdio.h>
#include <string.h>
#include <limits>
#include <map>
#ifdef _MSC_VER
#include <chrono>
#else
#include <sys/time.h>
#endif
#include <ailego/parallel/lock.h>
namespace zvec {
namespace core {
class BenchResult {
public:
BenchResult() {
total_query_count_ = 0;
total_process_time_by_us_ = 0;
min_time_by_us_ = std::numeric_limits<long>::max();
max_time_by_us_ = 0;
}
~BenchResult() {}
void add_time(int query_count, long time_by_us) {
lock_.lock();
total_query_count_ += query_count;
total_process_time_by_us_ += time_by_us;
long time_val = time_by_us / 100;
if (process_time_map_.find(time_val) != process_time_map_.end()) {
++process_time_map_[time_val];
} else {
process_time_map_[time_val] = 1;
}
if (time_by_us < min_time_by_us_) {
min_time_by_us_ = time_by_us;
} else if (time_by_us > max_time_by_us_) {
max_time_by_us_ = time_by_us;
}
lock_.unlock();
}
void mark_start() {
#ifdef _MSC_VER
start_ = std::chrono::steady_clock::now();
#else
gettimeofday(&start_, NULL);
#endif
}
void mark_end() {
#ifdef _MSC_VER
end_ = std::chrono::steady_clock::now();
#else
gettimeofday(&end_, NULL);
#endif
}
long get_duration_by_ms() {
#ifdef _MSC_VER
return static_cast<long>(
std::chrono::duration_cast<std::chrono::milliseconds>(end_ - start_)
.count());
#else
long duration = (end_.tv_sec - start_.tv_sec) * 1000 +
(end_.tv_usec - start_.tv_usec) / 1000;
return duration;
#endif
}
long get_total_query_count() {
return total_query_count_;
}
std::map<long, long> &get_process_time_map() {
return process_time_map_;
}
long get_total_process_time_by_ms() {
return total_process_time_by_us_ / 1000;
}
void print() {
fprintf(stdout,
"Process query: %ld, total process time: %ldms, "
"duration: %ldms, max: %ldms, min:%ldms\n",
get_total_query_count(), get_total_process_time_by_ms(),
get_duration_by_ms(), max_time_by_us_ / 1000,
min_time_by_us_ / 1000);
fprintf(stdout, "Avg latency: %0.1fms qps: %0.1f\n",
((float)get_total_process_time_by_ms()) / get_total_query_count(),
get_total_query_count() / ((float)get_duration_by_ms() / 1000));
int tot_num = 0;
int percent[] = {25, 50, 75, 90, 95, 99};
int index = 0;
float max_time = 0.0;
int last_num = 0;
for (auto element : process_time_map_) {
tot_num += element.second;
if (tot_num >= total_query_count_ * percent[index] / 100) {
if (last_num != tot_num) {
max_time = (float)element.first / 10;
last_num = tot_num;
}
fprintf(stdout, "%d Percentile:\t\t %.1f ms\n", percent[index],
max_time);
index++;
if (index >= 6) {
break;
}
}
}
for (; index < 6; index++) {
fprintf(stdout, "%d Percentile:\t\t %.1f ms\n", percent[index], max_time);
}
fprintf(stdout, "\n");
}
private:
long total_query_count_;
long total_process_time_by_us_;
long min_time_by_us_;
long max_time_by_us_;
#ifdef _MSC_VER
std::chrono::steady_clock::time_point start_;
std::chrono::steady_clock::time_point end_;
#else
struct timeval start_;
struct timeval end_;
#endif
ailego::SpinMutex lock_;
std::map<long, long> process_time_map_; // <processTimeBy100us, count>
};
} // namespace core
} // namespace zvec
+187
View File
@@ -0,0 +1,187 @@
from __future__ import annotations
import logging
import os
import pathlib
from pathlib import Path
import numpy as np
import pandas as pd
import polars as pl
to_append = True
def write_neighbors_file(data_frame, neighbors_file):
id_list = np.stack(data_frame["id"])
neighbors_list = np.stack(data_frame["neighbors_id"])
id_list.tolist()
neighbors_list.tolist()
if len(id_list) != len(neighbors_list):
logger.error("list size not equal: %d, %d", len(id_list), len(neighbors_list))
os._exit(1)
for i in range(len(id_list)):
id_int = id_list[i]
line = str(id_int) + ";"
neighbors = neighbors_list[i]
# for j in range(len(neighbors)):
for j in range(100):
neighbor_id = neighbors[j]
line += str(neighbor_id)
if j != 99:
line += " "
else:
line += "\n"
neighbors_file.write(line)
logger.info("Output neighbors file done. Total lines: %d", len(id_list))
def write_vector_file(data_frame, vector_file):
test_embedding_list = np.stack(data_frame["emb"])
test_embedding_list.tolist()
test_id_list = np.stack(data_frame["id"])
test_id_list.tolist()
if len(test_id_list) != len(test_embedding_list):
logger.info(
"id list not matched with embedding list! : %d, %d",
len(test_id_list),
len(test_embedding_list),
)
return
for case_id in range(len(test_id_list)):
idx = test_id_list[case_id]
vector = test_embedding_list[case_id]
vector_line = str(idx) + ";"
for i in range(len(vector)):
vector_line += str(round(vector[i], 16))
if i != len(vector) - 1:
vector_line += " "
else:
vector_line += ";"
vector_line += "\n"
vector_file.write(vector_line)
if case_id != 0 and case_id % 10000 == 0:
logger.info("output lines: %d", case_id)
logger.info("Output vector file done. Total lines: %d", len(test_id_list))
def read_parquet_file(file_name: str) -> pd.DataFrame:
parquet_file = pathlib.Path(file_name)
if not parquet_file.exists():
logger.error("open error!")
return pd.DataFrame()
try:
return pl.read_parquet(parquet_file)
except Exception:
logger.error("open error! error file: %s", file_name)
return pd.DataFrame()
def gen_vector_files(input_dir, input_file_pattern, output_dir, output_file_name):
input_file_list = list(Path(input_dir).rglob(input_file_pattern))
output_file_name_full = pathlib.Path(output_dir, output_file_name)
if not to_append and output_file_name_full.exists():
logger.error("File exists! File name: %s", output_file_name_full)
os._exit(1)
write_flag = "a" if to_append else "w"
with Path.open(output_file_name_full.resolve(), write_flag) as vector_file:
for input_file in input_file_list:
input_file_name = input_file.resolve()
logger.info(
"Load the entire file into memory. File name: %s", input_file_name
)
data_set = read_parquet_file(input_file.resolve())
logger.info("Read parquet file done. File name: %s", input_file_name)
if len(data_set) > 0:
logger.info("Process parquet file. File name: %s", input_file_name)
write_vector_file(data_set, vector_file)
logger.info("Process parquet file done. File name: %s", input_file_name)
def gen_neighbor_files(input_dir, input_file_pattern, output_dir, output_file_name):
input_file_list = list(Path(input_dir).rglob(input_file_pattern))
output_file_name_full = pathlib.Path(output_dir, output_file_name)
if not to_append and output_file_name_full.exists():
logger.error("File already exists. File name: %s", output_file_name_full)
os._exit(1)
write_flag = "a" if to_append else "w"
with Path.open(output_file_name_full.resolve(), write_flag) as neighbor_file:
for input_file in input_file_list:
input_file_name = input_file.resolve()
logger.info(
"Load the entire file into memory. File name: %s", input_file_name
)
data_set = read_parquet_file(input_file.resolve())
logger.info("Read parquet file done. File name: %s", input_file_name)
if len(data_set) > 0:
logger.info("Write parquet file. File name: %s", input_file_name)
write_neighbors_file(data_set, neighbor_file)
logger.info("Write parquet file done. File name: %s", input_file_name)
if __name__ == "__main__":
logger = logging.getLogger("convert_log")
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
input_dir = "./cohere/10m"
output_dir = "./10m.output"
logger.info("Generate test vector files")
input_file_pattern = "test.parquet"
output_file_name = "cohere_test_vector_1000.new.txt"
to_append = False
gen_vector_files(input_dir, input_file_pattern, output_dir, output_file_name)
logger.info("Generate neighbor files")
input_file_pattern = "neighbors.parquet"
output_file_name = "neighbors.txt"
to_append = False
gen_neighbor_files(input_dir, input_file_pattern, output_dir, output_file_name)
logger.info("Generate train vector files")
output_file_name = "cohere_768_10m_vector.train.txt"
to_append = True
for i in range(10):
input_file_pattern = "shuffle_train-0" + str(i) + "-of-10.parquet"
gen_vector_files(input_dir, input_file_pattern, output_dir, output_file_name)
+77
View File
@@ -0,0 +1,77 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <vector>
#include <roaring/roaring.h>
namespace zvec {
namespace core {
struct FilterResultCache {
public:
FilterResultCache() {
bitmap_ = roaring_bitmap_create();
}
FilterResultCache(uint32_t capacity_hint) {
bitmap_ = roaring_bitmap_create_with_capacity(capacity_hint);
}
~FilterResultCache() {
roaring_bitmap_free(bitmap_);
bitmap_ = nullptr;
}
bool find(uint64_t key) const {
return !roaring_bitmap_contains(bitmap_, key);
}
void set(uint64_t key) const {
roaring_bitmap_add(bitmap_, key);
}
int filter(const std::vector<std::vector<uint64_t>> &id_to_tags_list,
const std::vector<uint64_t> &query_tag_list,
const std::vector<uint64_t> &id_to_key_list) {
for (size_t i = 0; i < id_to_tags_list.size(); ++i) {
auto &id_tag_list = id_to_tags_list[i];
size_t t_i = 0;
size_t q_i = 0;
while (t_i < id_tag_list.size() && q_i < query_tag_list.size()) {
if (id_tag_list[t_i] == query_tag_list[q_i]) {
uint64_t key = id_to_key_list[i];
set(key);
break;
} else if (id_tag_list[t_i] < query_tag_list[q_i]) {
++t_i;
} else {
++q_i;
}
}
}
return 0;
}
public:
roaring_bitmap_t *bitmap_{nullptr};
};
} // namespace core
} // namespace zvec
+555
View File
@@ -0,0 +1,555 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 "zvec/core/framework/index_flow.h"
#include "meta_segment_common.h"
using namespace std;
namespace zvec {
namespace core {
#define SEARCH_DENSE_BATCH(_FUNC_NAME) \
int _FUNC_NAME(const void *query, const IndexQueryMeta &qmeta, \
uint32_t count, Context::Pointer &context) const { \
if (streamer_) { \
if (reformer_) { \
std::string ovec; \
IndexQueryMeta ometa; \
int ret = reformer_->convert(query, qmeta, count, &ovec, &ometa); \
if (ret != 0) { \
return ret; \
} \
return streamer_->_FUNC_NAME(ovec.data(), ometa, count, \
context->context()); \
} else { \
return streamer_->_FUNC_NAME(query, qmeta, count, context->context()); \
} \
} else { \
return flow_._FUNC_NAME(query, qmeta, count, context->flow_context()); \
} \
}
#define SEARCH_DENSE(_FUNC_NAME) \
int _FUNC_NAME(const void *query, const IndexQueryMeta &qmeta, \
Context::Pointer &context) const { \
if (streamer_) { \
if (reformer_) { \
std::string ovec; \
IndexQueryMeta ometa; \
int ret = reformer_->convert(query, qmeta, &ovec, &ometa); \
if (ret != 0) { \
return ret; \
} \
return streamer_->_FUNC_NAME(ovec.data(), ometa, context->context()); \
} else { \
return streamer_->_FUNC_NAME(query, qmeta, context->context()); \
} \
} else { \
return flow_._FUNC_NAME(query, qmeta, context->flow_context()); \
} \
}
#define SEARCH_SPRASE_BATCH(_FUNC_NAME) \
int _FUNC_NAME(const uint32_t *sparse_count, const uint32_t *sparse_indices, \
const void *sparse_query, const IndexQueryMeta &qmeta, \
uint32_t count, Context::Pointer &context) const { \
if (streamer_) { \
if (reformer_) { \
LOG_ERROR("reformer not supported in sparse search"); \
return IndexError_Runtime; \
} else { \
return streamer_->_FUNC_NAME(sparse_count, sparse_indices, \
sparse_query, qmeta, count, \
context->context()); \
} \
} else { \
return flow_._FUNC_NAME(sparse_count, sparse_indices, sparse_query, \
qmeta, count, context->flow_context()); \
} \
}
#define SEARCH_SPARSE(_FUNC_NAME) \
int _FUNC_NAME(const uint32_t sparse_count, const uint32_t *sparse_indices, \
const void *sparse_query, const IndexQueryMeta &qmeta, \
Context::Pointer &context) const { \
if (streamer_) { \
if (reformer_) { \
LOG_ERROR("reformer not supported in sparse search"); \
return IndexError_Runtime; \
} else { \
return streamer_->_FUNC_NAME(sparse_count, sparse_indices, \
sparse_query, qmeta, context->context()); \
} \
} else { \
return flow_._FUNC_NAME(sparse_count, sparse_indices, sparse_query, \
qmeta, context->flow_context()); \
} \
}
class Flow {
public:
class Context {
public:
typedef std::unique_ptr<Context> Pointer;
Context(IndexContext::Pointer &ctx, IndexFlow::Context::Pointer &flow_ctx)
: ctx_(std::move(ctx)), flow_ctx_(std::move(flow_ctx)) {}
void set_debug_mode(bool debug_mode) {
ctx_ ? ctx_->set_debug_mode(debug_mode)
: flow_ctx_->set_debug_mode(debug_mode);
}
std::string debug_string() {
return ctx_ ? ctx_->debug_string() : flow_ctx_->debug_string();
}
void set_topk(uint32_t topk) {
ctx_ ? ctx_->set_topk(topk) : flow_ctx_->set_topk(topk);
}
template <typename T>
void set_filter(T &&func) {
ctx_ ? ctx_->set_filter(func) : flow_ctx_->set_filter(func);
}
const IndexDocumentList &result(void) const {
return ctx_ ? ctx_->result() : flow_ctx_->result();
}
const IndexDocumentList &result(size_t index) const {
return ctx_ ? ctx_->result(index) : flow_ctx_->result(index);
}
public:
friend class Flow;
IndexFlow::Context::Pointer &flow_context(void) {
return flow_ctx_;
}
IndexContext::Pointer &context(void) {
return ctx_;
}
private:
IndexContext::Pointer ctx_;
IndexFlow::Context::Pointer flow_ctx_;
};
Context::Pointer create_context(void) const {
IndexContext::Pointer ctx;
IndexFlow::Context::Pointer flow_ctx;
if (streamer_) {
ctx = streamer_->create_context();
} else {
flow_ctx = flow_.create_context();
}
return Context::Pointer(new (std::nothrow) Context(ctx, flow_ctx));
}
int set_container(const std::string &name, const ailego::Params &params) {
return flow_.set_storage(name, params);
}
int load_taglists(const std::string &path) {
// load tag lists
auto storage = IndexFactory::CreateStorage("MMapFileReadStorage");
int ret = storage->open(path, false);
if (ret != 0) {
LOG_ERROR("Failed to load index with storage %s",
storage->name().c_str());
return ret;
}
auto segment_taglist_header = storage->get(TAGLIST_HEADER_SEGMENT_NAME);
if (!segment_taglist_header) {
LOG_INFO("No Tag Lists Found!");
return 0;
}
TagListHeader taglist_header;
void *data_ptr;
if (segment_taglist_header->read(0, (const void **)(&data_ptr),
sizeof(TagListHeader)) !=
sizeof(TagListHeader)) {
LOG_ERROR("Read tag list meta failed");
return IndexError_ReadData;
}
memcpy(&taglist_header, data_ptr, sizeof(TagListHeader));
auto segment_taglist_key = storage->get(TAGLIST_KEY_SEGMENT_NAME);
if (!segment_taglist_key) {
LOG_ERROR("IndexStorage get segment %s failed",
TAGLIST_KEY_SEGMENT_NAME.c_str());
return IndexError_InvalidValue;
}
size_t offset = 0;
for (size_t i = 0; i < taglist_header.num_vecs; ++i) {
if (segment_taglist_key->read(offset, (const void **)(&data_ptr),
sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERROR("Read tag list key failed");
return IndexError_ReadData;
}
uint64_t key = *reinterpret_cast<const uint64_t *>(data_ptr);
tag_key_list_.push_back(key);
offset += sizeof(uint64_t);
}
auto segment_taglist_data = storage->get(TAGLIST_DATA_SEGMENT_NAME);
if (!segment_taglist_data) {
LOG_ERROR("IndexStorage get segment %s failed",
TAGLIST_DATA_SEGMENT_NAME.c_str());
return IndexError_InvalidValue;
}
std::vector<uint64_t> taglist_offsets;
offset = 0;
for (size_t i = 0; i < taglist_header.num_vecs; ++i) {
if (segment_taglist_data->read(offset, (const void **)(&data_ptr),
sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERROR("Read tag list data failed");
return IndexError_ReadData;
}
uint64_t tag_offset = *reinterpret_cast<const uint64_t *>(data_ptr);
taglist_offsets.push_back(tag_offset);
offset += sizeof(uint64_t);
}
offset = taglist_header.num_vecs * sizeof(uint64_t);
for (size_t i = 0; i < taglist_header.num_vecs; ++i) {
if (segment_taglist_data->read(offset, (const void **)(&data_ptr),
sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERROR("Read tag list data failed");
return IndexError_ReadData;
}
offset += sizeof(uint64_t);
uint64_t tag_count = *reinterpret_cast<const uint64_t *>(data_ptr);
if (segment_taglist_data->read(offset, (const void **)(&data_ptr),
tag_count * sizeof(uint64_t)) !=
tag_count * sizeof(uint64_t)) {
LOG_ERROR("Read tag list data failed");
return IndexError_ReadData;
}
offset += tag_count * sizeof(uint64_t);
std::vector<uint64_t> tag_list;
for (size_t j = 0; j < tag_count; ++j) {
uint64_t tag_id = *(reinterpret_cast<const uint64_t *>(data_ptr) + j);
tag_list.push_back(tag_id);
}
// order tags
sort(tag_list.begin(), tag_list.end());
id_to_tags_list_.push_back(std::move(tag_list));
}
storage->cleanup();
storage = nullptr;
return 0;
}
int load(const std::string &path) {
int ret = load_taglists(path);
if (ret != 0) {
LOG_ERROR("Failed to load tag lists");
return ret;
}
if (streamer_) {
stg_ = IndexFactory::CreateStorage("MMapFileStorage");
if (!stg_) {
return IndexError_NoExist;
}
ailego::Params params;
params.set("proxima.mmap_file.storage.memory_warmup", true);
ret = stg_->init(params);
if (ret != 0) {
return ret;
}
ret = stg_->open(path, true);
if (ret != 0) {
return ret;
}
if (!inited_) {
IndexMeta meta;
ret = IndexHelper::DeserializeFromStorage(stg_.get(), &meta);
if (ret != 0) {
LOG_ERROR("Failed to get IndexMeta from Storage");
return ret;
}
ret = streamer_->init(meta, searcher_params_);
if (ret != 0) {
return ret;
}
if (!meta.reformer_name().empty()) {
reformer_ = IndexFactory::CreateReformer(meta.reformer_name());
if (!reformer_) {
LOG_ERROR("Failed to create reformer %s",
meta.reformer_name().c_str());
return IndexError_NoExist;
}
reformer_->init(meta.reformer_params());
}
}
return streamer_->open(stg_);
} else {
return flow_.load(path);
}
}
int unload(void) {
if (streamer_) {
streamer_->close();
return stg_->close();
} else {
return flow_.unload();
}
}
int set_searcher(const std::string &name, const ailego::Params &params) {
//! If the searcher is streamer, create it
streamer_ = IndexFactory::CreateStreamer(name);
if (!streamer_) {
return flow_.set_searcher(name, params);
}
searcher_params_ = params;
return 0;
}
int set_searcher(IndexStreamer::Pointer streamer) {
streamer_ = streamer;
inited_ = true;
return 0;
}
const std::vector<std::vector<uint64_t>> &id_to_tags_list() const {
return id_to_tags_list_;
}
const std::vector<uint64_t> &tag_key_list() const {
return tag_key_list_;
}
SEARCH_DENSE_BATCH(search_impl);
SEARCH_DENSE(search_impl);
SEARCH_DENSE_BATCH(search_bf_impl);
SEARCH_DENSE(search_bf_impl);
private:
IndexFlow flow_{};
IndexStreamer::Pointer streamer_{};
IndexReformer::Pointer reformer_{};
bool inited_{false};
IndexStorage::Pointer stg_{};
ailego::Params searcher_params_{};
std::vector<std::vector<uint64_t>> id_to_tags_list_;
std::vector<uint64_t> tag_key_list_;
};
class SparseFlow {
public:
class Context {
public:
typedef std::unique_ptr<Context> Pointer;
Context(IndexContext::Pointer &ctx,
IndexSparseFlow::Context::Pointer &flow_ctx)
: ctx_(std::move(ctx)), flow_ctx_(std::move(flow_ctx)) {}
void set_debug_mode(bool debug_mode) {
ctx_ ? ctx_->set_debug_mode(debug_mode)
: flow_ctx_->set_debug_mode(debug_mode);
}
std::string debug_string() {
return ctx_ ? ctx_->debug_string() : flow_ctx_->debug_string();
}
template <typename T>
void set_filter(T &&func) {
ctx_ ? ctx_->set_filter(func) : flow_ctx_->set_filter(func);
}
void set_topk(uint32_t topk) {
ctx_ ? ctx_->set_topk(topk) : flow_ctx_->set_topk(topk);
}
const IndexDocumentList &result(void) const {
return ctx_ ? ctx_->result() : flow_ctx_->result();
}
const IndexDocumentList &result(size_t index) const {
return ctx_ ? ctx_->result(index) : flow_ctx_->result(index);
}
private:
friend class SparseFlow;
IndexSparseFlow::Context::Pointer &flow_context(void) {
return flow_ctx_;
}
IndexContext::Pointer &context(void) {
return ctx_;
}
private:
IndexContext::Pointer ctx_;
IndexSparseFlow::Context::Pointer flow_ctx_;
};
Context::Pointer create_context(void) const {
IndexContext::Pointer ctx;
IndexSparseFlow::Context::Pointer flow_ctx;
if (streamer_) {
ctx = streamer_->create_context();
} else {
flow_ctx = flow_.create_context();
}
return Context::Pointer(new (std::nothrow) Context(ctx, flow_ctx));
}
int set_container(const std::string &name, const ailego::Params &params) {
return flow_.set_storage(name, params);
}
int load(const std::string &path) {
if (streamer_) {
stg_ = IndexFactory::CreateStorage("MMapFileStorage");
if (!stg_) {
return IndexError_NoExist;
}
ailego::Params params;
params.set("proxima.mmap_file.storage.memory_warmup", true);
int ret = stg_->init(params);
if (ret != 0) {
return ret;
}
ret = stg_->open(path, true);
if (ret != 0) {
return ret;
}
if (!inited_) {
IndexMeta meta;
ret = IndexHelper::DeserializeFromStorage(stg_.get(), &meta);
if (ret != 0) {
LOG_ERROR("Failed to get IndexMeta from Storage");
return ret;
}
ret = streamer_->init(meta, searcher_params_);
if (ret != 0) {
return ret;
}
if (!meta.reformer_name().empty()) {
reformer_ = IndexFactory::CreateReformer(meta.reformer_name());
if (!reformer_) {
LOG_ERROR("Failed to create reformer %s",
meta.reformer_name().c_str());
return IndexError_NoExist;
}
reformer_->init(meta.reformer_params());
}
}
return streamer_->open(stg_);
} else {
return flow_.load(path);
}
return 0;
}
int unload(void) {
if (streamer_) {
streamer_->close();
return stg_->close();
} else {
return flow_.unload();
}
}
int set_searcher(const std::string &name, const ailego::Params &params) {
//! If the searcher is streamer, create it
streamer_ = IndexFactory::CreateStreamer(name);
if (!streamer_) {
return flow_.set_searcher(name, params);
}
searcher_params_ = params;
return 0;
}
int set_searcher(IndexStreamer::Pointer streamer) {
streamer_ = streamer;
inited_ = true;
return 0;
}
const std::vector<std::vector<uint64_t>> &id_to_tags_list() const {
return id_to_tags_list_;
}
const std::vector<uint64_t> &tag_key_list() const {
return tag_key_list_;
}
SEARCH_SPRASE_BATCH(search_impl);
SEARCH_SPARSE(search_impl);
SEARCH_SPRASE_BATCH(search_bf_impl);
SEARCH_SPARSE(search_bf_impl);
private:
IndexSparseFlow flow_{};
IndexStreamer::Pointer streamer_{};
IndexReformer::Pointer reformer_{};
bool inited_{false};
IndexStorage::Pointer stg_{};
ailego::Params searcher_params_{};
std::vector<std::vector<uint64_t>> id_to_tags_list_;
std::vector<uint64_t> tag_key_list_;
};
} // namespace core
} // namespace zvec
+297
View File
@@ -0,0 +1,297 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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.
#include <sys/stat.h>
#include <signal.h>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <ailego/container/bitmap.h>
#include <ailego/parallel/lock.h>
#include <zvec/ailego/hash/crc32c.h>
#include <zvec/ailego/io/file.h>
#include <zvec/ailego/logger/logger.h>
#include <zvec/ailego/parallel/thread_pool.h>
#include <zvec/ailego/utility/string_helper.h>
#include <zvec/ailego/utility/time_helper.h>
#include "zvec/core/framework/index_error.h"
#include "zvec/core/framework/index_factory.h"
#include "zvec/core/framework/index_plugin.h"
#include "zvec/core/framework/index_storage.h"
#include "zvec/core/interface/index.h"
#include "zvec/core/interface/index_factory.h"
#include "zvec/core/interface/index_param.h"
#include "filter_result_cache.h"
#include "meta_segment_common.h"
#include "txt_input_reader.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
#include <yaml-cpp/yaml.h>
#ifdef __clang__
#pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop
#endif
using namespace std;
using namespace zvec;
using namespace zvec::core;
using namespace zvec::ailego;
int parse_and_load_index_param(
YAML::Node &config_node, string &index_dir,
core_interface::Index::Pointer &index,
core_interface::BaseIndexQueryParam::Pointer &query_param) {
// Create Index from config
if (auto index_config = config_node["IndexCommon"]["IndexConfig"]) {
std::cout << "IndexConfig: " << index_config.as<string>() << std::endl;
auto params = core_interface::IndexFactory::DeserializeIndexParamFromJson(
index_config.as<string>());
index = core_interface::IndexFactory::CreateAndInitIndex(*params);
if (!index) {
LOG_ERROR("Failed to create index");
return -1;
}
core_interface::StorageOptions storage_options;
storage_options.type = core_interface::StorageOptions::StorageType::kMMAP;
storage_options.create_new = false;
storage_options.read_only = true;
int ret = index->Open(index_dir, storage_options);
if (0 != ret) {
LOG_ERROR("Index open failed with ret %d", ret);
return -1;
}
cout << "Load index done!" << endl;
} else {
LOG_ERROR("IndexCommon.IndexConfig is required");
return -1;
}
/*
QueryConfig:
QueryParam: '{"ef_search":100,"index_type":"kHNSW"}'
RefinerConfig:
ScaleFactor: !!int 2
ReferenceIndex:
Config:
'{"use_id_map":false,"data_type":"DT_FP32","dimension":768,"index_type":"kHNSW","metric_type":"kCosine"}'
Path: ./cohere_train_vector_1m.2.index
*/
// QUERY PARAM
if (auto query_config = config_node["QueryConfig"]; query_config) {
// QueryConfig.QueryParam
if (auto query_param_config = query_config["QueryParam"];
query_param_config) {
std::cout << "QueryParam: " << query_param_config.as<string>()
<< std::endl;
query_param = core_interface::IndexFactory::QueryParamDeserializeFromJson<
core_interface::BaseIndexQueryParam>(
query_param_config.as<std::string>());
if (!query_param) {
LOG_ERROR("Failed to deserialize query params");
return -1;
}
}
// QueryConfig.RefinerConfig
if (auto refiner_config = query_config["RefinerConfig"]; refiner_config) {
core_interface::Index::Pointer reference_index = nullptr;
auto refiner_param = std::make_shared<core_interface::RefinerParam>();
// QueryConfig.RefinerConfig.ScaleFactor
if (auto scale_factor_config = refiner_config["ScaleFactor"];
scale_factor_config) {
auto scale_factor = scale_factor_config.as<float>();
refiner_param->scale_factor_ = scale_factor;
} else {
LOG_ERROR("QueryConfig.RefinerConfig.ScaleFactor config is required");
return -1;
}
// QueryConfig.RefinerConfig.ReferenceIndex
if (auto reference_index_config = refiner_config["ReferenceIndex"];
reference_index_config) {
// QueryConfig.RefinerConfig.ReferenceIndex.Config
if (auto reference_index_config_config =
reference_index_config["Config"];
reference_index_config_config) {
auto params =
core_interface::IndexFactory::DeserializeIndexParamFromJson(
reference_index_config_config.as<std::string>());
reference_index =
core_interface::IndexFactory::CreateAndInitIndex(*params);
} else {
LOG_ERROR(
"QueryConfig.RefinerConfig.ReferenceIndex.Config config is "
"required");
return -1;
}
// QueryConfig.RefinerConfig.ReferenceIndex.Path
if (auto reference_index_path_config = reference_index_config["Path"];
reference_index_path_config) {
auto reference_index_path =
reference_index_path_config.as<std::string>();
core_interface::StorageOptions storage_options;
storage_options.type =
core_interface::StorageOptions::StorageType::kMMAP;
storage_options.create_new = false;
storage_options.read_only = true;
int ret =
reference_index->Open(reference_index_path, storage_options);
if (0 != ret) {
LOG_ERROR("Index open failed with ret %d", ret);
return -1;
}
cout << "Load reference index done!" << endl;
} else {
LOG_ERROR(
"QueryConfig.RefinerConfig.ReferenceIndex.Path is required");
return -1;
}
refiner_param->reference_index = reference_index;
} else {
LOG_ERROR(
"QueryConfig.RefinerConfig.ReferenceIndex section is required");
return -1;
} // QueryConfig.RefinerConfig.ReferenceIndex
query_param->refiner_param = refiner_param;
} // QueryConfig.RefinerConfig
} // QUERY PARAM
return 0;
}
//--------------------------------------------------
// Helper functions for loading tag lists
//--------------------------------------------------
int load_taglists(const std::string &path,
std::vector<std::vector<uint64_t>> &id_to_tags_list,
std::vector<uint64_t> &tag_key_list) {
// Load tag lists
auto storage = IndexFactory::CreateStorage("MMapFileReadStorage");
int ret = storage->open(path, false);
if (ret != 0) {
LOG_ERROR("Failed to load index with storage %s", storage->name().c_str());
return ret;
}
auto segment_taglist_header = storage->get(TAGLIST_HEADER_SEGMENT_NAME);
if (!segment_taglist_header) {
LOG_INFO("No Tag Lists Found!");
return 0;
}
TagListHeader taglist_header;
void *data_ptr;
if (segment_taglist_header->read(0, (const void **)(&data_ptr),
sizeof(TagListHeader)) !=
sizeof(TagListHeader)) {
LOG_ERROR("Read tag list meta failed");
return IndexError_ReadData;
}
memcpy(&taglist_header, data_ptr, sizeof(TagListHeader));
auto segment_taglist_key = storage->get(TAGLIST_KEY_SEGMENT_NAME);
if (!segment_taglist_key) {
LOG_ERROR("IndexStorage get segment %s failed",
TAGLIST_KEY_SEGMENT_NAME.c_str());
return IndexError_InvalidValue;
}
size_t offset = 0;
for (size_t i = 0; i < taglist_header.num_vecs; ++i) {
if (segment_taglist_key->read(offset, (const void **)(&data_ptr),
sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERROR("Read tag list key failed");
return IndexError_ReadData;
}
uint64_t key = *reinterpret_cast<const uint64_t *>(data_ptr);
tag_key_list.push_back(key);
offset += sizeof(uint64_t);
}
auto segment_taglist_data = storage->get(TAGLIST_DATA_SEGMENT_NAME);
if (!segment_taglist_data) {
LOG_ERROR("IndexStorage get segment %s failed",
TAGLIST_DATA_SEGMENT_NAME.c_str());
return IndexError_InvalidValue;
}
std::vector<uint64_t> taglist_offsets;
offset = 0;
for (size_t i = 0; i < taglist_header.num_vecs; ++i) {
if (segment_taglist_data->read(offset, (const void **)(&data_ptr),
sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERROR("Read tag list data failed");
return IndexError_ReadData;
}
uint64_t tag_offset = *reinterpret_cast<const uint64_t *>(data_ptr);
taglist_offsets.push_back(tag_offset);
offset += sizeof(uint64_t);
}
offset = taglist_header.num_vecs * sizeof(uint64_t);
for (size_t i = 0; i < taglist_header.num_vecs; ++i) {
if (segment_taglist_data->read(offset, (const void **)(&data_ptr),
sizeof(uint64_t)) != sizeof(uint64_t)) {
LOG_ERROR("Read tag list data failed");
return IndexError_ReadData;
}
offset += sizeof(uint64_t);
uint64_t tag_count = *reinterpret_cast<const uint64_t *>(data_ptr);
if (segment_taglist_data->read(offset, (const void **)(&data_ptr),
tag_count * sizeof(uint64_t)) !=
tag_count * sizeof(uint64_t)) {
LOG_ERROR("Read tag list data failed");
return IndexError_ReadData;
}
offset += tag_count * sizeof(uint64_t);
std::vector<uint64_t> tag_list;
tag_list.reserve(tag_count);
for (size_t j = 0; j < tag_count; ++j) {
tag_list.push_back(reinterpret_cast<const uint64_t *>(data_ptr)[j]);
}
id_to_tags_list.push_back(std::move(tag_list));
}
return 0;
}
+121
View File
@@ -0,0 +1,121 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <iostream>
#include <string>
#include "zvec/core/framework/index_meta.h"
namespace zvec {
namespace core {
class IndexMetaHelper {
public:
static std::string to_string(IndexMeta::DataType type) {
switch (type) {
case IndexMeta::DataType::DT_FP32:
return std::string("FP32");
case IndexMeta::DataType::DT_FP64:
return std::string("FP64");
case IndexMeta::DataType::DT_INT16:
return std::string("INT16");
case IndexMeta::DataType::DT_INT8:
return std::string("INT8");
case IndexMeta::DataType::DT_BINARY32:
return std::string("Binary");
case IndexMeta::DataType::DT_BINARY64:
return std::string("Binary64");
case IndexMeta::DataType::DT_FP16:
return std::string("FP16");
default:
return std::string("NotSupportedType");
}
}
static std::string to_string(IndexMeta meta) {
char buffer[1024];
snprintf(buffer, 1024,
"IndexMeta: type[%s] method[%s] dimension[%u] element_size[%u]",
to_string(meta.data_type()).c_str(), meta.metric_name().c_str(),
meta.dimension(), meta.element_size());
return std::string(buffer);
}
static bool parse_from(const std::string &type, const std::string &method,
const std::string &vector_type, IndexMeta &meta) {
return parse_from(type, method, 0, vector_type, meta);
}
static bool parse_from(const std::string &type, const std::string &method,
const size_t dimension, const std::string &vector_type,
IndexMeta &meta) {
if (vector_type != "dense" && vector_type != "sparse") {
std::cerr << "vector type should be dense or sparse!!!" << std::endl;
return false;
}
auto feature_type = IndexMeta::DataType::DT_UNDEFINED;
if (type == std::string("float")) {
feature_type = IndexMeta::DataType::DT_FP32;
} else if (type == std::string("double")) {
feature_type = IndexMeta::DataType::DT_FP64;
} else if (type == std::string("int16")) {
feature_type = IndexMeta::DataType::DT_INT16;
} else if (type == std::string("int8")) {
feature_type = IndexMeta::DataType::DT_INT8;
} else if (type == std::string("binary")) {
feature_type = IndexMeta::DataType::DT_BINARY32;
} else if (type == std::string("binary64")) {
feature_type = IndexMeta::DataType::DT_BINARY64;
} else {
std::cerr << "Not supported type: " << type << std::endl;
return false;
}
meta.set_meta(feature_type, dimension);
ailego::Params params;
if (method == std::string("L2")) {
if (feature_type == IndexMeta::DataType::DT_FP32) {
meta.set_metric("SquaredEuclidean", 0, std::move(params));
} else if (feature_type == IndexMeta::DataType::DT_INT8) {
meta.set_metric("SquaredEuclidean", 0, std::move(params));
} else if (feature_type == IndexMeta::DataType::DT_FP16) {
meta.set_metric("SquaredEuclidean", 0, std::move(params));
} else {
std::cerr << "Not supported type(" << type << ") for L2" << std::endl;
return false;
}
} else if (method == std::string("IP")) {
if (feature_type == IndexMeta::DataType::DT_FP32) {
meta.set_metric("InnerProduct", 0, std::move(params));
} else if (feature_type == IndexMeta::DataType::DT_INT8) {
meta.set_metric("InnerProduct", 0, std::move(params));
} else if (feature_type == IndexMeta::DataType::DT_FP16) {
meta.set_metric("InnerProduct", 0, std::move(params));
} else {
std::cerr << "Not supported type(" << type << ") for IP" << std::endl;
return false;
}
} else {
std::cerr << "Not supported method: " << method << std::endl;
return false;
}
return true;
}
};
} // namespace core
} // namespace zvec
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <zvec/ailego/utility/type_helper.h>
namespace zvec {
namespace core {
const static std::string TAGLIST_HEADER_SEGMENT_NAME("local_taglists_header");
const static std::string TAGLIST_KEY_SEGMENT_NAME("local_taglists_key");
const static std::string TAGLIST_DATA_SEGMENT_NAME("local_taglists_data");
#pragma pack(4)
struct TagListHeader {
uint64_t num_vecs;
uint8_t meta_buf[252];
};
#pragma pack()
} // namespace core
} // namespace zvec
+1787
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+723
View File
@@ -0,0 +1,723 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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.
#include <iostream>
#include <set>
#include "gflags/gflags.h"
#include "zvec/core/framework/index_meta.h"
#include "index_meta_helper.h"
#include "txt_input_reader.h"
#include "vecs_common.h"
using namespace std;
using namespace zvec::core;
DEFINE_string(input, "input.txt", "txt input file");
DEFINE_string(input_first_sep, ";", "input first sep");
DEFINE_string(input_second_sep, " ", "input second sep");
DEFINE_string(output, "output.vecs", "vecs output file");
DEFINE_string(type, "float",
"available type: float, double, int16, int8, binary");
DEFINE_string(method, "L2", "available method: L2, IP");
DEFINE_int32(dimension, 256, "data dimension");
DEFINE_string(vector_type, "dense", "available type: dense, hybrid, sparse");
bool write_header_output(VecsHeader &header, const IndexMeta &meta,
size_t &total_writes, FILE *wfp) {
// write header
std::cout << "Begin to Write Header Section..." << std::endl;
std::string meta_buf;
meta.serialize(&meta_buf);
header.meta_size = meta_buf.size();
size_t wret = fwrite(&header, sizeof(header), 1, wfp);
if (wret != 1) {
cerr << "Write header error" << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(header);
std::cout << "Total Writes after Header Section: " << total_writes
<< std::endl
<< std::endl;
// write meta
std::cout << "Begin to Write Meta Section..." << std::endl;
wret = fwrite(meta_buf.c_str(), meta_buf.size(), 1, wfp);
if (wret != 1) {
cerr << "Write header meta_buf error" << endl;
fclose(wfp);
return false;
}
total_writes += meta_buf.size();
std::cout << "Total Writes after Meta Buf: " << total_writes << std::endl
<< std::endl;
return true;
}
bool write_header_output_sparse(VecsHeader &header, const IndexMeta &meta,
size_t &total_writes, FILE *wfp) {
// write header
std::cout << "Begin to Write Header Section..." << std::endl;
std::string meta_buf;
meta.serialize(&meta_buf);
header.meta_size = meta_buf.size();
size_t wret = fwrite(&header, sizeof(header), 1, wfp);
if (wret != 1) {
cerr << "Write header error" << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(header);
std::cout << "Total Writes after Header Section: " << total_writes
<< std::endl
<< std::endl;
// write meta
std::cout << "Begin to Write Sparse Meta Section..." << std::endl;
wret = fwrite(meta_buf.c_str(), meta_buf.size(), 1, wfp);
if (wret != 1) {
cerr << "Write header meta buf error" << endl;
fclose(wfp);
return false;
}
total_writes += meta_buf.size();
std::cout << "Total Writes after Meta Buf: " << total_writes << std::endl
<< std::endl;
return true;
}
template <typename T>
bool write_features_output(size_t vec_num, const vector<vector<T>> &features,
size_t &total_writes, FILE *wfp) {
// write dense vector
std::cout << "Begin to Write Dense Vector Section..." << std::endl;
for (size_t i = 0; i < vec_num; ++i) {
auto &feature = features[i];
size_t wret = fwrite(&feature[0], sizeof(T), feature.size(), wfp);
if (wret != feature.size()) {
cerr << "Write feature error. " << endl;
fclose(wfp);
return false;
}
total_writes += feature.size() * sizeof(T);
}
std::cout << "Total Writes after Dense Vector: " << total_writes << std::endl
<< std::endl;
return true;
}
bool write_keys_output(size_t vec_num, const vector<uint64_t> &keys,
size_t &total_writes, FILE *wfp) {
std::cout << "Begin to Write Key Section..." << std::endl;
for (size_t i = 0; i < vec_num; ++i) {
uint64_t key = keys[i];
size_t wret = fwrite(&key, sizeof(key), 1, wfp);
if (wret != 1) {
cerr << "Write key error. key:" << key << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(uint64_t);
}
std::cout << "Total Writes after Key Section: " << total_writes << std::endl
<< std::endl;
return true;
}
template <typename T>
bool write_sparse_features_output(size_t vec_num,
const vector<SparseData<T>> &sparse_data,
size_t &total_writes, FILE *wfp) {
std::set<uint32_t> sparse_dims;
uint32_t sparse_max_count = 0;
uint32_t sparse_min_count = -1U;
uint32_t sparse_total_count = 0;
// write sparse meta
std::cout << "Begin to Write Sparse Meta Section..." << std::endl;
size_t wret;
uint64_t offset = 0;
for (size_t i = 0; i < vec_num; ++i) {
wret = fwrite(&offset, sizeof(uint64_t), 1, wfp);
if (wret != 1) {
cerr << "Write sparse feature len error. " << endl;
fclose(wfp);
return false;
}
offset += sparse_data[i].get_len();
total_writes += sizeof(size_t);
}
std::cout << "Total Writes after Sparse Meta Section: " << total_writes
<< std::endl
<< std::endl;
std::cout << "Begin to Write Sparse Vector Section..." << std::endl;
for (size_t i = 0; i < vec_num; ++i) {
auto &sparse_one_data = sparse_data[i];
wret = fwrite(&(sparse_one_data.count), sizeof(uint32_t), 1, wfp);
if (wret != 1) {
cerr << "Write sparse feature count error. " << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(uint32_t);
wret = fwrite(&sparse_one_data.indices[0], sizeof(uint32_t),
sparse_one_data.indices.size(), wfp);
if (wret != sparse_one_data.indices.size()) {
cerr << "Write feature error. " << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(uint32_t) * sparse_one_data.indices.size();
// do some stat
for (size_t s = 0; s < sparse_one_data.indices.size(); ++s) {
sparse_dims.insert(sparse_one_data.indices[s]);
}
if (sparse_one_data.indices.size() > sparse_max_count) {
sparse_max_count = sparse_one_data.indices.size();
}
if (sparse_one_data.indices.size() < sparse_min_count) {
sparse_min_count = sparse_one_data.indices.size();
}
sparse_total_count += sparse_one_data.indices.size();
// //done
wret = fwrite(&sparse_one_data.features[0], sizeof(T),
sparse_one_data.features.size(), wfp);
if (wret != sparse_one_data.features.size()) {
cerr << "Write feature error. " << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(T) * sparse_one_data.features.size();
}
std::cout << "Total Writes after Sparse Vector Section: " << total_writes
<< std::endl
<< std::endl;
// for (auto itr=sparse_dims.begin(); itr!=sparse_dims.end(); ++itr) {
// std::cout << (*itr) << ",";
// }
// std::cout << std::endl;
std::cout << "Max Sparse Dimension Count: " << sparse_max_count << std::endl;
std::cout << "Min Sparse Dimension Count: " << sparse_min_count << std::endl;
std::cout << "Avg Sparse Dimension Count: " << sparse_total_count / vec_num
<< std::endl;
return true;
}
bool write_taglists_output(size_t vec_num,
const vector<vector<uint64_t>> &taglists,
size_t &total_writes, FILE *wfp) {
std::cout << "Begin to Write Tag List Section..." << std::endl;
// write tag list meta
std::cout << "Begin to Write Tag List Meta Section..." << std::endl;
size_t wret;
uint64_t offset = 0;
for (size_t i = 0; i < vec_num; ++i) {
wret = fwrite(&offset, sizeof(uint64_t), 1, wfp);
if (wret != 1) {
cerr << "Write tag list meta error. Rec no: " << i << endl;
fclose(wfp);
return false;
}
offset += taglists[i].size() * sizeof(uint64_t);
total_writes += sizeof(size_t);
}
std::cout << "Total Writes after Tag Meta Section: " << total_writes
<< std::endl
<< std::endl;
for (size_t i = 0; i < vec_num; ++i) {
std::vector<uint64_t> taglist = taglists[i];
uint64_t taglist_size = taglist.size();
wret = fwrite(&taglist_size, sizeof(uint64_t), 1, wfp);
if (wret != 1) {
cerr << "Write tag list size error. Rec no: " << i << endl;
fclose(wfp);
return false;
}
wret = fwrite(&(taglist[0]), sizeof(uint64_t), taglist.size(), wfp);
if (wret != taglist.size()) {
cerr << "Write tag list error. Rec no: " << i << endl;
fclose(wfp);
return false;
}
total_writes += sizeof(uint64_t) * taglist.size() + sizeof(uint64_t);
}
std::cout << "Total Writes after Tag List Section: " << total_writes
<< std::endl
<< std::endl;
return true;
}
template <typename T>
bool write_vecs_output_sparse(VecsHeader &header, const IndexMeta &meta,
const vector<uint64_t> &keys,
const vector<SparseData<T>> &sparse_data,
const vector<vector<uint64_t>> &taglists) {
if (keys.empty()) {
cerr << "keys is empty." << endl;
return false;
}
if (keys.size() != sparse_data.size()) {
cerr << "keys's size(" << keys.size()
<< ") is not equal to sparse data's size(" << sparse_data.size()
<< ")." << endl;
return false;
}
size_t vec_num = keys.size();
FILE *wfp = fopen(FLAGS_output.c_str(), "wb");
if (!wfp) {
cerr << "Open file error. " << FLAGS_output << endl;
return false;
}
size_t total_writes = 0;
std::cout << "------------------------" << std::endl;
std::cout << " Output Process " << std::endl;
std::cout << "------------------------" << std::endl;
// write sparse header
bool ret = write_header_output_sparse(header, meta, total_writes, wfp);
if (!ret) {
cerr << "write header error! " << endl;
return false;
}
// write keys
ret = write_keys_output(vec_num, keys, total_writes, wfp);
if (!ret) {
cerr << "write keys error! " << endl;
return false;
}
// write sparse features
ret = write_sparse_features_output(vec_num, sparse_data, total_writes, wfp);
if (!ret) {
cerr << "write sparse features error! " << endl;
return false;
}
if ((header.bitmap & (1ULL << BITMAP_INDEX_TAGLIST)) != 0) {
// write tag lists features
ret = write_taglists_output(vec_num, taglists, total_writes, wfp);
if (!ret) {
cerr << "write tag lists error! " << endl;
return false;
}
}
std::cout << "------------------------" << std::endl;
std::cout << " Output Done " << std::endl;
std::cout << "------------------------" << std::endl;
fclose(wfp);
return true;
}
template <typename T>
bool write_vecs_output(VecsHeader &header, const IndexMeta &meta,
const vector<uint64_t> &keys,
const vector<vector<T>> &features,
const vector<SparseData<T>> &sparse_data,
const vector<vector<uint64_t>> &taglists) {
if (keys.empty()) {
cerr << "keys is empty." << endl;
return false;
}
if (keys.size() != features.size()) {
cerr << "keys's size(" << keys.size()
<< ") is not equal to features's size(" << features.size() << ")."
<< endl;
return false;
}
size_t vec_num = header.num_vecs;
FILE *wfp = fopen(FLAGS_output.c_str(), "wb");
if (!wfp) {
cerr << "Open file error. " << FLAGS_output << endl;
return false;
}
size_t total_writes = 0;
std::cout << "------------------------" << std::endl;
std::cout << " Output Process " << std::endl;
std::cout << "------------------------" << std::endl;
// write header
bool ret = write_header_output(header, meta, total_writes, wfp);
if (!ret) {
cerr << "write header error! " << endl;
return false;
}
// write features
ret = write_features_output(vec_num, features, total_writes, wfp);
if (!ret) {
cerr << "write features error! " << endl;
return false;
}
// write keys
ret = write_keys_output(vec_num, keys, total_writes, wfp);
if (!ret) {
cerr << "write keys error! " << endl;
return false;
}
// write sparse features
if ((header.bitmap & (1ULL << BITMAP_INDEX_SPARSE)) != 0) {
ret = write_sparse_features_output(vec_num, sparse_data, total_writes, wfp);
if (!ret) {
cerr << "write sparse features error! " << endl;
return false;
}
}
if ((header.bitmap & (1ULL << BITMAP_INDEX_TAGLIST)) != 0) {
// write tag lists features
ret = write_taglists_output(vec_num, taglists, total_writes, wfp);
if (!ret) {
cerr << "write tag lists error! " << endl;
return false;
}
}
std::cout << "------------------------" << std::endl;
std::cout << " Output Done " << std::endl;
std::cout << "------------------------" << std::endl;
fclose(wfp);
return true;
}
template <typename T>
bool compute_offset(uint64_t num_vecs, const IndexMeta &meta,
const vector<uint64_t> & /*keys*/,
const vector<vector<T>> & /*features*/,
const vector<SparseData<T>> &sparse_data,
const vector<std::vector<uint64_t>> &taglists,
uint64_t &key_offset, uint64_t &feature_offset,
uint64_t &sparse_offset, uint64_t &taglist_offset,
uint64_t &key_size, uint64_t &feature_size,
uint64_t &sparse_size, uint64_t &taglist_size) {
size_t total_offset = 0;
feature_offset = 0;
feature_size = num_vecs * meta.element_size();
total_offset += feature_size;
key_offset = total_offset;
key_size = num_vecs * sizeof(uint64_t);
total_offset += key_size;
if (sparse_data.size() != 0) {
sparse_offset = total_offset;
size_t data_offset = num_vecs * sizeof(uint64_t);
for (size_t i = 0; i < sparse_data.size(); ++i) {
data_offset += sizeof(uint32_t) +
sparse_data[i].count * (sizeof(uint32_t) + sizeof(T));
}
sparse_size = data_offset;
total_offset += sparse_size;
} else {
sparse_offset = -1LLU;
sparse_size = 0;
}
if (taglists.size() != 0) {
taglist_offset = total_offset;
size_t data_offset = num_vecs * sizeof(uint64_t);
for (size_t i = 0; i < taglists.size(); ++i) {
data_offset += sizeof(uint64_t) + taglists[i].size() * sizeof(uint64_t);
}
taglist_size = data_offset;
} else {
taglist_offset = -1LLU;
taglist_size = 0;
}
return true;
}
template <typename T>
bool compute_sparse_offset(uint64_t num_vecs, const IndexMeta & /*meta*/,
const vector<uint64_t> & /*keys*/,
const vector<SparseData<T>> &sparse_data,
const vector<std::vector<uint64_t>> &taglists,
uint64_t &key_offset, uint64_t &sparse_offset,
uint64_t &taglist_offset, uint64_t &key_size,
uint64_t &sparse_size, uint64_t &taglist_size) {
size_t total_offset = 0;
key_offset = 0;
key_size = num_vecs * sizeof(uint64_t);
total_offset += num_vecs * sizeof(uint64_t);
sparse_offset = total_offset;
size_t data_offset = num_vecs * sizeof(uint64_t);
for (size_t i = 0; i < sparse_data.size(); ++i) {
data_offset += sizeof(uint32_t) +
sparse_data[i].count * (sizeof(uint32_t) + sizeof(T));
}
sparse_size = data_offset;
total_offset += sparse_size;
if (taglists.size() != 0) {
taglist_offset = total_offset;
data_offset = num_vecs * sizeof(uint64_t);
for (size_t i = 0; i < taglists.size(); ++i) {
data_offset += sizeof(uint64_t) + taglists[i].size() * sizeof(uint64_t);
}
taglist_size = data_offset;
} else {
taglist_offset = -1LLU;
taglist_size = 0;
}
return true;
}
template <typename T>
bool process(void) {
if (FLAGS_vector_type == "sparse") {
std::cout << "------------------------" << std::endl;
std::cout << " Vector Type: sparse " << std::endl;
std::cout << "------------------------" << std::endl;
IndexMeta meta;
if (!IndexMetaHelper::parse_from(FLAGS_type, FLAGS_method,
FLAGS_vector_type, meta)) {
cerr << "Index meta parse error." << endl;
return false;
}
cerr << IndexMetaHelper::to_string(meta) << endl;
TxtInputReader<T> reader;
vector<uint64_t> keys;
vector<SparseData<T>> sparse_data;
vector<std::vector<uint64_t>> taglists;
bool ret = reader.load_record_sparse(FLAGS_input, FLAGS_input_first_sep,
FLAGS_input_second_sep, keys,
sparse_data, taglists);
if (!ret) {
cerr << "Read record failed" << endl;
return false;
}
if (sparse_data.size() == 0) {
cerr << "empty sparse data!" << endl;
return false;
}
uint64_t num_vecs = keys.size();
uint64_t key_offset{-1LLU}, sparse_offset{-1LLU}, taglist_offset{-1LLU};
uint64_t key_size{0}, sparse_size{0}, taglist_size{0};
compute_sparse_offset(num_vecs, meta, keys, sparse_data, taglists,
key_offset, sparse_offset, taglist_offset, key_size,
sparse_size, taglist_size);
VecsHeader header;
header.num_vecs = keys.size();
header.meta_size_v1 = 0;
header.version = 1;
header.bitmap = 0;
header.key_offset = key_offset;
header.dense_offset = -1LLU;
header.sparse_offset = sparse_offset;
header.taglist_offset = taglist_offset;
header.key_size = key_size;
header.dense_size = 0;
header.sparse_size = sparse_size;
header.taglist_size = taglist_size;
header.bitmap |= (1 << BITMAP_INDEX_KEY);
header.bitmap |= (1 << BITMAP_INDEX_SPARSE);
if (taglist_offset != -1LLU) {
header.bitmap |= (1 << BITMAP_INDEX_TAGLIST);
}
ret = write_vecs_output_sparse(header, meta, keys, sparse_data, taglists);
if (!ret) {
cerr << "write vecs output failed" << endl;
return false;
}
} else {
std::cout << "------------------------" << std::endl;
std::cout << " Vector Type: " << FLAGS_vector_type << std::endl;
std::cout << "------------------------" << std::endl;
IndexMeta meta;
if (!IndexMetaHelper::parse_from(FLAGS_type, FLAGS_method, FLAGS_dimension,
FLAGS_vector_type, meta)) {
cerr << "Index meta parse error." << endl;
return false;
}
cerr << IndexMetaHelper::to_string(meta) << endl;
TxtInputReader<T> reader;
vector<uint64_t> keys;
vector<vector<T>> features;
vector<SparseData<T>> sparse_data;
vector<std::vector<uint64_t>> taglists;
bool ret = reader.load_record(FLAGS_input, FLAGS_input_first_sep,
FLAGS_input_second_sep, FLAGS_dimension, keys,
features, sparse_data, taglists);
if (!ret) {
cerr << "Read record failed" << endl;
return false;
}
uint64_t num_vecs = keys.size();
uint64_t key_offset{-1LLU}, features_offset{-1LLU}, sparse_offset{-1LLU},
taglist_offset{-1LLU};
uint64_t key_size{0}, feature_size{0}, sparse_size{0}, taglist_size{0};
compute_offset(num_vecs, meta, keys, features, sparse_data, taglists,
key_offset, features_offset, sparse_offset, taglist_offset,
key_size, feature_size, sparse_size, taglist_size);
VecsHeader header;
header.num_vecs = num_vecs;
header.meta_size_v1 = 0;
header.version = 1;
header.bitmap = 0;
header.key_offset = key_offset;
header.dense_offset = features_offset;
header.sparse_offset = sparse_offset;
header.taglist_offset = taglist_offset;
header.key_size = key_size;
header.dense_size = feature_size;
header.sparse_size = sparse_size;
header.taglist_size = taglist_size;
header.bitmap |= (1 << BITMAP_INDEX_KEY);
header.bitmap |= (1 << BITMAP_INDEX_DENSE);
if (sparse_offset != -1LLU) {
header.bitmap |= (1 << BITMAP_INDEX_SPARSE);
}
if (taglist_offset != -1LLU) {
header.bitmap |= (1 << BITMAP_INDEX_TAGLIST);
}
ret =
write_vecs_output(header, meta, keys, features, sparse_data, taglists);
if (!ret) {
cerr << "write vecs output failed" << endl;
return false;
}
}
return true;
}
int main(int argc, char *argv[]) {
// gflags
gflags::SetUsageMessage("Usage: txt2vecs [options]");
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_type == "float") {
if (!process<float>()) {
return -1;
}
} else if (FLAGS_type == "double") {
if (!process<double>()) {
return -1;
}
} else if (FLAGS_type == "int16") {
if (!process<int16_t>()) {
return -1;
}
} else if (FLAGS_type == "int8") {
if (!process<int8_t>()) {
return -1;
}
} else if (FLAGS_type == "binary") {
if (!process<uint32_t>()) {
return -1;
}
} else if (FLAGS_type == "binary64") {
if (!process<uint64_t>()) {
return -1;
}
} else {
cerr << "Can not recognize type: " << FLAGS_type << endl;
return -1;
}
return 0;
}
+549
View File
@@ -0,0 +1,549 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <string.h>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <zvec/ailego/utility/string_helper.h>
namespace zvec {
namespace core {
template <typename T>
struct SparseData {
public:
SparseData(uint32_t count_in, std::vector<uint32_t> &indices_in,
std::vector<T> &features_in)
: count(count_in),
indices(std::move(indices_in)),
features(std::move(features_in)) {}
SparseData(uint32_t count_in, std::vector<uint32_t> &&indices_in,
std::vector<T> &&features_in)
: count(count_in),
indices(std::move(indices_in)),
features(std::move(features_in)) {}
public:
uint64_t get_len() const {
return sizeof(uint32_t) + sizeof(uint32_t) * indices.size() +
sizeof(T) * features.size();
}
public:
uint32_t count;
std::vector<uint32_t> indices;
std::vector<T> features;
};
// support type: float, binary, int16, int8
template <typename T>
class TxtInputReader {
public:
bool load_query(const std::string &query_file, const std::string &first_sep,
const std::string &second_sep,
std::vector<std::vector<T>> &features,
std::vector<SparseData<T>> &sparse_data,
std::vector<std::vector<uint64_t>> &taglists) {
std::fstream qf(query_file, std::ios::in);
if (!qf.is_open()) {
std::cerr << "open query file failed! [" << query_file << "]"
<< std::endl;
return false;
}
bool ret;
std::string buffer;
while (getline(qf, buffer)) {
buffer.erase(buffer.find_last_not_of('\n') + 1);
if (buffer.empty()) {
continue;
}
std::vector<std::string> res;
ailego::StringHelper::Split(buffer, first_sep, &res);
if (res.empty()) {
continue;
}
std::string feature_str = res[0];
if (res.size() > 1) {
feature_str = res[1];
}
std::vector<T> feature;
size_t dimension = 0;
ret = load_from_string(feature_str, second_sep, feature, &dimension);
if (!ret) {
return false;
}
features.emplace_back(feature);
uint64_t key = atol(res[0].c_str());
// load sparse feature
uint32_t sparse_count = 0;
std::vector<uint32_t> sparse_indices;
std::vector<T> sparse_feature;
if (res.size() >= 3) {
ret = load_from_string_sparse(key, res[2], second_sep, sparse_indices,
sparse_feature, &sparse_count);
if (!ret) {
std::cerr << "load sparse failed for key: " << key << std::endl;
return false;
}
}
sparse_data.emplace_back(sparse_count, std::move(sparse_indices),
std::move(sparse_feature));
if (res.size() >= 4) {
std::vector<uint64_t> taglist;
size_t tag_count = 0;
ret = load_tags_from_string(res[4], second_sep, taglist, &tag_count);
if (!ret) {
std::cerr << "load tags failed for key: " << key << std::endl;
return false;
}
taglists.emplace_back(taglist);
}
}
qf.close();
if (features.size() == 0) {
std::cerr << "Read query size is 0" << std::endl;
return false;
}
return true;
}
bool load_record(const std::string &input, const std::string &first_sep,
const std::string &second_sep, const size_t dimension,
std::vector<uint64_t> &keys,
std::vector<std::vector<T>> &features,
std::vector<SparseData<T>> &sparse_data,
std::vector<std::vector<uint64_t>> &taglists) {
std::fstream qf(input, std::ios::in);
if (!qf.is_open()) {
std::cerr << "open file failed! [" << input << "]" << std::endl;
return false;
}
bool ret;
uint32_t count = 0;
std::string buffer;
while (getline(qf, buffer)) {
buffer.erase(buffer.find_last_not_of('\n') + 1);
if (buffer.empty()) {
continue;
}
std::vector<std::string> res;
ailego::StringHelper::Split(buffer, first_sep, &res);
if (res.size() < 2) {
std::cerr << "skip record : " << buffer << std::endl;
continue;
}
std::vector<T> feature;
size_t real_dim = 0;
// load sparse feature
uint32_t sparse_count = 0;
std::vector<uint32_t> sparse_indices;
std::vector<T> sparse_feature;
uint64_t key = atol(res[0].c_str());
// load dense feature
ret = load_from_string(res[1], second_sep, feature, &real_dim);
if (!ret) {
return false;
}
if (real_dim != dimension) {
std::cerr << "real dim (" << real_dim << ") is not equal to dimension("
<< dimension << ") key : " << res[0] << std::endl;
continue;
}
features.emplace_back(feature);
keys.emplace_back(key);
if (res.size() >= 3) {
ret = load_from_string_sparse(key, res[2], second_sep, sparse_indices,
sparse_feature, &sparse_count);
if (!ret) {
std::cerr << "load sparse failed for key: " << key << std::endl;
return false;
}
sparse_data.emplace_back(sparse_count, std::move(sparse_indices),
std::move(sparse_feature));
}
if (res.size() >= 4) {
std::vector<uint64_t> taglist;
size_t tag_count = 0;
ret = load_tags_from_string(res[3], second_sep, taglist, &tag_count);
if (!ret) {
std::cerr << "load tags failed for key: " << key << std::endl;
return false;
}
taglists.emplace_back(taglist);
}
count++;
if (count % 1000000 == 0) {
std::cout << "processed " << count << " records!" << std::endl;
}
}
qf.close();
if (keys.size() == 0) {
std::cerr << "Reading nothing from input" << std::endl;
return false;
}
return true;
}
bool load_record_sparse(const std::string &input,
const std::string &first_sep,
const std::string &second_sep,
std::vector<uint64_t> &keys,
std::vector<SparseData<T>> &sparse_data,
std::vector<std::vector<uint64_t>> &taglists) {
std::fstream qf(input, std::ios::in);
if (!qf.is_open()) {
std::cerr << "open file failed! [" << input << "]" << std::endl;
return false;
}
bool ret;
uint32_t count = 0;
std::string buffer;
while (getline(qf, buffer)) {
buffer.erase(buffer.find_last_not_of('\n') + 1);
if (buffer.empty()) {
continue;
}
std::vector<std::string> res;
ailego::StringHelper::Split(buffer, first_sep, &res);
if (res.size() < 2) {
std::cerr << "skip record : " << buffer << std::endl;
continue;
}
uint64_t key = atol(res[0].c_str());
// load sparse feature
uint32_t sparse_count = 0;
std::vector<uint32_t> sparse_indices;
std::vector<T> sparse_feature;
if (res.size() <= 2) {
std::cerr << "field erorr, key: " << key << std::endl;
continue;
}
ret = load_from_string_sparse(key, res[2], second_sep, sparse_indices,
sparse_feature, &sparse_count);
if (!ret) {
std::cerr << "load sparse failed for key: " << key << std::endl;
return false;
}
keys.emplace_back(key);
sparse_data.emplace_back(sparse_count, std::move(sparse_indices),
std::move(sparse_feature));
if (res.size() >= 4) {
std::vector<uint64_t> taglist;
size_t tag_count;
ret = load_tags_from_string(res[4], second_sep, taglist, &tag_count);
if (!ret) {
std::cerr << "load tags failed for key: " << key << std::endl;
return false;
}
taglists.emplace_back(taglist);
}
count++;
if (count % 1000000 == 0) {
std::cout << "processed " << count << " records!" << std::endl;
}
}
qf.close();
if (keys.size() == 0) {
std::cerr << "Reading nothing from input" << std::endl;
return false;
}
return true;
}
template <typename U>
bool load_from_string(const std::string &record,
const std::string &second_sep, std::vector<U> &data,
size_t *count) {
ailego::StringHelper::Split(record, second_sep, &data, true);
*count = data.size();
return true;
}
bool load_scores_from_string(const std::string &record,
const std::string &second_sep,
std::vector<float> &data, size_t *count) {
ailego::StringHelper::Split(record, second_sep, &data, true);
*count = data.size();
return true;
}
bool load_ids_from_string(const std::string &record,
const std::string &second_sep,
std::vector<uint64_t> &data, size_t *count) {
ailego::StringHelper::Split(record, second_sep, &data, true);
*count = data.size();
return true;
}
bool load_tags_from_string(const std::string &record,
const std::string &second_sep,
std::vector<uint64_t> &tags, size_t *count) {
ailego::StringHelper::Split(record, second_sep, &tags, true);
*count = tags.size();
// order tags
sort(tags.begin(), tags.end());
return true;
}
// overloading for binary
bool load_from_string(const std::string &record,
const std::string &second_sep,
std::vector<uint32_t> &data, size_t *count) {
// fetch split value from text file
std::vector<uint8_t> vec;
ailego::StringHelper::Split(record, second_sep, &vec, true);
if (vec.size() == 0) {
std::cerr << "Binary vector size is 0" << std::endl;
return false;
}
if (vec.size() % 32 != 0) {
std::cerr << "Binary vector size must be 32_X" << std::endl;
return false;
}
// compact into uint32_t
size_t sz = vec.size();
std::vector<uint8_t> tmp;
for (size_t i = 0; i < sz; i += 8) {
uint8_t v = 0;
v |= (vec[i] & 0x01) << 7;
v |= (vec[i + 1] & 0x01) << 6;
v |= (vec[i + 2] & 0x01) << 5;
v |= (vec[i + 3] & 0x01) << 4;
v |= (vec[i + 4] & 0x01) << 3;
v |= (vec[i + 5] & 0x01) << 2;
v |= (vec[i + 6] & 0x01) << 1;
v |= (vec[i + 7] & 0x01) << 0;
tmp.push_back(v);
}
data.resize(sz / 32);
memcpy(&data[0], &tmp[0], tmp.size());
*count = sz;
return true;
}
// overloading for binary
bool load_from_string(const std::string &record,
const std::string &second_sep,
std::vector<uint64_t> &data, size_t *count) {
// fetch split value from text file
std::vector<uint8_t> vec;
ailego::StringHelper::Split(record, second_sep, &vec);
if (vec.size() == 0) {
std::cerr << "Binary vector size is 0" << std::endl;
return false;
}
if (vec.size() % 64 != 0) {
std::cerr << "Binary vector size must be 32_X" << std::endl;
return false;
}
// compact into uint64_t
size_t sz = vec.size();
std::vector<uint8_t> tmp;
for (size_t i = 0; i < sz; i += 8) {
uint8_t v = 0;
v |= (vec[i] & 0x01) << 7;
v |= (vec[i + 1] & 0x01) << 6;
v |= (vec[i + 2] & 0x01) << 5;
v |= (vec[i + 3] & 0x01) << 4;
v |= (vec[i + 4] & 0x01) << 3;
v |= (vec[i + 5] & 0x01) << 2;
v |= (vec[i + 6] & 0x01) << 1;
v |= (vec[i + 7] & 0x01) << 0;
tmp.push_back(v);
}
data.resize(sz / 64);
memcpy(&data[0], &tmp[0], tmp.size());
*count = sz;
return true;
}
bool load_from_string_sparse(uint64_t key, const std::string &record,
const std::string &second_sep,
std::vector<uint32_t> &sparse_indices,
std::vector<T> &sparse_feature,
uint32_t *sparse_count) {
const std::string sparse_sep = ":";
std::vector<std::string> res;
ailego::StringHelper::Split(record, sparse_sep, &res);
if (res.size() == 2) {
ailego::StringHelper::Split(res[0], second_sep, &sparse_indices);
ailego::StringHelper::Split(res[1], second_sep, &sparse_feature);
uint32_t index_count = sparse_indices.size();
uint32_t feature_count = sparse_feature.size();
if (feature_count == index_count) {
*sparse_count = feature_count;
} else {
std::cerr << "sparse feature count (" << feature_count
<< ") is not equal with sparse index count(" << index_count
<< ") key : " << key << std::endl;
*sparse_count = 0;
return false;
}
// check order
for (size_t i = 1; i < sparse_indices.size(); ++i) {
if (sparse_indices[i - 1] >= sparse_indices[i]) {
std::cerr << "sparse indices not ordered, key : " << key
<< ", dim info: [" << sparse_indices[i - 1] << ", "
<< sparse_indices[i] << "]" << std::endl;
return false;
}
}
}
return true;
}
// LINE FORMAT is as follows:
// key:key0 key1 key2 ... keyN:score0 score1 score2 ... scoreN
bool load_external_gt(
const std::string &input, const std::string &first_sep,
const std::string &second_sep,
std::vector<std::vector<std::pair<uint64_t, float>>> &ground_truth) {
std::fstream gf(input, std::ios::in);
if (!gf.is_open()) {
std::cerr << "open file failed! [" << input << "]" << std::endl;
return false;
}
uint32_t count = 0;
std::string buffer;
while (getline(gf, buffer)) {
buffer.erase(buffer.find_last_not_of('\n') + 1);
if (buffer.empty()) {
continue;
}
std::vector<std::string> res;
ailego::StringHelper::Split(buffer, first_sep, &res);
if (res.size() < 2) {
std::cerr << "skip record : " << buffer << std::endl;
continue;
}
// uint64_t main_key = std::strtoll(res[0].c_str(), NULL, 10);
if (res.size() == 2) {
std::vector<uint64_t> keys;
size_t key_num = 0;
load_ids_from_string(res[1], second_sep, keys, &key_num);
std::vector<std::pair<uint64_t, float>> one_groud_truth;
for (size_t i = 0; i < keys.size(); ++i) {
one_groud_truth.push_back(std::make_pair(keys[i], 0.0f));
}
ground_truth.push_back(std::move(one_groud_truth));
} else {
std::vector<uint64_t> keys;
size_t key_num = 0;
load_ids_from_string(res[1], second_sep, keys, &key_num);
std::vector<float> scores;
size_t score_num = 0;
load_scores_from_string(res[2], second_sep, scores, &score_num);
if (key_num != score_num) {
std::cerr << "key num (" << key_num << ") is not equal to ("
<< score_num << "), line data:" << buffer << std::endl;
continue;
}
std::vector<std::pair<uint64_t, float>> one_groud_truth;
for (size_t i = 0; i < keys.size(); ++i) {
one_groud_truth.push_back(std::make_pair(keys[i], scores[i]));
}
ground_truth.push_back(std::move(one_groud_truth));
}
count++;
if (count % 1000000 == 0) {
std::cout << "processed " << count << " records!" << std::endl;
}
}
gf.close();
if (ground_truth.size() == 0) {
std::cerr << "Reading nothing from input" << std::endl;
return false;
}
return true;
}
};
} // namespace core
} // namespace zvec
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <cstdint>
namespace zvec {
namespace core {
enum VecsBitMapIndex {
BITMAP_INDEX_KEY = 0,
BITMAP_INDEX_DENSE = 1,
BITMAP_INDEX_SPARSE = 2,
BITMAP_INDEX_TAGLIST = 4
};
#pragma pack(4)
struct VecsHeader {
uint64_t num_vecs;
uint16_t meta_size_v1;
uint16_t version;
uint32_t meta_size;
uint64_t bitmap; // set for data section
uint64_t key_offset; // offset for key
uint64_t key_size; // size for key
uint64_t dense_offset; // offset for dense
uint64_t dense_size; // size for dense
uint64_t sparse_offset; // offset for sparse
uint64_t sparse_size; // size for sparse
uint64_t partition_offset; // offset for partition
uint64_t partition_size; // size for partition
uint64_t taglist_offset; // offset for taglist
uint64_t taglist_size; // size for taglist
uint8_t *meta_buf() {
return reinterpret_cast<uint8_t *>(this + 1);
}
const uint8_t *meta_buf() const {
return reinterpret_cast<const uint8_t *>(this + 1);
}
};
#pragma pack()
} // namespace core
} // namespace zvec
+433
View File
@@ -0,0 +1,433 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <string>
#include <unordered_map>
#include <zvec/ailego/container/params.h>
#include "zvec/core/framework/index_error.h"
#include "zvec/core/framework/index_holder.h"
#include "zvec/core/framework/index_provider.h"
#include "zvec/core/framework/index_storage.h"
#include "vecs_reader.h"
namespace zvec {
namespace core {
/*!
* Vecs Index Holder
* framwork will use IndexHolder in this way:
* for (iter = create_iterator(); iter->is_valid(); iter->next()) {
* key = iter->key();
* data = iter->data();
* }
*/
class VecsIndexHolder : public IndexProvider {
public:
typedef std::shared_ptr<VecsIndexHolder> Pointer;
bool load(const std::string &file_path) {
if (!vecs_reader_.load(file_path)) {
return false;
}
build_key_index_map();
return true;
}
const IndexMeta &index_meta(void) const {
return vecs_reader_.index_meta();
}
void set_metric(const std::string &name, const ailego::Params &params) {
vecs_reader_.set_metric(name, params);
}
/*!
* Index Holder Iterator
*/
class Iterator : public IndexHybridHolder::Iterator {
public:
//! Constructor
Iterator(const VecsIndexHolder &holder, uint32_t cursor)
: cursor_(cursor),
vecs_reader_(holder.vecs_reader_),
stop_(holder.stop_) {}
//! Test if the iterator is valid
virtual bool is_valid(void) const override {
return !stop_ && cursor_ < vecs_reader_.num_vecs();
}
//! Retrieve primary key
virtual uint64_t key(void) const override {
return vecs_reader_.get_key(cursor_);
}
//! Retrieve pointer of data
virtual const void *data() const override {
return vecs_reader_.get_vector(cursor_);
}
//! Retrieve primary key
virtual uint32_t sparse_count() const override {
return vecs_reader_.get_sparse_count(cursor_);
}
//! Retrieve primary key
virtual const uint32_t *sparse_indices() const override {
return vecs_reader_.get_sparse_indices(cursor_);
}
//! Retrieve primary key
virtual const void *sparse_data() const override {
return vecs_reader_.get_sparse_data(cursor_);
}
//! Next iterator
virtual void next(void) override {
++cursor_;
}
//! Reset the iterator
virtual void reset(void) {
cursor_ = 0;
}
private:
size_t cursor_;
const VecsReader &vecs_reader_;
const bool &stop_;
};
virtual IndexHolder::Iterator::Pointer create_iterator(void) override {
// make sure iter has value whenn create_iterator finished
IndexHolder::Iterator::Pointer iter(
new VecsIndexHolder::Iterator(*this, start_cursor_));
return iter;
}
virtual IndexHybridHolder::Iterator::Pointer create_hybrid_iterator(void) {
// make sure iter has value whenn create_iterator finished
IndexHybridHolder::Iterator::Pointer iter(
new VecsIndexHolder::Iterator(*this, start_cursor_));
return iter;
}
//! Retrieve count of elements in holder
virtual size_t count(void) const override {
return max_doc_count_ != 0
? std::min(max_doc_count_, vecs_reader_.num_vecs())
: vecs_reader_.num_vecs();
}
//! Retrieve dimension
virtual size_t dimension(void) const override {
return vecs_reader_.index_meta().dimension();
}
//! Retrieve type information
virtual IndexMeta::DataType data_type(void) const override {
return vecs_reader_.index_meta().data_type();
}
//! Retrieve element size in bytes
virtual size_t element_size(void) const override {
return vecs_reader_.index_meta().element_size();
}
//! Retrieve if it can multi-pass
virtual bool multipass(void) const override {
return true;
}
void stop(void) {
stop_ = true;
}
uint64_t get_num_vecs() const {
return vecs_reader_.num_vecs();
}
uint64_t get_key(size_t idx) const {
return vecs_reader_.get_key(idx);
}
uint32_t get_sparse_count(size_t idx) const {
return vecs_reader_.get_sparse_count(idx);
}
const uint32_t *get_sparse_indices(size_t idx) const {
return vecs_reader_.get_sparse_indices(idx);
}
const void *get_sparse_data(size_t idx) const {
return vecs_reader_.get_sparse_data(idx);
}
void set_start_cursor(uint32_t index) {
start_cursor_ = index;
}
void set_max_doc_count(size_t value) {
max_doc_count_ = value;
}
uint32_t start_cursor() const {
return start_cursor_;
}
size_t total_sparse_count(void) const {
return vecs_reader_.get_total_sparse_count();
}
bool has_taglist() const {
return vecs_reader_.has_taglist();
}
uint64_t get_taglist_count(size_t index) const {
return vecs_reader_.get_taglist_count(index);
}
const void *get_taglist(size_t index) const {
return vecs_reader_.get_taglist(index);
}
const void *get_taglist_data(size_t &size) const {
return vecs_reader_.get_taglist_data(size);
}
const void *get_key_base() const {
return vecs_reader_.key_base();
}
const void *get_vector_by_index(size_t idx) const {
return vecs_reader_.get_vector(idx);
}
public: // IndexProvider interface implementation
//! Retrieve a vector using a primary key
const void *get_vector(const uint64_t key) const override {
auto it = key_to_index_map_.find(key);
if (it == key_to_index_map_.end()) {
return nullptr;
}
return vecs_reader_.get_vector(it->second);
}
//! Retrieve a vector using a primary key
virtual int get_vector(const uint64_t key,
IndexStorage::MemoryBlock &block) const override {
const void *vector = get_vector(key);
if (vector == nullptr) {
return IndexError_NoExist;
}
block.reset((void *)vector);
return 0;
}
//! Retrieve the owner class
virtual const std::string &owner_class(void) const override {
static std::string owner_class_name = "VecsIndexHolder";
return owner_class_name;
}
private:
//! Build key to index mapping
void build_key_index_map() {
key_to_index_map_.clear();
size_t num_vecs = vecs_reader_.num_vecs();
for (size_t i = 0; i < num_vecs; ++i) {
uint64_t key = vecs_reader_.get_key(i);
key_to_index_map_[key] = i;
}
}
bool stop_{false};
uint32_t start_cursor_{0};
VecsReader vecs_reader_;
size_t max_doc_count_{0};
std::unordered_map<uint64_t, size_t> key_to_index_map_;
};
/*!
* Vecs Index Sparse Holder
* framwork will use IndexHolder in this way:
* for (iter = create_iterator(); iter->is_valid(); iter->next()) {
* key = iter->key();
* data = iter->sparse_data();
* }
*/
class VecsIndexSparseHolder : public IndexSparseHolder {
public:
typedef std::shared_ptr<VecsIndexSparseHolder> Pointer;
bool load(const std::string &file_path) {
return vecs_reader_.load(file_path);
}
const IndexMeta &index_meta(void) const {
return vecs_reader_.index_meta();
}
void set_metric(const std::string &name, const ailego::Params &params) {
vecs_reader_.set_metric(name, params);
}
/*!
* Index Holder Iterator
*/
class Iterator : public IndexSparseHolder::Iterator {
public:
//! Constructor
Iterator(const VecsIndexSparseHolder &holder, uint32_t cursor)
: cursor_(cursor),
vecs_reader_(holder.vecs_reader_),
stop_(holder.stop_) {}
//! Test if the iterator is valid
virtual bool is_valid(void) const override {
return !stop_ && cursor_ < vecs_reader_.num_vecs();
}
//! Retrieve primary key
virtual uint64_t key(void) const override {
return vecs_reader_.get_key(cursor_);
}
//! Retrieve primary key
virtual uint32_t sparse_count() const override {
return vecs_reader_.get_sparse_count(cursor_);
}
//! Retrieve primary key
virtual const uint32_t *sparse_indices() const override {
return vecs_reader_.get_sparse_indices(cursor_);
}
//! Retrieve primary key
virtual const void *sparse_data() const override {
return vecs_reader_.get_sparse_data(cursor_);
}
//! Next iterator
virtual void next(void) override {
++cursor_;
}
//! Reset the iterator
virtual void reset(void) {
cursor_ = 0;
}
private:
size_t cursor_;
const SparseVecsReader &vecs_reader_;
const bool &stop_;
};
virtual IndexSparseHolder::Iterator::Pointer create_iterator(void) override {
// make sure iter has value whenn create_iterator finished
IndexSparseHolder::Iterator::Pointer iter(
new VecsIndexSparseHolder::Iterator(*this, start_cursor_));
return iter;
}
//! Retrieve count of elements in holder
virtual size_t count(void) const override {
return max_doc_count_ != 0
? std::min(max_doc_count_, vecs_reader_.num_vecs())
: vecs_reader_.num_vecs();
}
//! Retrieve type information
virtual IndexMeta::DataType data_type(void) const override {
return vecs_reader_.index_meta().data_type();
}
//! Retrieve if it can multi-pass
virtual bool multipass(void) const override {
return true;
}
void stop(void) {
stop_ = true;
}
uint64_t get_key(size_t idx) const {
return vecs_reader_.get_key(idx);
}
uint32_t get_sparse_count(size_t idx) const {
return vecs_reader_.get_sparse_count(idx);
}
const uint32_t *get_sparse_indices(size_t idx) const {
return vecs_reader_.get_sparse_indices(idx);
}
const void *get_sparse_data(size_t idx) const {
return vecs_reader_.get_sparse_data(idx);
}
void set_start_cursor(uint32_t index) {
start_cursor_ = index;
}
void set_max_doc_count(size_t value) {
max_doc_count_ = value;
}
uint64_t get_num_vecs() const {
return vecs_reader_.num_vecs();
}
uint32_t start_cursor() const {
return start_cursor_;
}
size_t total_sparse_count(void) const override {
return vecs_reader_.get_total_sparse_count();
}
bool has_taglist() const {
return vecs_reader_.has_taglist();
}
uint64_t get_taglist_count(size_t index) const {
return vecs_reader_.get_taglist_count(index);
}
const void *get_taglist(size_t index) const {
return vecs_reader_.get_taglist(index);
}
const void *get_taglist_data(size_t &size) const {
return vecs_reader_.get_taglist_data(size);
}
const void *get_key_base() const {
return vecs_reader_.key_base();
}
private:
bool stop_{false};
uint32_t start_cursor_{0};
SparseVecsReader vecs_reader_;
size_t max_doc_count_{0};
};
} // namespace core
} // namespace zvec
+378
View File
@@ -0,0 +1,378 @@
// Copyright 2025-present the zvec project
//
// 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
// distributed under the License is distributed 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 <iostream>
#include <zvec/ailego/io/mmap_file.h>
#include "zvec/core/framework/index_meta.h"
#include "vecs_common.h"
namespace zvec {
namespace core {
class VecsReader {
public:
VecsReader()
: mmap_file_(),
index_meta_(),
num_vecs_(0),
vector_base_(nullptr),
key_base_(nullptr),
sparse_base_meta_{nullptr},
sparse_base_data_{nullptr},
partition_base_{nullptr},
taglist_base_meta_{nullptr},
taglist_base_data_{nullptr},
taglist_size_{0} {}
void set_metric(const std::string &name, const ailego::Params &params) {
index_meta_.set_metric(name, 0, params);
}
bool load(const std::string &fname) {
return load(fname.c_str());
}
bool load(const char *fname) {
if (!fname) {
std::cerr << "Load fname is nullptr" << std::endl;
return false;
}
if (!mmap_file_.open(fname, true)) {
std::cerr << "Open file error: " << fname << std::endl;
return false;
}
return load();
}
bool load() {
const VecsHeader *header =
reinterpret_cast<const VecsHeader *>(mmap_file_.region());
// check
num_vecs_ = header->num_vecs;
// deserialize
bool bret = index_meta_.deserialize(header->meta_buf(), header->meta_size);
if (!bret) {
std::cerr << "deserialize index meta error." << std::endl;
return false;
}
const char *data_base_ptr =
reinterpret_cast<const char *>(header->meta_buf()) + header->meta_size;
vector_base_ = reinterpret_cast<const char *>(data_base_ptr);
key_base_ = reinterpret_cast<const uint64_t *>(
vector_base_ + num_vecs_ * index_meta_.element_size());
if (header->sparse_offset != -1LLU) {
sparse_base_meta_ = data_base_ptr + header->sparse_offset;
sparse_base_data_ = sparse_base_meta_ + num_vecs_ * sizeof(uint64_t);
}
if (header->partition_offset != -1LLU) {
partition_base_ = reinterpret_cast<const uint32_t *>(
data_base_ptr + header->partition_offset);
}
if (header->taglist_offset != -1LLU) {
taglist_base_meta_ = data_base_ptr + header->taglist_offset;
taglist_base_data_ = taglist_base_meta_ + num_vecs_;
taglist_size_ = header->taglist_size;
}
return true;
}
size_t num_vecs() const {
return num_vecs_;
}
const void *vector_base() const {
return vector_base_;
}
const uint64_t *key_base() const {
return key_base_;
}
const IndexMeta &index_meta() const {
return index_meta_;
}
uint64_t get_key(size_t index) const {
return key_base_[index];
}
const void *get_vector(size_t index) const {
return vector_base_ + index * index_meta_.element_size();
}
uint32_t get_sparse_count(size_t index) const {
auto sparse_data_meta = sparse_base_meta_ + index * sizeof(uint64_t);
uint64_t sparse_offset = *((uint64_t *)sparse_data_meta);
uint32_t sparse_count = *((uint32_t *)(sparse_base_data_ + sparse_offset));
return sparse_count;
return 0;
}
const uint32_t *get_sparse_indices(size_t index) const {
auto sparse_data_meta = sparse_base_meta_ + index * sizeof(uint64_t);
uint64_t sparse_offset = *((uint64_t *)sparse_data_meta);
uint32_t *sparse_indices =
(uint32_t *)(sparse_base_data_ + sparse_offset + sizeof(uint32_t));
return sparse_indices;
return nullptr;
}
const void *get_sparse_data(size_t index) const {
auto sparse_data_meta = sparse_base_meta_ + index * sizeof(uint64_t);
uint64_t sparse_offset = *((uint64_t *)sparse_data_meta);
uint32_t sparse_count = *((uint32_t *)(sparse_base_data_ + sparse_offset));
void *sparse_data =
(uint32_t *)(sparse_base_data_ + sparse_offset + sizeof(uint32_t) +
sparse_count * sizeof(uint32_t));
return sparse_data;
}
size_t get_total_sparse_count(void) const {
size_t total_sparse_count = 0;
for (size_t i = 0; i < num_vecs_; ++i) {
total_sparse_count += get_sparse_count(i);
}
return total_sparse_count;
}
bool has_taglist(void) const {
return taglist_base_meta_ != nullptr;
}
uint64_t get_taglist_count(size_t index) const {
if (!taglist_base_data_ || !taglist_base_meta_) {
return 0;
}
uint64_t taglist_count = *reinterpret_cast<const uint64_t *>(
taglist_base_data_ + taglist_base_meta_[index]);
return taglist_count;
}
const uint64_t *get_taglist(size_t index) const {
if (!taglist_base_data_ || !taglist_base_meta_) {
return nullptr;
}
return reinterpret_cast<const uint64_t *>(taglist_base_data_ +
taglist_base_meta_[index]) +
1;
}
const void *get_taglist_data(size_t &size) const {
size = taglist_size_;
return taglist_base_meta_;
}
private:
ailego::MMapFile mmap_file_;
IndexMeta index_meta_;
size_t num_vecs_;
const char *vector_base_;
const uint64_t *key_base_;
const char *sparse_base_meta_;
const char *sparse_base_data_;
const uint32_t *partition_base_;
const char *taglist_base_meta_;
const char *taglist_base_data_;
uint64_t taglist_size_;
};
class SparseVecsReader {
public:
SparseVecsReader()
: mmap_file_(),
index_meta_(),
num_vecs_(0),
key_base_(nullptr),
sparse_base_meta_(nullptr),
sparse_base_data_{nullptr},
partition_base_{nullptr},
taglist_base_meta_{nullptr},
taglist_base_data_{nullptr},
taglist_size_{0} {}
void set_metric(const std::string &name, const ailego::Params &params) {
index_meta_.set_metric(name, 0, params);
}
bool load(const std::string &fname) {
return load(fname.c_str());
}
bool load(const char *fname) {
if (!fname) {
std::cerr << "Load fname is nullptr" << std::endl;
return false;
}
if (!mmap_file_.open(fname, true)) {
std::cerr << "Open file error: " << fname << std::endl;
return false;
}
return load();
}
bool load() {
const VecsHeader *header =
reinterpret_cast<const VecsHeader *>(mmap_file_.region());
// check
num_vecs_ = header->num_vecs;
// deserialize
bool bret = index_meta_.deserialize(header->meta_buf(), header->meta_size);
if (!bret) {
std::cerr << "deserialize index meta error." << std::endl;
return false;
}
const char *data_base_ptr =
reinterpret_cast<const char *>(header->meta_buf()) + header->meta_size;
key_base_ = reinterpret_cast<const uint64_t *>(
reinterpret_cast<const char *>(header->meta_buf()) + header->meta_size);
sparse_base_meta_ = reinterpret_cast<const char *>(key_base_ + num_vecs_);
sparse_base_data_ = reinterpret_cast<const char *>(
sparse_base_meta_ + num_vecs_ * sizeof(uint64_t));
if (header->partition_offset != -1LLU) {
partition_base_ = reinterpret_cast<const uint32_t *>(
data_base_ptr + header->partition_offset);
}
if (header->taglist_offset != -1LLU) {
taglist_base_meta_ = data_base_ptr + header->taglist_offset;
taglist_base_data_ = taglist_base_meta_ + num_vecs_;
taglist_size_ = header->taglist_size;
}
return true;
}
size_t num_vecs() const {
return num_vecs_;
}
const void *sparse_meta_base() const {
return sparse_base_meta_;
}
const uint64_t *key_base() const {
return key_base_;
}
const IndexMeta &index_meta() const {
return index_meta_;
}
uint64_t get_key(size_t index) const {
return key_base_[index];
}
uint32_t get_sparse_count(size_t index) const {
auto sparse_data_meta = sparse_base_meta_ + index * sizeof(uint64_t);
uint64_t sparse_offset = *((uint64_t *)sparse_data_meta);
uint32_t sparse_count = *((uint32_t *)(sparse_base_data_ + sparse_offset));
return sparse_count;
return 0;
}
const uint32_t *get_sparse_indices(size_t index) const {
auto sparse_data_meta = sparse_base_meta_ + index * sizeof(uint64_t);
uint64_t sparse_offset = *((uint64_t *)sparse_data_meta);
uint32_t *sparse_indices =
(uint32_t *)(sparse_base_data_ + sparse_offset + sizeof(uint32_t));
return sparse_indices;
return nullptr;
}
const void *get_sparse_data(size_t index) const {
auto sparse_data_meta = sparse_base_meta_ + index * sizeof(uint64_t);
uint64_t sparse_offset = *((uint64_t *)sparse_data_meta);
uint32_t sparse_count = *((uint32_t *)(sparse_base_data_ + sparse_offset));
void *sparse_data =
(uint32_t *)(sparse_base_data_ + sparse_offset + sizeof(uint32_t) +
sparse_count * sizeof(uint32_t));
return sparse_data;
}
size_t get_total_sparse_count(void) const {
size_t total_sparse_count = 0;
for (size_t i = 0; i < num_vecs_; ++i) {
total_sparse_count += get_sparse_count(i);
}
return total_sparse_count;
}
bool has_taglist(void) const {
return taglist_base_meta_ != nullptr;
}
uint64_t get_taglist_count(size_t index) const {
uint64_t taglist_count = *reinterpret_cast<const uint64_t *>(
taglist_base_data_ + taglist_base_meta_[index]);
return taglist_count;
}
const uint64_t *get_taglist(size_t index) const {
return reinterpret_cast<const uint64_t *>(taglist_base_data_ +
taglist_base_meta_[index]) +
1;
}
const void *get_taglist_data(size_t &size) const {
size = taglist_size_;
return taglist_base_meta_;
}
private:
ailego::MMapFile mmap_file_;
IndexMeta index_meta_;
size_t num_vecs_;
const uint64_t *key_base_;
const char *sparse_base_meta_;
const char *sparse_base_data_;
const uint32_t *partition_base_;
const char *taglist_base_meta_;
const char *taglist_base_data_;
uint64_t taglist_size_;
};
} // namespace core
} // namespace zvec