// 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 #include #include #include #include #include #include #include #include #include #include #include "zvec/core/framework/index_plugin.h" #include "zvec/core/interface/index_factory.h" #include "zvec/core/interface/index_param.h" #include "filter_result_cache.h" #include "flow.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 #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #endif using namespace std; using namespace zvec::core; using namespace zvec::ailego; using Flow = Flow; using SparseFlow = SparseFlow; mutex recall_lock; bool g_compare_by_id = false; float g_recall_precision; //-------------------------------------------------- // Recall //-------------------------------------------------- enum RetrievalMode { RM_UNDEFINED = 0, RM_DENSE = 1, RM_SPARSE = 2 }; enum FilterMode { FM_UNDEFINED = 0, FM_NONE = 1, FM_TAG = 2 }; template class Recall { public: Recall(size_t threads, const string &output, size_t batch_count, FilterMode filter_mode) : threads_(threads), output_(output), batch_count_(batch_count), filter_mode_{filter_mode} { if (threads_ == 0) { pool_ = make_shared(true); threads_ = pool_->count(); cout << "Using cpu count as thread pool count[" << threads_ << "]" << endl; } else { pool_ = make_shared(threads_, true); cout << "Using thread pool count[" << threads_ << "]" << endl; } if (batch_count_ < 1) { batch_count_ = 1; call_batch_api_ = false; } else { call_batch_api_ = true; } } 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; } void run_dense(Flow *flower, const string &recall_tops, size_t gt_count) { StringHelper::Split(recall_tops, ",", &topk_ids_); std::sort(topk_ids_.begin(), topk_ids_.end()); for (auto i : topk_ids_) { recall_res_[i] = 0.0f; } size_t topk = recall_res_.rbegin()->first; gt_count = topk < gt_count ? gt_count : topk; if (external_gt_file_enabled_) { cout << "Internal ground truth file NOT used since external ground truth " "file has been loaded" << endl; } else { cout << "Loading internal ground truth file" << endl; if (!load_gt_dense(flower, gt_count)) { cerr << "Load ground truth file failed!" << endl; return; } } if (batch_queries_.size() < threads_) { threads_ = batch_queries_.size(); pool_ = make_shared(true, threads_); cout << "Query size too small, resize thread pool count[" << threads_ << "]" << endl; } // Prepare file handler vector> output_fs; if (!output_.empty()) { string cmd = "mkdir -p " + output_; int ret = system(cmd.c_str()); if (ret != 0) { std::cerr << "execute cmd " << cmd << " failed" << std::endl; return; } struct stat sb; if (stat(output_.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { cout << "logs output to : " << output_ << endl; for (size_t i = 0; i < threads_; ++i) { fstream *fs_k = new fstream(); fs_k->open(output_ + "/t" + to_string(i) + ".knn", ios::out); fstream *fs_l = new fstream(); fs_l->open(output_ + "/t" + to_string(i) + ".linear", ios::out); output_fs.push_back(make_pair(fs_k, fs_l)); } } } signal(SIGINT, stop); size_t i = 0; for (; !STOP_NOW && i < batch_queries_.size();) { if (pool_->pending_count() >= pool_->count()) { this_thread::sleep_for(chrono::microseconds(1)); continue; } Closure::Pointer task = Closure::New(this, &Recall::recall_one_dense, flower, topk, i, output_fs); pool_->enqueue_and_wake(task); i++; } pool_->wait_finish(); for (auto fs : output_fs) { fs.first->close(); fs.second->close(); delete fs.first; delete fs.second; } cout << "Process query: " << i << endl; for (auto it : recall_res_) { cout << "Recall@" << it.first << ": " << it.second / linear_queries_.size() << endl; } } bool load_query(const std::string &query_file, const std::string &first_sep, const std::string &second_sep) { TxtInputReader reader; if (!reader.load_query(query_file, first_sep, second_sep, linear_queries_, linear_sparse_data_, linear_taglists_)) { cerr << "Load query error" << endl; return false; } if (batch_count_ == 1) { batch_queries_ = linear_queries_; for (size_t i = 0; i < linear_sparse_data_.size(); ++i) { vector sparse_count; sparse_count.push_back(linear_sparse_data_[i].count); batch_sparse_counts_.push_back(sparse_count); batch_sparse_indices_.push_back(linear_sparse_data_[i].indices); batch_sparse_features_.push_back(linear_sparse_data_[i].features); } for (size_t i = 0; i < linear_taglists_.size(); ++i) { vector> new_taglists; new_taglists.push_back(linear_taglists_[i]); batch_taglists_.push_back(std::move(new_taglists)); } } else { size_t num_batch = (linear_queries_.size() + batch_count_ - 1) / batch_count_; size_t idx = 0; for (size_t n = 0; n < num_batch; ++n) { vector batch_query; vector batch_sparse_count; vector batch_sparse_indices; vector batch_sparse_feature; vector> batch_taglists; for (size_t i = 0; i < batch_count_; ++i) { for (size_t k = 0; k < linear_queries_[idx].size(); ++k) { batch_query.push_back(linear_queries_[idx][k]); } batch_sparse_count.push_back(linear_sparse_data_[idx].count); for (size_t k = 0; k < linear_sparse_data_[idx].indices.size(); ++k) { batch_sparse_indices.push_back(linear_sparse_data_[idx].indices[k]); } for (size_t k = 0; k < linear_sparse_data_[idx].features.size(); ++k) { batch_sparse_feature.push_back( linear_sparse_data_[idx].features[k]); } idx = (idx + 1) % linear_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_ = linear_queries_[0].size(); total_querys_ = linear_queries_.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 { cerr << "unsupported type"; return false; } cout << "Load query done!" << endl; return true; } bool load_external_gt_file(const std::string &external_gt_file, const std::string &first_sep, const std::string &second_sep) { TxtInputReader reader; bool ret = reader.load_external_gt(external_gt_file, first_sep, second_sep, gt_); if (ret) { cout << "Load external ground truth file[" << File::BaseName(external_gt_file) << "] done!" << endl; external_gt_file_enabled_ = true; } else { cerr << "Failed to load ground truth file!" << endl; } return ret; } private: std::string compute_crc(size_t gt_count) { uint32_t crc = 0u; // dense if (batch_queries_.size() > 0) { size_t one_size = dim_ * sizeof(T); size_t data_size = total_querys_ * one_size + sizeof(size_t); char *data = new char[data_size]; size_t q = 0; char *p = data; for (; q < batch_queries_.size(); ++q) { memcpy(p, batch_queries_[q].data(), batch_queries_[q].size() * sizeof(T)); p += batch_queries_[q].size() * sizeof(T); } memcpy(p, >_count, sizeof(size_t)); crc = Crc32c::Hash(data, data_size, crc); delete[] data; } // sparse if (linear_sparse_data_.size() > 0) { for (size_t i = 0; i < linear_sparse_data_.size(); ++i) { crc = Crc32c::Hash(&(linear_sparse_data_[i].count), sizeof(uint32_t), crc); crc = Crc32c::Hash(linear_sparse_data_[i].indices.data(), linear_sparse_data_[i].count * sizeof(uint32_t), crc); crc = Crc32c::Hash(linear_sparse_data_[i].features.data(), linear_sparse_data_[i].count * sizeof(T), crc); } } char crc_str[64]; snprintf(crc_str, sizeof(crc_str), "%X", crc); return std::string(crc_str); } bool load_gt_dense(Flow *flower, size_t gt_count) { std::string crc_str = compute_crc(gt_count); string gt_file = string("gt.") + crc_str; File gtf; if (!gtf.IsRegular(gt_file.c_str())) { cout << "Ground truth file[" << gt_file << "] not exist, try to create it" << endl; ElapsedTime timer; size_t size = sizeof(uint64_t) + sizeof(float); size_t file_size = linear_queries_.size() * (sizeof(int) + size * gt_count); std::string gt_file_temp = gt_file + ".tmp"; gtf.create(gt_file_temp.c_str(), file_size); gt_.resize(linear_queries_.size()); atomic_bool error(false); size_t count = 0; float s = linear_queries_.size() / 100.0; size_t pc = 0; SpinMutex spin_lock; function fun = [&](size_t i) { spin_lock.lock(); count++; size_t process = (size_t)ceil(count / s); if (process > pc) { pc = process; stringstream msg; msg << "\r" << setw(3) << setfill(' ') << process << "% " << left << setfill('=') << setw(process / 2 + 1) << "[" << right << setfill(' ') << setw(51 - process / 2) << "]"; cout << msg.str() << flush; } spin_lock.unlock(); auto query = linear_queries_[i]; Flow::Context::Pointer context = flower->create_context(); if (!context) { cerr << "Failed to create search context" << endl; return; } FilterResultCache filter_cache; if (filter_mode_ == FM_TAG) { if (batch_taglists_[i].size() != 1) { cerr << "query tag list not equal to one!" << endl; return; } int ret = filter_cache.filter(flower->id_to_tags_list(), batch_taglists_[i][0], flower->tag_key_list()); if (ret != 0) { cerr << "prefilter failed, idx: " << i << std::endl; return; } auto filterFunc = [&](uint64_t key) { return filter_cache.find(key); }; context->set_filter(filterFunc); } context->set_topk(gt_count); int ret = do_linear_search(flower, context, query); if (ret < 0) { cerr << "Failed to linear search, ret=" << ret << endl; error.exchange(true); return; } auto result = context->result(); vector> one_gt; one_gt.reserve(gt_count); for (auto knn : result) { one_gt.emplace_back(knn.key(), knn.score()); } gt_[i] = one_gt; }; for (size_t i = 0; i < linear_queries_.size(); ++i) { if (error) { break; } pool_->enqueue_and_wake(Closure::New(fun, i)); } pool_->wait_finish(); if (error) { cout << endl << "Ground truth file[" << gt_file << "] create failed!" << endl; gtf.close(); remove(gt_file.c_str()); return false; } for (size_t i = 0; i < gt_.size(); ++i) { auto > = gt_[i]; gtf.write(>_count, sizeof(int)); for (size_t j = 0; j < gt.size(); j++) { auto &one_gt = gt[j]; gtf.write(&one_gt.first, sizeof(uint64_t)); gtf.write(&one_gt.second, sizeof(float)); } // if ground truth is less than gt count, fill it up if (gt.size() != gt_count) { std::cout << "WARN: GT result count less than GT expected count, index: " << i << ", expected GT count: " << gt_count << ", actual GT count: " << gt.size() << std::endl; uint64_t key{-1LLU}; float score{std::nanf("")}; for (size_t j = gt.size(); j < gt_count; ++j) { gtf.write(&key, sizeof(uint64_t)); gtf.write(&score, sizeof(float)); } } } gtf.close(); if (!File::Rename(gt_file_temp, gt_file)) { cerr << "failed to rename ground truth file, src: " << gt_file_temp << ", dst: " << gt_file << endl; return false; } cout << endl << "Ground truth file create successful in " << timer.milli_seconds() / 1000 << "s." << endl; } else { if (!gtf.open(gt_file.c_str(), true)) { cerr << "Failed to open ground truth file[" << gt_file << "]" << endl; return false; } size_t file_size = gtf.size(); constexpr size_t LENGTH = 10240; constexpr size_t GT_PAIR_SIZE = sizeof(uint64_t) + sizeof(float); char *buffer = new char[LENGTH]; gtf.read(buffer, sizeof(int)); size_t gt_count_input = (size_t) * (int *)buffer; size_t one_query_line_size = sizeof(int) + GT_PAIR_SIZE * gt_count_input; if (gt_count != gt_count_input || file_size % one_query_line_size != 0) { cerr << "Ground truth file[" << gt_file << "] content error!" << endl; gtf.close(); return false; } size_t query_num = file_size / one_query_line_size; if (one_query_line_size > LENGTH) { delete[] buffer; buffer = new char[one_query_line_size]; } for (size_t n = 0; n < query_num; ++n) { gtf.read(n * one_query_line_size, buffer, one_query_line_size); vector> one_gt; one_gt.reserve(gt_count); for (size_t i = 0; i < gt_count; ++i) { uint64_t key = *(uint64_t *)(buffer + sizeof(int) + GT_PAIR_SIZE * i); float score = *(float *)(buffer + sizeof(int) + GT_PAIR_SIZE * i + sizeof(uint64_t)); if (key != -1LLU) { one_gt.emplace_back(key, score); } } gt_.emplace_back(one_gt); } delete[] buffer; cout << "Load ground truth file[" << gt_file << "] done!" << endl; } return true; } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_FP32, // query.size() / count * sizeof(float), count); return flower->search_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_INT8, // query.size() / count, count); return flower->search_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() / count * sizeof(uint32_t), count); return flower->search_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() / count * sizeof(uint32_t), count); return flower->search_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_FP32, // query.size() * sizeof(float), 1); return flower->search_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_INT8, // query.size() , 1); return flower->search_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() * sizeof(uint32_t), 1); return flower->search_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_knn_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do knn_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() * sizeof(uint32_t), 1); return flower->search_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_FP32, // query.size() * sizeof(float), 1); return flower->search_bf_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_INT8, // query.size() , 1); return flower->search_bf_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() * sizeof(uint32_t), 1); return flower->search_bf_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() * sizeof(uint32_t), 1); return flower->search_bf_impl(query.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_FP32, // query.size() / count * sizeof(float), count); return flower->search_bf_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_INT8, // query.size() / count, count); return flower->search_bf_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() / count * sizeof(uint32_t), count); return flower->search_bf_impl(query.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_linear_search(Flow *flower, Flow::Context::Pointer &context, const vector &query, size_t count) { // Do linear_search // IndexQueryMeta qmeta(IndexMeta::DataType::DT_BINARY32, // query.size() / count * sizeof(uint32_t), count); return flower->search_bf_impl(query.data(), qmeta_, count, context); } void recall_one_dense( Flow *flower, size_t topk, size_t index, std::vector> &output_fs) { const auto &query = batch_queries_[index]; size_t thread_index = pool_->indexof_this(); fstream *knn_fs = nullptr; fstream *linear_fs = nullptr; if (output_fs.size() > thread_index) { knn_fs = output_fs[thread_index].first; linear_fs = output_fs[thread_index].second; } Flow::Context::Pointer knn_context = flower->create_context(); if (!knn_context) { cerr << "Failed to create search context" << endl; return; } knn_context->set_topk(topk); auto cal_recall = [&, this](const std::vector &knn_res, size_t idx) { vector linear_res; size_t result_size = std::min(topk, gt_[idx].size()); if (result_size == 0) { return; } for (size_t i = 0; i < result_size; ++i) { auto gt_node = gt_[idx][i]; linear_res.emplace_back(gt_node.first, gt_node.second, gt_node.first); } if (knn_fs) { for (auto knn : knn_res) { string str = "query[" + to_string(idx) + "]\tkey[" + to_string(knn.key()) + "], dist[" + to_string(knn.score()) + "]\n"; knn_fs->write(str.c_str(), str.size()); } } size_t match = 0; bool asc = (linear_res.size() > 1 && (linear_res[0].score() > linear_res[linear_res.size() - 1].score())) ? false : true; map topk_matchs; if (g_compare_by_id) { for (size_t i = 0; i < topk_ids_.size(); ++i) { topk_matchs[topk_ids_[i]] = 0; } } for (size_t i = 0, j = 0; i < linear_res.size();) { bool m = false; // if current doc matched in max topk bool changed = true; // if i changed if (g_compare_by_id) { for (size_t k = 0; k < topk_ids_.size(); ++k) { size_t dynamic_size = (size_t)topk_ids_[k]; for (; dynamic_size + 1 < knn_res.size(); ++dynamic_size) { if (fabs(knn_res[dynamic_size - 1].score() - knn_res[dynamic_size].score()) >= numeric_limits::epsilon()) { break; } } for (size_t l = 0; l < dynamic_size && l < knn_res.size(); ++l) { if (linear_res[i].key() == knn_res[l].key()) { topk_matchs[topk_ids_[k]]++; if (k == topk_ids_.size() - 1) { m = true; } break; } } } ++i; auto it = recall_res_.find(i); if (it != recall_res_.end()) { lock_guard lock(recall_lock); it->second += 100.0 * topk_matchs[i] / i; } } else { size_t cur_topk = i + 1; if (j < knn_res.size()) { if (fabs(linear_res[i].score() - knn_res[j].score()) < g_recall_precision) { ++j; ++i; match++; m = true; } else { if ((asc && linear_res[i].score() < knn_res[j].score()) || (!asc && linear_res[i].score() > knn_res[j].score())) { ++i; } else { changed = false; ++j; } } } else { ++i; } auto it = recall_res_.find(cur_topk); if (changed && it != recall_res_.end()) { lock_guard lock(recall_lock); it->second += 100.0 * match / cur_topk; } } if (linear_fs && changed) { string str = string(m ? " HIT" : "NOT HIT") + " query[" + to_string(idx) + "]\tkey[" + to_string(linear_res[i - 1].key()) + "], dist[" + to_string(linear_res[i - 1].score()) + "]\n"; linear_fs->write(str.c_str(), str.size()); } } }; // prefilter FilterResultCache filter_cache; if (filter_mode_ == FM_TAG) { if (batch_taglists_[index].size() != 1) { cerr << "query tag list not equal to one!" << endl; return; } int ret = filter_cache.filter(flower->id_to_tags_list(), batch_taglists_[index][0], flower->tag_key_list()); if (ret != 0) { cerr << "prefilter failed, idx: " << index << std::endl; return; } auto filterFunc = [&](uint64_t key) { return filter_cache.find(key); }; knn_context->set_filter(filterFunc); } if (call_batch_api_) { size_t qnum = query.size() / dim_; int ret = do_knn_search(flower, knn_context, query, qnum); if (ret < 0) { cerr << "Failed to knn_search batch, ret=" << ret << " " << IndexError::What(ret) << endl; return; } for (size_t i = 0; i < qnum; ++i) { size_t idx = index * batch_count_ + i; if (idx >= linear_queries_.size()) { break; } auto &knn_res = knn_context->result(i); cal_recall(knn_res, idx); } } else { int ret = do_knn_search(flower, knn_context, query); if (ret < 0) { cerr << "Failed to knn_search, ret=" << ret << " " << IndexError::What(ret) << endl; return; } auto &knn_res = knn_context->result(); cal_recall(knn_res, index); } // std::cout << "id: " << index << ": \n" << // knn_context->flow_context()->searcher_context()->profiler().display(); } private: IndexQueryMeta qmeta_{}; size_t threads_; bool call_batch_api_; string output_; size_t batch_count_; shared_ptr pool_; // for gt vector> linear_queries_; vector> linear_sparse_data_; vector> linear_taglists_; // for recall vector> batch_queries_; vector> batch_sparse_counts_; vector> batch_sparse_indices_; vector> batch_sparse_features_; vector>> batch_taglists_; size_t dim_; size_t total_querys_; map recall_res_; vector topk_ids_; vector>> gt_; bool external_gt_file_enabled_{false}; FilterMode filter_mode_{FM_NONE}; static bool STOP_NOW; }; template bool Recall::STOP_NOW = false; //-------------------------------------------------- // Sparse Recall //-------------------------------------------------- template class SparseRecall { public: SparseRecall(size_t threads, const string &output, size_t batch_count, FilterMode filter_mode) : threads_(threads), output_(output), batch_count_(batch_count), filter_mode_{filter_mode} { if (threads_ == 0) { pool_ = make_shared(true); threads_ = pool_->count(); cout << "Using cpu count as thread pool count[" << threads_ << "]" << endl; } else { pool_ = make_shared(threads_, true); cout << "Using thread pool count[" << threads_ << "]" << endl; } if (batch_count_ < 1) { batch_count_ = 1; call_batch_api_ = false; } else { call_batch_api_ = true; } } 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; } int transform_queries_without_hybrid_scale( const vector> &queries, const vector> &sparse_features, vector> *queries_output, vector> *sparse_features_output) { if (!queries_output || !sparse_features_output) { std::cerr << "input should not be empty in transfrom queries" << std::endl; return -1; } queries_output->clear(); sparse_features_output->clear(); for (size_t i = 0; i < queries.size(); ++i) { vector query_output; vector sparse_feature_output; transform_query_without_hybrid_scale(queries[i], sparse_features[i], &query_output, &sparse_feature_output); queries_output->push_back(query_output); sparse_features_output->push_back(sparse_feature_output); } return 0; } void run_sparse(SparseFlow *flower, const string &recall_tops, size_t gt_count) { StringHelper::Split(recall_tops, ",", &topk_ids_); std::sort(topk_ids_.begin(), topk_ids_.end()); for (auto i : topk_ids_) { recall_res_[i] = 0.0f; } size_t topk = recall_res_.rbegin()->first; gt_count = topk < gt_count ? gt_count : topk; if (external_gt_file_enabled_) { cout << "Internal ground truth file NOT used since external ground truth " "file has been loaded" << endl; } else { cout << "Loading internal ground truth file" << endl; if (!load_gt_sparse(flower, gt_count)) { cerr << "Load ground truth file failed!" << endl; return; } } if (batch_sparse_counts_.size() < threads_) { threads_ = batch_sparse_counts_.size(); pool_ = make_shared(true, threads_); cout << "Query size too small, resize thread pool count[" << threads_ << "]" << endl; } // Prepare file handler vector> output_fs; if (!output_.empty()) { string cmd = "mkdir -p " + output_; int ret = system(cmd.c_str()); if (ret != 0) { std::cerr << "execute cmd " << cmd << " failed" << std::endl; return; } struct stat sb; if (stat(output_.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { cout << "logs output to : " << output_ << endl; for (size_t i = 0; i < threads_; ++i) { fstream *fs_k = new fstream(); fs_k->open(output_ + "/t" + to_string(i) + ".knn", ios::out); fstream *fs_l = new fstream(); fs_l->open(output_ + "/t" + to_string(i) + ".linear", ios::out); output_fs.push_back(make_pair(fs_k, fs_l)); } } } signal(SIGINT, stop); size_t i = 0; for (; !STOP_NOW && i < batch_sparse_counts_.size();) { if (pool_->pending_count() >= pool_->count()) { this_thread::sleep_for(chrono::microseconds(1)); continue; } Closure::Pointer task = Closure::New( this, &SparseRecall::recall_one_sparse, flower, topk, i, output_fs); pool_->enqueue_and_wake(task); i++; } pool_->wait_finish(); for (auto fs : output_fs) { fs.first->close(); fs.second->close(); delete fs.first; delete fs.second; } cout << "Process query: " << i << endl; for (auto it : recall_res_) { cout << "Recall@" << it.first << ": " << it.second / linear_queries_.size() << endl; } } bool load_query(const std::string &query_file, const std::string &first_sep, const std::string &second_sep) { TxtInputReader reader; if (!reader.load_query(query_file, first_sep, second_sep, linear_queries_, linear_sparse_data_, linear_taglists_)) { cerr << "Load query error" << endl; return false; } if (batch_count_ == 1) { for (size_t i = 0; i < linear_sparse_data_.size(); ++i) { vector sparse_count; sparse_count.push_back(linear_sparse_data_[i].count); batch_sparse_counts_.push_back(sparse_count); batch_sparse_indices_.push_back(linear_sparse_data_[i].indices); batch_sparse_features_.push_back(linear_sparse_data_[i].features); } } else { size_t num_batch = (linear_queries_.size() + batch_count_ - 1) / batch_count_; size_t idx = 0; for (size_t n = 0; n < num_batch; ++n) { vector batch_sparse_count; vector batch_sparse_indices; vector batch_sparse_feature; for (size_t i = 0; i < batch_count_; ++i) { batch_sparse_count.push_back(linear_sparse_data_[idx].count); for (size_t k = 0; k < linear_sparse_data_[idx].indices.size(); ++k) { batch_sparse_indices.push_back(linear_sparse_data_[idx].indices[k]); } for (size_t k = 0; k < linear_sparse_data_[idx].features.size(); ++k) { batch_sparse_feature.push_back( linear_sparse_data_[idx].features[k]); } idx = (idx + 1) % linear_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); } } total_querys_ = linear_queries_.size(); 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 { cerr << "unsupported type"; return false; } cout << "Load query done!" << endl; return true; } bool load_gt_sparse(SparseFlow *flower, size_t gt_count) { std::string crc_str = compute_crc(); string gt_file = string("gt.") + crc_str; File gtf; if (!gtf.IsRegular(gt_file.c_str())) { cout << "Ground truth file[" << gt_file << "] not exist, try to create it" << endl; ElapsedTime timer; size_t size = sizeof(uint64_t) + sizeof(float); size_t file_size = linear_sparse_data_.size() * (sizeof(int) + size * gt_count); std::string gt_file_temp = gt_file + ".tmp"; gtf.create(gt_file_temp.c_str(), file_size); gt_.resize(linear_sparse_data_.size()); atomic_bool error(false); size_t count = 0; float s = linear_sparse_data_.size() / 100.0; size_t pc = 0; SpinMutex spin_lock; function fun = [&](size_t i) { spin_lock.lock(); count++; size_t process = (size_t)ceil(count / s); if (process > pc) { pc = process; stringstream msg; msg << "\r" << setw(3) << setfill(' ') << process << "% " << left << setfill('=') << setw(process / 2 + 1) << "[" << right << setfill(' ') << setw(51 - process / 2) << "]"; cout << msg.str() << flush; } spin_lock.unlock(); SparseFlow::Context::Pointer context = flower->create_context(); if (!context) { cerr << "Failed to create search context" << endl; return; } context->set_topk(gt_count); SparseData sparse_data = linear_sparse_data_[i]; // prefilter FilterResultCache filter_cache; if (filter_mode_ == FM_TAG) { if (batch_taglists_[i].size() != 1) { cerr << "query tag list not equal to one!" << endl; return; } int ret = filter_cache.filter(flower->id_to_tags_list(), batch_taglists_[i][0], flower->tag_key_list()); if (ret != 0) { cerr << "prefilter failed, idx: " << i << std::endl; return; } auto filterFunc = [&](uint64_t key) { return filter_cache.find(key); }; context->set_filter(filterFunc); } int ret = do_linear_search(flower, context, sparse_data.count, sparse_data.indices, sparse_data.features); if (ret < 0) { cerr << "Failed to sparse linear search, ret=" << ret << endl; error.exchange(true); return; } auto result = context->result(); vector> one_gt; one_gt.reserve(gt_count); for (auto knn : result) { one_gt.emplace_back(knn.key(), knn.score()); } gt_[i] = one_gt; }; for (size_t i = 0; i < linear_sparse_data_.size(); ++i) { if (error) { break; } pool_->enqueue_and_wake(Closure::New(fun, i)); } pool_->wait_finish(); if (error) { cout << endl << "Ground truth file[" << gt_file << "] create failed!" << endl; gtf.close(); remove(gt_file.c_str()); return false; } for (size_t i = 0; i < gt_.size(); ++i) { auto > = gt_[i]; gtf.write(>_count, sizeof(int)); for (size_t j = 0; j < gt.size(); j++) { auto &one_gt = gt[j]; gtf.write(&one_gt.first, sizeof(uint64_t)); gtf.write(&one_gt.second, sizeof(float)); } // if ground truth is less than gt count, fill it up if (gt.size() != gt_count) { std::cout << "WARN: GT result count less than GT expected count, index: " << i << ", expected GT count: " << gt_count << ", actual GT count: " << gt.size() << std::endl; uint64_t key{-1LLU}; float score{std::nanf("")}; for (size_t j = gt.size(); j < gt_count; ++j) { gtf.write(&key, sizeof(uint64_t)); gtf.write(&score, sizeof(float)); } } } gtf.close(); if (!File::Rename(gt_file_temp, gt_file)) { cerr << "failed to rename ground truth file, src: " << gt_file_temp << ", dst: " << gt_file << endl; return false; } cout << endl << "Ground truth file create successful in " << timer.milli_seconds() / 1000 << "s." << endl; } else { if (!gtf.open(gt_file.c_str(), true)) { cerr << "Failed to open ground truth file[" << gt_file << "]" << endl; return false; } size_t file_size = gtf.size(); constexpr size_t LENGTH = 10240; constexpr size_t GT_PAIR_SIZE = sizeof(uint64_t) + sizeof(float); char *buffer = new char[LENGTH]; gtf.read(buffer, sizeof(int)); size_t gt_count_input = (size_t) * (int *)buffer; size_t one_query_line_size = sizeof(int) + GT_PAIR_SIZE * gt_count_input; if (gt_count != gt_count_input || file_size % one_query_line_size != 0) { cerr << "Ground truth file[" << gt_file << "] content error!" << endl; gtf.close(); return false; } size_t query_num = file_size / one_query_line_size; if (one_query_line_size > LENGTH) { delete[] buffer; buffer = new char[one_query_line_size]; } for (size_t n = 0; n < query_num; ++n) { gtf.read(n * one_query_line_size, buffer, one_query_line_size); vector> one_gt; one_gt.reserve(gt_count); for (size_t i = 0; i < gt_count; ++i) { uint64_t key = *(uint64_t *)(buffer + sizeof(int) + GT_PAIR_SIZE * i); float score = *(float *)(buffer + sizeof(int) + GT_PAIR_SIZE * i + sizeof(uint64_t)); if (key != -1LLU) { one_gt.emplace_back(key, score); } } gt_.emplace_back(one_gt); } delete[] buffer; cout << "Load ground truth file[" << gt_file << "] done!" << endl; } return true; } bool load_external_gt_file(const std::string &external_gt_file, const std::string &first_sep, const std::string &second_sep) { TxtInputReader reader; bool ret = reader.load_external_gt(external_gt_file, first_sep, second_sep, gt_); if (ret) { cout << "Load external ground truth file[" << File::BaseName(external_gt_file) << "] done!" << endl; external_gt_file_enabled_ = true; } else { cerr << "Failed to load ground truth file!" << endl; } return ret; } private: std::string compute_crc() { uint32_t crc = 0u; // sparse if (linear_sparse_data_.size() > 0) { for (size_t i = 0; i < linear_sparse_data_.size(); ++i) { crc = Crc32c::Hash(&(linear_sparse_data_[i].count), sizeof(uint32_t), crc); crc = Crc32c::Hash(linear_sparse_data_[i].indices.data(), linear_sparse_data_[i].count * sizeof(uint32_t), crc); crc = Crc32c::Hash(linear_sparse_data_[i].features.data(), linear_sparse_data_[i].count * sizeof(T), crc); } } char crc_str[64]; snprintf(crc_str, sizeof(crc_str), "%X", crc); return std::string(crc_str); } // sparse search template typename std::enable_if::value, int>::type do_knn_search(SparseFlow *flower, SparseFlow::Context::Pointer &context, const vector &sparse_count, const vector &sparse_indices, const vector &sparse_feature, size_t count) { return flower->search_impl(sparse_count.data(), sparse_indices.data(), sparse_feature.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_knn_search(SparseFlow *flower, SparseFlow::Context::Pointer &context, const uint32_t sparse_count, const vector &sparse_indices, const vector &sparse_feature) { return flower->search_impl(sparse_count, sparse_indices.data(), sparse_feature.data(), qmeta_, context); } template typename std::enable_if::value, int>::type do_linear_search(SparseFlow *flower, SparseFlow::Context::Pointer &context, const vector &sparse_count, const vector &sparse_indices, const vector &sparse_feature, size_t count) { return flower->search_bf_impl(sparse_count.data(), sparse_indices.data(), sparse_feature.data(), qmeta_, count, context); } template typename std::enable_if::value, int>::type do_linear_search(SparseFlow *flower, SparseFlow::Context::Pointer &context, const uint32_t sparse_count, const vector &sparse_indices, const vector &sparse_feature) { return flower->search_bf_impl(sparse_count, sparse_indices.data(), sparse_feature.data(), qmeta_, context); } void recall_one_sparse( SparseFlow *flower, size_t topk, size_t index, std::vector> &output_fs) { const auto &sparse_count = batch_sparse_counts_[index]; const auto &sparse_index = batch_sparse_indices_[index]; const auto &sparse_feature = batch_sparse_features_[index]; size_t thread_index = pool_->indexof_this(); fstream *knn_fs = nullptr; fstream *linear_fs = nullptr; if (output_fs.size() > thread_index) { knn_fs = output_fs[thread_index].first; linear_fs = output_fs[thread_index].second; } SparseFlow::Context::Pointer knn_context = flower->create_context(); if (!knn_context) { cerr << "Failed to create search context" << endl; return; } knn_context->set_topk(topk); auto cal_recall = [&, this](const std::vector &knn_res, size_t idx) { vector linear_res; size_t result_size = std::min(topk, gt_[idx].size()); if (result_size == 0) { return; } for (size_t i = 0; i < result_size; ++i) { auto gt_node = gt_[idx][i]; linear_res.emplace_back(gt_node.first, gt_node.second, gt_node.first); } if (knn_fs) { for (auto knn : knn_res) { string str = "query[" + to_string(idx) + "]\tkey[" + to_string(knn.key()) + "], dist[" + to_string(knn.score()) + "]\n"; knn_fs->write(str.c_str(), str.size()); } } size_t match = 0; bool asc = (linear_res.size() > 1 && (linear_res[0].score() > linear_res[linear_res.size() - 1].score())) ? false : true; map topk_matchs; if (g_compare_by_id) { for (size_t i = 0; i < topk_ids_.size(); ++i) { topk_matchs[topk_ids_[i]] = 0; } } for (size_t i = 0, j = 0; i < linear_res.size();) { bool m = false; // if current doc matched in max topk bool changed = true; // if i changed if (g_compare_by_id) { for (size_t k = 0; k < topk_ids_.size(); ++k) { size_t dynamic_size = (size_t)topk_ids_[k]; for (; dynamic_size + 1 < knn_res.size(); ++dynamic_size) { if (fabs(knn_res[dynamic_size - 1].score() - knn_res[dynamic_size].score()) >= numeric_limits::epsilon()) { break; } } for (size_t l = 0; l < dynamic_size && l < knn_res.size(); ++l) { if (linear_res[i].key() == knn_res[l].key()) { topk_matchs[topk_ids_[k]]++; if (k == topk_ids_.size() - 1) { m = true; } break; } } } ++i; auto it = recall_res_.find(i); if (it != recall_res_.end()) { lock_guard lock(recall_lock); it->second += 100.0 * topk_matchs[i] / i; } } else { size_t cur_topk = i + 1; if (j < knn_res.size()) { if (fabs(linear_res[i].score() - knn_res[j].score()) < g_recall_precision) { ++j; ++i; match++; m = true; } else { if ((asc && linear_res[i].score() < knn_res[j].score()) || (!asc && linear_res[i].score() > knn_res[j].score())) { ++i; } else { changed = false; ++j; } } } else { ++i; } auto it = recall_res_.find(cur_topk); if (changed && it != recall_res_.end()) { lock_guard lock(recall_lock); it->second += 100.0 * match / cur_topk; } } if (linear_fs && changed) { string str = string(m ? " HIT" : "NOT HIT") + " query[" + to_string(idx) + "]\tkey[" + to_string(linear_res[i - 1].key()) + "], dist[" + to_string(linear_res[i - 1].score()) + "]\n"; linear_fs->write(str.c_str(), str.size()); } } }; FilterResultCache filter_cache; if (filter_mode_ == FM_TAG) { if (batch_taglists_[index].size() != 1) { cerr << "query tag list not equal to one!" << endl; return; } int ret = filter_cache.filter(flower->id_to_tags_list(), batch_taglists_[index][0], flower->tag_key_list()); if (ret != 0) { cerr << "prefilter failed, idx: " << index << std::endl; return; } auto filterFunc = [&](uint64_t key) { return filter_cache.find(key); }; knn_context->set_filter(filterFunc); } if (call_batch_api_) { // size_t qnum = sparse_count.size() / dim_; // int ret = do_knn_search(flower, knn_context, sparse_count, // sparse_index, sparse_feature, qnum); if (ret < 0) { // cerr << "Failed to sparse_knn_search batch, ret=" << ret << " " // << IndexError::What(ret) << endl; // return; // } // for (size_t i = 0; i < qnum; ++i) { // size_t idx = index * batch_count_ + i; // if (idx >= linear_queries_.size()) { // break; // } // auto &knn_res = knn_context->result(i); // cal_recall(knn_res, idx); // } } else { int ret = do_knn_search(flower, knn_context, sparse_count[0], sparse_index, sparse_feature); if (ret < 0) { cerr << "Failed to sparse_knn_search, ret=" << ret << " " << IndexError::What(ret) << endl; return; } auto &knn_res = knn_context->result(); cal_recall(knn_res, index); } } private: IndexQueryMeta qmeta_{}; size_t threads_; bool call_batch_api_; string output_; size_t batch_count_; shared_ptr pool_; // for gt vector> linear_queries_; vector> linear_sparse_data_; vector linear_partitions_; vector> linear_taglists_; std::map>> linear_queries_scaled_; std::map>> linear_sparse_features_scaled_; // for recall vector> batch_queries_; vector> batch_sparse_counts_; vector> batch_sparse_indices_; vector> batch_sparse_features_; vector> batch_partitions_; vector>> batch_taglists_; std::map>> batch_queries_scaled_; std::map>> batch_sparse_features_scaled_; size_t total_querys_; map recall_res_; vector topk_ids_; vector>> gt_; map>>> gt_hybrid_; bool external_gt_file_enabled_{false}; FilterMode filter_mode_{FM_NONE}; static bool STOP_NOW; }; template bool SparseRecall::STOP_NOW = false; bool prepare_params(YAML::Node &&config_params, Params ¶ms) { cout << "Parse params as blow:" << endl; for (auto it = config_params.begin(); it != config_params.end(); ++it) { string tag = it->second.Tag(); if (tag == "tag:yaml.org,2002:int") { int64_t val = it->second.as(); params.set(it->first.as(), val); cout << it->first.as() << "=" << val << endl; } else if (tag == "tag:yaml.org,2002:float") { float val = it->second.as(); params.set(it->first.as(), val); cout << it->first.as() << "=" << val << endl; } else if (tag == "tag:yaml.org,2002:bool") { bool val = it->second.as(); params.set(it->first.as(), val); cout << it->first.as() << "=" << val << endl; } else { if (it->second.IsScalar()) { string val = it->second.as(); params.set(it->first.as(), val); cout << it->first.as() << "=" << val << endl; } else if (it->second.IsMap()) { Params sub_params; auto sub_node = it->second; if (!prepare_params(std::move(sub_node), sub_params)) { cerr << "parse params error with key[" << it->first.as() << "]" << endl; return false; } params.set(it->first.as(), sub_params); } } } return true; } bool check_config(YAML::Node &config_node) { auto common = config_node["SearcherCommon"]; if (!common) { cerr << "Can not find [SearcherCommon] in config" << endl; return false; } if (!common["SearcherClass"] && !common["SearcherConfig"]) { cerr << "Can not find [SearcherClass] or [SearcherConfig] in config" << endl; return false; } if (!common["IndexPath"]) { cerr << "Can not find [IndexPath] in config" << endl; return false; } if (!common["TopK"]) { cerr << "Can not find [TopK] in config" << endl; return false; } if (!common["QueryFile"]) { cerr << "Can not find [QueryFile] in config" << endl; return false; } return true; } void usage(void) { cout << "Usage: recall CONFIG.yaml [plugin file path]" << endl; } bool load_index(Flow &flower, string &index_dir) { int ret = flower.load(index_dir); if (0 != ret) { cerr << "Flow load failed with ret " << ret << endl; return false; } cout << "Load index done!" << endl; return true; }; int recall_dense(std::string &query_type, size_t thread_count, size_t batch_count, string top_k, size_t gt_count, string query_file, string &first_sep, string &second_sep, string &ground_truth_file, string &ground_truth_first_sep, string ground_truth_second_sep, Flow &flower, string &index_dir, string &log_dir, FilterMode filter_mode) { if (query_type == "float") { Recall recall(thread_count, log_dir, batch_count, filter_mode); if (!recall.load_query(query_file, first_sep, second_sep)) { return -1; } if (ground_truth_file != "") { if (!recall.load_external_gt_file(ground_truth_file, ground_truth_first_sep, ground_truth_second_sep)) { return -1; } } if (load_index(flower, index_dir)) { recall.run_dense(&flower, top_k, gt_count); } else { return -1; } } else if (query_type == "int8") { Recall recall(thread_count, log_dir, batch_count, filter_mode); if (!recall.load_query(query_file, first_sep, second_sep)) { return -1; } if (ground_truth_file != "") { if (!recall.load_external_gt_file(ground_truth_file, ground_truth_first_sep, ground_truth_second_sep)) { return -1; } } if (load_index(flower, index_dir)) { recall.run_dense(&flower, top_k, gt_count); } else { return -1; } } else if (query_type == "binary") { Recall recall(thread_count, log_dir, batch_count, filter_mode); if (!recall.load_query(query_file, first_sep, second_sep)) { return -1; } if (ground_truth_file != "") { if (!recall.load_external_gt_file(ground_truth_file, ground_truth_first_sep, ground_truth_second_sep)) { return -1; } } if (load_index(flower, index_dir)) { recall.run_dense(&flower, top_k, gt_count); } else { return -1; } } else if (query_type == "binary64") { Recall recall(thread_count, log_dir, batch_count, filter_mode); if (!recall.load_query(query_file, first_sep, second_sep)) { return -1; } if (ground_truth_file != "") { if (!recall.load_external_gt_file(ground_truth_file, ground_truth_first_sep, ground_truth_second_sep)) { return -1; } } if (load_index(flower, index_dir)) { recall.run_dense(&flower, top_k, gt_count); } else { return -1; } } else { cerr << "Can not recognize type: " << query_type << endl; } return 0; } bool load_sparse_index(SparseFlow &flower, string &index_dir) { int ret = flower.load(index_dir); if (0 != ret) { cerr << "Flow load failed with ret " << ret << endl; return false; } cout << "Load index done!" << endl; return true; }; int recall_sparse(std::string &query_type, size_t thread_count, size_t batch_count, string top_k, size_t gt_count, string &query_file, string &first_sep, string &second_sep, string &ground_truth_file, string &ground_truth_first_sep, string &ground_truth_second_sep, SparseFlow &flower, string &index_dir, string &log_dir, FilterMode filter_mode) { if (query_type == "float") { SparseRecall recall(thread_count, log_dir, batch_count, filter_mode); if (!recall.load_query(query_file, first_sep, second_sep)) { return -1; } if (ground_truth_file != "") { if (!recall.load_external_gt_file(ground_truth_file, ground_truth_first_sep, ground_truth_second_sep)) { return -1; } } if (load_sparse_index(flower, index_dir)) { recall.run_sparse(&flower, top_k, gt_count); } else { return -1; } } else { cerr << "Can not recognize type: " << query_type << endl; } return 0; } int get_recall_precision(string &recall_precision_string) { constexpr float DEFAULT_RECALL_PRECISION = 1e-6; if (recall_precision_string == "") { g_recall_precision = DEFAULT_RECALL_PRECISION; return true; } try { g_recall_precision = std::stof(recall_precision_string); std::cout << "Recall Score Precesion: " << g_recall_precision << std::endl; } catch (const std::invalid_argument &e) { std::cerr << "Exeception in getting recall precision: " << e.what() << ", value: " << recall_precision_string << std::endl; return false; } catch (const std::out_of_range &e) { std::cerr << "Out of range exception in getting recall precision: " << e.what() << ", value: " << recall_precision_string << std::endl; return false; } return true; } 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)) { cerr << "Failed to load plugin: " << argv[i] << " (" << error << ")" << endl; return -1; } } YAML::Node config_node; try { config_node = YAML::LoadFile(argv[1]); } catch (...) { cerr << "Load YAML file[" << argv[1] << "] failed!" << endl; return -1; } if (!check_config(config_node)) { return -1; } auto config_common = config_node["SearcherCommon"]; map 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() : "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]); } // Calculate Recall string log_dir = ""; if (config_common["RecallLogDir"]) { log_dir = config_common["RecallLogDir"].as(); } size_t thread_count = config_common["RecallThreadCount"] ? config_common["RecallThreadCount"].as() : 0; size_t gt_count = config_common["RecallGTCount"] ? config_common["RecallGTCount"].as() : 100; size_t batch_count = config_common["RecallBatchCount"] ? config_common["RecallBatchCount"].as() : 0; g_compare_by_id = config_common["CompareById"] ? config_common["CompareById"].as() : 0; string top_k = config_common["TopK"].as(); string recall_precision_string = config_common["RecallScorePrecision"] ? config_common["RecallScorePrecision"].as() : ""; if (!get_recall_precision(recall_precision_string)) { cerr << "Get recall precision failed, value: " << recall_precision_string << endl; return -1; } RetrievalMode retrieval_mode{RM_DENSE}; if (config_common["RetrievalMode"]) { std::string retrieval_mode_str = config_common["RetrievalMode"].as(); 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(); if (filter_mode_str == "tag") { filter_mode = FM_TAG; } } string query_file = config_common["QueryFile"].as(); string first_sep = config_common["QueryFirstSep"] ? config_common["QueryFirstSep"].as() : ";"; string second_sep = config_common["QuerySecondSep"] ? config_common["QuerySecondSep"].as() : " "; string query_type = config_common["QueryType"] ? config_common["QueryType"].as() : "float"; string container_type = config_common["ContainerType"] ? config_common["ContainerType"].as() : "MMapFileStorage"; string ground_truth_file = ""; string ground_truth_first_sep = ";"; string ground_truth_second_sep = " "; if (config_common["GroundTruthFile"]) { ground_truth_file = config_common["GroundTruthFile"].as(); if (config_common["GroundTruthFirstSep"]) { ground_truth_first_sep = config_common["GroundTruthFirstSep"].as(); } if (config_common["GroundTruthSecondSep"]) { ground_truth_second_sep = config_common["GroundTruthSecondSep"].as(); } } if (retrieval_mode == RM_SPARSE) { SparseFlow flower; Params container_params; if (config_node["ContainerParams"]) { // Get index params of Searcher in flower object if (!prepare_params(config_node["ContainerParams"], container_params)) { return -1; } cout << "Created index params of a container in flower object " << endl; } int ret = flower.set_container(container_type, container_params); if (0 != ret) { cerr << "Create" << container_type << "failed." << endl; return -1; } // Set a Searcher if (config_common["SearcherClass"]) { Params params; if (config_node["SearcherParams"]) { // Get index params of Searcher in flower object if (!prepare_params(config_node["SearcherParams"], params)) { return -1; } cout << "Created index params of a searcher in flower object " << endl; } string searcher_class = config_common["SearcherClass"].as(); ret = flower.set_searcher(searcher_class, params); if (0 != ret) { cerr << "Failed to create searcher " << searcher_class << endl; return -1; } cout << "Created searcher " << searcher_class << endl; } else { // SearcherConfig std::cout << config_common["SearcherConfig"].as() << std::endl; auto params = zvec::core_interface::IndexFactory::DeserializeIndexParamFromJson( config_common["SearcherConfig"].as()); auto index = zvec::core_interface::IndexFactory::CreateAndInitIndex(*params); flower.set_searcher(index->index_searcher()); } string index_dir = config_common["IndexPath"].as(); recall_sparse(query_type, thread_count, batch_count, top_k, gt_count, query_file, first_sep, second_sep, ground_truth_file, ground_truth_first_sep, ground_truth_second_sep, flower, index_dir, log_dir, filter_mode); flower.unload(); cout << "Recall done." << endl; } else { Flow flower; Params container_params; if (config_node["ContainerParams"]) { // Get index params of Searcher in flower object if (!prepare_params(config_node["ContainerParams"], container_params)) { return -1; } cout << "Created index params of a container in flower object " << endl; } int ret = flower.set_container(container_type, container_params); if (0 != ret) { cerr << "Create" << container_type << "failed." << endl; return -1; } // Set a Searcher if (config_common["SearcherClass"]) { Params params; if (config_node["SearcherParams"]) { // Get index params of Searcher in flower object if (!prepare_params(config_node["SearcherParams"], params)) { return -1; } cout << "Created index params of a searcher in flower object " << endl; } string searcher_class = config_common["SearcherClass"].as(); ret = flower.set_searcher(searcher_class, params); if (0 != ret) { cerr << "Failed to create searcher " << searcher_class << endl; return -1; } cout << "Created searcher " << searcher_class << endl; } else { // SearcherConfig std::cout << config_common["SearcherConfig"].as() << std::endl; auto params = zvec::core_interface::IndexFactory::DeserializeIndexParamFromJson( config_common["SearcherConfig"].as()); auto index = zvec::core_interface::IndexFactory::CreateAndInitIndex(*params); flower.set_searcher(index->index_searcher()); } string index_dir = config_common["IndexPath"].as(); if (retrieval_mode == RM_DENSE) { recall_dense(query_type, thread_count, batch_count, top_k, gt_count, query_file, first_sep, second_sep, ground_truth_file, ground_truth_first_sep, ground_truth_second_sep, flower, index_dir, log_dir, filter_mode); } else { std::string mode = retrieval_mode == 1 ? "Dense" : "Sparse"; cerr << "unsupported retrieval mode: " << mode << endl; return -1; } // Cleanup flower.unload(); cout << "Recall done." << endl; } return 0; }