Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions src/algorithm/hgraph/hgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,19 @@ HGraph::Tune(const std::string& parameters, bool disable_future_tuning) {

FlattenInterfacePtr tune_source;
if (is_tune_base_code or is_tune_precise_code or is_tune_raw_code) {
if (covers_active_ids(raw_vector_)) {
tune_source = raw_vector_;
} else if (covers_active_ids(high_precise_codes_) and
high_precise_codes_->GetQuantizerName() == QUANTIZATION_TYPE_VALUE_FP32) {
tune_source = high_precise_codes_;
} else if (covers_active_ids(basic_flatten_codes_) and
basic_flatten_codes_->GetQuantizerName() == QUANTIZATION_TYPE_VALUE_FP32) {
tune_source = basic_flatten_codes_;
} else {
auto select_larger_source = [&](const FlattenInterfacePtr& codes, bool require_fp32) {
if (codes == nullptr or
(require_fp32 and codes->GetQuantizerName() != QUANTIZATION_TYPE_VALUE_FP32)) {
return;
}
if (tune_source == nullptr or codes->TotalCount() > tune_source->TotalCount()) {
tune_source = codes;
}
};
select_larger_source(raw_vector_, false);
select_larger_source(high_precise_codes_, true);
select_larger_source(basic_flatten_codes_, true);
if (tune_source == nullptr) {
return false;
}
}
Expand All @@ -230,7 +234,12 @@ HGraph::Tune(const std::string& parameters, bool disable_future_tuning) {
}
};

auto train_count = std::min(this->train_sample_count_, this->GetNumElements());
// Add failures can leave the published HGraph count ahead of the available FP32 source.
// Rebuild the source's readable prefix and leave Add-state recovery to the Add path.
const auto source_count =
std::min(static_cast<int64_t>(tune_source == nullptr ? 0 : tune_source->TotalCount()),
static_cast<int64_t>(current_count));
auto train_count = std::min(this->train_sample_count_, source_count);
Comment thread
jac0626 marked this conversation as resolved.
Outdated
Vector<float> train_data(train_count * dim_, 0, allocator_);
if (is_tune_base_code or is_tune_precise_code or is_tune_raw_code) {
for (InnerIdType i = 0; i < train_count; i++) {
Expand Down Expand Up @@ -261,7 +270,7 @@ HGraph::Tune(const std::string& parameters, bool disable_future_tuning) {
new_code->Train(train_data.data(), train_count);

Vector<float> insert_buffer(dim_, 0, allocator_);
for (int64_t i = 0; i < total_count_; ++i) {
for (int64_t i = 0; i < source_count; ++i) {
decode_tune_source(i, insert_buffer.data());
new_code->InsertVector(static_cast<const void*>(insert_buffer.data()), i);
}
Expand Down
90 changes: 90 additions & 0 deletions src/algorithm/hgraph/hgraph_add_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
#include <algorithm>
#include <atomic>
#include <chrono>
#include <functional>
#include <future>
#include <initializer_list>
#include <new>
#include <sstream>
#include <string>
#include <thread>
Expand All @@ -35,6 +37,38 @@

namespace {

class ArmableRejectingThreadPool final : public vsag::ThreadPool {
public:
void
WaitUntilEmpty() override {
}

void
SetQueueSizeLimit(uint64_t) override {
}

void
SetPoolSize(uint64_t) override {
}

std::future<void>
Enqueue(std::function<void(void)> task) override {
if (reject_submissions_.load(std::memory_order_acquire)) {
throw std::bad_alloc();
}
task();
return {};
}
Comment thread
jac0626 marked this conversation as resolved.

void
SetRejectSubmissions(bool reject) {
reject_submissions_.store(reject, std::memory_order_release);
}

private:
std::atomic<bool> reject_submissions_{false};
};

vsag::DatasetPtr
MakeFloatDataset(std::vector<float>& vectors,
std::vector<int64_t>& ids,
Expand Down Expand Up @@ -162,6 +196,62 @@ const std::string kBruteForceSearchParams =

} // namespace

TEST_CASE("HGraph Tune accepts an incomplete source after Add failure",
"[ut][hgraph][add][hgraph_tune_incomplete_source]") {
constexpr int64_t dim = 4;
constexpr int64_t base_count = 2;

auto rejecting_pool = std::make_shared<ArmableRejectingThreadPool>();
auto common_param = MakeCommonParam(dim);
common_param.thread_pool_ = std::make_shared<vsag::SafeThreadPool>(rejecting_pool);
auto hgraph_json = vsag::JsonType::Parse(R"({
"base_quantization_type": "sq8",
"max_degree": 8,
"ef_construction": 32,
"build_thread_count": 1,
"store_raw_vector": true
})");
auto index = MakeHGraphIndex(hgraph_json, common_param);

std::vector<float> base_vectors = {
0.0F,
0.0F,
0.0F,
0.0F,
1.0F,
1.0F,
1.0F,
1.0F,
};
std::vector<int64_t> base_ids = {10, 20};
auto base = MakeFloatDataset(base_vectors, base_ids, dim, base_count);
REQUIRE(index->Build(base).has_value());
REQUIRE(index->GetNumElements() == base_count);

std::vector<float> add_vectors = {2.0F, 2.0F, 2.0F, 2.0F};
std::vector<int64_t> add_ids = {30};
auto add = MakeFloatDataset(add_vectors, add_ids, dim, 1);

rejecting_pool->SetRejectSubmissions(true);
auto add_result = index->Add(add);
rejecting_pool->SetRejectSubmissions(false);

REQUIRE_FALSE(add_result.has_value());
REQUIRE(add_result.error().type == vsag::ErrorType::NO_ENOUGH_MEMORY);
REQUIRE(index->GetNumElements() == base_count + 1);
REQUIRE(index->CheckIdExist(add_ids[0]));

auto tune_result = index->Tune(R"({
"index_param": {
"base_quantization_type": "bf16",
"max_degree": 8,
"ef_construction": 32
}
})");
REQUIRE(tune_result.has_value());
CHECK(tune_result.value());
}

TEST_CASE("HGraph exact duplicate fallback supports every dense data type",
"[ut][hgraph][duplicate][data_type]") {
constexpr int64_t dim = 8;
Expand Down
Loading