refactor: clarify HNSW 'm' as max neighbors of upper layer (#12)

* refactor: clarify HNSW 'm' as max neighbors of upper layer

- Distinguish l0/upper_max_neighbor_cnt
- Define l0_max_neighbor_cnt as m * l0_multiplier.

* fix ut
This commit is contained in:
Jalin Wang 2026-01-08 16:31:14 +08:00 committed by GitHub
parent 9c106775df
commit 84b5f03f5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 225 additions and 314 deletions

View File

@ -37,41 +37,43 @@ int HnswBuilder::init(const IndexMeta &meta, const ailego::Params &params) {
size_t memory_quota = 0UL;
params.get(PARAM_HNSW_BUILDER_MEMORY_QUOTA, &memory_quota);
params.get(PARAM_HNSW_BUILDER_THREAD_COUNT, &thread_cnt_);
params.get(PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT, &neighbor_cnt_);
params.get(PARAM_HNSW_BUILDER_MIN_NEIGHBOR_COUNT, &min_neighbor_cnt_);
float ratio = HnswEntity::kDefaultNeighborPruneRatio;
params.get(PARAM_HNSW_BUILDER_UPPER_NEIGHBOR_RATIO, &ratio);
upper_neighbor_cnt_ = ratio * neighbor_cnt_;
params.get(PARAM_HNSW_BUILDER_EFCONSTRUCTION, &ef_construction_);
params.get(PARAM_HNSW_BUILDER_SCALING_FACTOR, &scaling_factor_);
params.get(PARAM_HNSW_BUILDER_CHECK_INTERVAL_SECS, &check_interval_secs_);
ratio = HnswEntity::kDefaultNeighborPruneRatio;
params.get(PARAM_HNSW_BUILDER_NEIGHBOR_PRUNE_RATIO, &ratio);
size_t prune_cnt = neighbor_cnt_ * ratio;
params.get(PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT, &upper_max_neighbor_cnt_);
float multiplier = HnswEntity::kDefaultL0MaxNeighborCntMultiplier;
params.get(PARAM_HNSW_BUILDER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER, &multiplier);
l0_max_neighbor_cnt_ = multiplier * upper_max_neighbor_cnt_;
multiplier = HnswEntity::kDefaultNeighborPruneMultiplier;
params.get(PARAM_HNSW_BUILDER_NEIGHBOR_PRUNE_MULTIPLIER, &multiplier);
size_t prune_cnt = multiplier * upper_max_neighbor_cnt_;
if (ef_construction_ == 0) {
ef_construction_ = HnswEntity::kDefaultEfConstruction;
}
if (neighbor_cnt_ == 0) {
neighbor_cnt_ = HnswEntity::kDefaultNeighborCnt;
if (upper_max_neighbor_cnt_ == 0) {
upper_max_neighbor_cnt_ = HnswEntity::kDefaultUpperMaxNeighborCnt;
}
if (neighbor_cnt_ > kMaxNeighborCnt) {
if (upper_max_neighbor_cnt_ > kMaxNeighborCnt) {
LOG_ERROR("[%s] must be in range (0,%d]",
PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT.c_str(), kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
if (min_neighbor_cnt_ > neighbor_cnt_) {
if (min_neighbor_cnt_ > upper_max_neighbor_cnt_) {
LOG_ERROR("[%s]-[%d] must be <= [%s]-[%d]",
PARAM_HNSW_BUILDER_MIN_NEIGHBOR_COUNT.c_str(), min_neighbor_cnt_,
PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT.c_str(), neighbor_cnt_);
PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT.c_str(),
upper_max_neighbor_cnt_);
return IndexError_InvalidArgument;
}
if (upper_neighbor_cnt_ == 0) {
neighbor_cnt_ = HnswEntity::kDefaultUpperNeighborCnt;
if (l0_max_neighbor_cnt_ == 0) {
l0_max_neighbor_cnt_ = HnswEntity::kDefaultUpperMaxNeighborCnt;
}
if (upper_neighbor_cnt_ > HnswEntity::kMaxNeighborCnt) {
LOG_ERROR("UpperNeighborCnt must be in range (0,%d)",
if (l0_max_neighbor_cnt_ > HnswEntity::kMaxNeighborCnt) {
LOG_ERROR("L0MaxNeighborCnt must be in range (0,%d)",
HnswEntity::kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
@ -92,7 +94,7 @@ int HnswBuilder::init(const IndexMeta &meta, const ailego::Params &params) {
std::thread::hardware_concurrency());
}
if (prune_cnt == 0UL) {
prune_cnt = upper_neighbor_cnt_;
prune_cnt = upper_max_neighbor_cnt_;
}
metric_ = IndexFactory::CreateMetric(meta_.metric_name());
@ -109,9 +111,9 @@ int HnswBuilder::init(const IndexMeta &meta, const ailego::Params &params) {
entity_.set_vector_size(meta_.element_size());
entity_.set_ef_construction(ef_construction_);
entity_.set_neighbor_cnt(neighbor_cnt_);
entity_.set_l0_neighbor_cnt(l0_max_neighbor_cnt_);
entity_.set_min_neighbor_cnt(min_neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_max_neighbor_cnt_);
entity_.set_scaling_factor(scaling_factor_);
entity_.set_memory_quota(memory_quota);
entity_.set_prune_cnt(prune_cnt);
@ -131,10 +133,10 @@ int HnswBuilder::init(const IndexMeta &meta, const ailego::Params &params) {
state_ = BUILD_STATE_INITED;
LOG_INFO(
"End HnswBuilder::init, params: vectorSize=%u efConstruction=%u "
"neighborCnt=%u upperNeighborCnt=%u scalingFactor=%u "
"l0NeighborCnt=%u upperNeighborCnt=%u scalingFactor=%u "
"memoryQuota=%zu neighborPruneCnt=%zu metricName=%s ",
meta_.element_size(), ef_construction_, neighbor_cnt_,
upper_neighbor_cnt_, scaling_factor_, memory_quota, prune_cnt,
meta_.element_size(), ef_construction_, l0_max_neighbor_cnt_,
upper_max_neighbor_cnt_, scaling_factor_, memory_quota, prune_cnt,
meta_.metric_name().c_str());
return 0;
@ -143,9 +145,9 @@ int HnswBuilder::init(const IndexMeta &meta, const ailego::Params &params) {
int HnswBuilder::cleanup(void) {
LOG_INFO("Begin HnswBuilder::cleanup");
neighbor_cnt_ = HnswEntity::kDefaultNeighborCnt;
l0_max_neighbor_cnt_ = HnswEntity::kDefaultL0MaxNeighborCnt;
min_neighbor_cnt_ = 0;
upper_neighbor_cnt_ = HnswEntity::kDefaultUpperNeighborCnt;
upper_max_neighbor_cnt_ = HnswEntity::kDefaultUpperMaxNeighborCnt;
ef_construction_ = HnswEntity::kDefaultEfConstruction;
scaling_factor_ = HnswEntity::kDefaultScalingFactor;
check_interval_secs_ = kDefaultLogIntervalSecs;

View File

@ -71,9 +71,9 @@ class HnswBuilder : public IndexBuilder {
HnswBuilderEntity entity_{};
HnswAlgorithm::UPointer alg_; // impl graph algorithm
uint32_t thread_cnt_{0};
uint32_t neighbor_cnt_{HnswEntity::kDefaultNeighborCnt};
uint32_t min_neighbor_cnt_{0};
uint32_t upper_neighbor_cnt_{HnswEntity::kDefaultUpperNeighborCnt};
uint32_t upper_max_neighbor_cnt_{HnswEntity::kDefaultUpperMaxNeighborCnt};
uint32_t l0_max_neighbor_cnt_{HnswEntity::kDefaultL0MaxNeighborCnt};
uint32_t ef_construction_{HnswEntity::kDefaultEfConstruction};
uint32_t scaling_factor_{HnswEntity::kDefaultScalingFactor};
uint32_t check_interval_secs_{kDefaultLogIntervalSecs};

View File

@ -97,7 +97,7 @@ class HnswBuilderEntity : public HnswEntity {
//! Get neighbors size
inline size_t neighbors_size() const {
return sizeof(NeighborsHeader) + neighbor_cnt() * sizeof(node_id_t);
return sizeof(NeighborsHeader) + l0_neighbor_cnt() * sizeof(node_id_t);
}
//! Get upper neighbors size

View File

@ -47,7 +47,7 @@ int HnswContext::init(ContextType type) {
return ret;
}
candidates_.limit(max_scan_num_);
update_heap_.limit(entity_->neighbor_cnt() + 1);
update_heap_.limit(entity_->l0_neighbor_cnt() + 1);
break;
case kSearcherContext:
@ -74,7 +74,7 @@ int HnswContext::init(ContextType type) {
return ret;
}
update_heap_.limit(entity_->neighbor_cnt() + 1);
update_heap_.limit(entity_->l0_neighbor_cnt() + 1);
candidates_.limit(max_scan_num_);
check_need_adjuct_ctx();
@ -240,7 +240,7 @@ int HnswContext::update_context(ContextType type, const IndexMeta &meta,
return IndexError_Runtime;
}
update_heap_.limit(entity->neighbor_cnt() + 1);
update_heap_.limit(entity->l0_neighbor_cnt() + 1);
candidates_.limit(max_scan_num_);
topk_heap_.limit(std::max(topk_, ef_));
break;

View File

@ -224,7 +224,7 @@ int64_t HnswEntity::dump_graph_neighbors(
graph_meta.reserve(doc_cnt());
size_t offset = 0;
uint32_t crc = 0;
node_id_t mapping[neighbor_cnt()];
node_id_t mapping[l0_neighbor_cnt()];
uint32_t min_neighbor_count = 10000;
uint32_t max_neighbor_count = 0;
@ -234,7 +234,8 @@ int64_t HnswEntity::dump_graph_neighbors(
const Neighbors neighbors =
get_neighbors(0, reorder_mapping.empty() ? id : reorder_mapping[id]);
ailego_assert_with(!!neighbors.data, "invalid neighbors");
ailego_assert_with(neighbors.size() <= neighbor_cnt(), "invalid neighbors");
ailego_assert_with(neighbors.size() <= l0_neighbor_cnt(),
"invalid neighbors");
uint32_t neighbor_count = neighbors.size();
if (neighbor_count < min_neighbor_count) {

View File

@ -42,7 +42,7 @@ struct GraphHeader {
uint32_t doc_count;
uint32_t vector_size;
uint32_t node_size;
uint32_t max_neighbor_count;
uint32_t l0_neighbor_count;
uint32_t prune_type;
uint32_t prune_neighbor_count;
uint32_t ef_construction;
@ -58,7 +58,7 @@ static_assert(sizeof(GraphHeader) % 32 == 0,
struct HnswHeader {
uint32_t size; // header size
uint32_t revision; // current total docs of the graph
uint32_t thumb_neighbor_count;
uint32_t upper_neighbor_count;
uint32_t ef_construction;
uint32_t scaling_factor;
uint32_t max_level;
@ -100,12 +100,12 @@ struct HNSWHeader {
hnsw.size = sizeof(HnswHeader);
}
size_t neighbor_cnt() const {
return graph.max_neighbor_count;
size_t l0_neighbor_cnt() const {
return graph.l0_neighbor_count;
}
size_t upper_neighbor_cnt() const {
return hnsw.thumb_neighbor_count;
return hnsw.upper_neighbor_count;
}
size_t vector_size() const {
@ -201,13 +201,13 @@ class HnswEntity {
//! Get max neighbor size of graph level
inline size_t neighbor_cnt(level_t level) const {
return level == 0 ? header_.graph.max_neighbor_count
: header_.hnsw.thumb_neighbor_count;
return level == 0 ? header_.graph.l0_neighbor_count
: header_.hnsw.upper_neighbor_count;
}
//! get max neighbor size of graph level 0
inline size_t neighbor_cnt() const {
return header_.graph.max_neighbor_count;
inline size_t l0_neighbor_cnt() const {
return header_.graph.l0_neighbor_count;
}
//! get min neighbor size of graph
@ -217,7 +217,7 @@ class HnswEntity {
//! get upper neighbor size of graph level other than 0
inline size_t upper_neighbor_cnt() const {
return header_.hnsw.thumb_neighbor_count;
return header_.hnsw.upper_neighbor_count;
}
//! Get current total doc of the hnsw graph
@ -276,8 +276,8 @@ class HnswEntity {
header_.hnsw.scaling_factor = val;
}
void set_neighbor_cnt(size_t cnt) {
header_.graph.max_neighbor_count = cnt;
void set_l0_neighbor_cnt(size_t cnt) {
header_.graph.l0_neighbor_count = cnt;
}
void set_min_neighbor_cnt(size_t cnt) {
@ -285,7 +285,7 @@ class HnswEntity {
}
void set_upper_neighbor_cnt(size_t cnt) {
header_.hnsw.thumb_neighbor_count = cnt;
header_.hnsw.upper_neighbor_count = cnt;
}
void set_ef_construction(size_t ef) {
@ -499,8 +499,8 @@ class HnswEntity {
constexpr static size_t kMaxGraphLayers = 15;
constexpr static uint32_t kDefaultEfConstruction = 500;
constexpr static uint32_t kDefaultEf = 500;
constexpr static uint32_t kDefaultNeighborCnt = 100;
constexpr static uint32_t kDefaultUpperNeighborCnt = 50;
constexpr static uint32_t kDefaultUpperMaxNeighborCnt = 50; // M of HNSW
constexpr static uint32_t kDefaultL0MaxNeighborCnt = 100;
constexpr static uint32_t kMaxNeighborCnt = 65535;
constexpr static float kDefaultScanRatio = 0.1f;
constexpr static uint32_t kDefaultMinScanLimit = 10000;
@ -514,10 +514,10 @@ class HnswEntity {
constexpr static size_t kMaxChunkSize = 0xFFFFFFFF;
constexpr static size_t kDefaultChunkSize = 2UL * 1024UL * 1024UL;
constexpr static size_t kDefaultMaxChunkCnt = 50000UL;
constexpr static float kDefaultNeighborPruneRatio =
0.5f; // prune count / neighbor count
constexpr static float kDefaultUpperNeighborRatio =
0.5f; // upper neighbor count / neighbor count
constexpr static float kDefaultNeighborPruneMultiplier =
1.0f; // prune_cnt = upper_max_neighbor_cnt * multiplier
constexpr static float kDefaultL0MaxNeighborCntMultiplier =
2.0f; // l0_max_neighbor_cnt = upper_max_neighbor_cnt * multiplier
protected:
HNSWHeader header_{};

View File

@ -28,20 +28,14 @@ static const std::string PARAM_HNSW_BUILDER_SCALING_FACTOR(
"proxima.hnsw.builder.scaling_factor");
static const std::string PARAM_HNSW_BUILDER_CHECK_INTERVAL_SECS(
"proxima.hnsw.builder.check_interval_secs");
static const std::string PARAM_HNSW_BUILDER_NEIGHBOR_PRUNE_RATIO(
"proxima.hnsw.builder.neighbor_prune_ratio");
static const std::string PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT(
"proxima.hnsw.builder.max_neighbor_count");
static const std::string PARAM_HNSW_BUILDER_NEIGHBOR_PRUNE_MULTIPLIER(
"proxima.hnsw.builder.neighbor_prune_multiplier");
static const std::string PARAM_HNSW_BUILDER_MIN_NEIGHBOR_COUNT(
"proxima.hnsw.builder.min_neighbor_count");
static const std::string PARAM_HNSW_BUILDER_UPPER_NEIGHBOR_RATIO(
"proxima.hnsw.builder.upper_neighbor_ratio");
static const std::string PARAM_HNSW_BUILDER_HYBRID_VECTOR_ENABLE(
"proxima.hnsw.builder.hybrid_vector_enable");
static const std::string PARAM_HNSW_BUILDER_HYBRID_VECTOR_SEPARATE_NEIGHBOR(
"proxima.hnsw.builder.hybrid_vector_separate_neighbor");
static const std::string PARAM_HNSW_BUILDER_SPARSE_NEIGHBOR_RATIO(
"proxima.hnsw.builder.sparse_neighbor_ratio");
static const std::string PARAM_HNSW_BUILDER_MAX_NEIGHBOR_COUNT(
"proxima.hnsw.builder.max_neighbor_count");
static const std::string PARAM_HNSW_BUILDER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER(
"proxima.hnsw.builder.l0_max_neighbor_count_multiplier");
static const std::string PARAM_HNSW_SEARCHER_EF("proxima.hnsw.searcher.ef");
static const std::string PARAM_HNSW_SEARCHER_BRUTE_FORCE_THRESHOLD(
@ -70,8 +64,8 @@ static const std::string PARAM_HNSW_STREAMER_EFCONSTRUCTION(
"proxima.hnsw.streamer.efconstruction");
static const std::string PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT(
"proxima.hnsw.streamer.max_neighbor_count");
static const std::string PARAM_HNSW_STREAMER_UPPER_NEIGHBOR_RATIO(
"proxima.hnsw.streamer.upper_neighbor_ratio");
static const std::string PARAM_HNSW_STREAMER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER(
"proxima.hnsw.streamer.l0_max_neighbor_count_multiplier");
static const std::string PARAM_HNSW_STREAMER_SCALING_FACTOR(
"proxima.hnsw.streamer.scaling_factor");
static const std::string PARAM_HNSW_STREAMER_BRUTE_FORCE_THRESHOLD(
@ -88,8 +82,8 @@ static const std::string PARAM_HNSW_STREAMER_VISIT_BLOOMFILTER_NEGATIVE_PROB(
"proxima.hnsw.streamer.visit_bloomfilter_negative_prob");
static const std::string PARAM_HNSW_STREAMER_CHECK_CRC_ENABLE(
"proxima.hnsw.streamer.check_crc_enable");
static const std::string PARAM_HNSW_STREAMER_NEIGHBOR_PRUNE_RATIO(
"proxima.hnsw.streamer.neighbor_prune_ratio");
static const std::string PARAM_HNSW_STREAMER_NEIGHBOR_PRUNE_MULTIPLIER(
"proxima.hnsw.streamer.neighbor_prune_multiplier");
static const std::string PARAM_HNSW_STREAMER_CHUNK_SIZE(
"proxima.hnsw.streamer.chunk_size");
static const std::string PARAM_HNSW_STREAMER_FILTER_SAME_KEY(
@ -100,12 +94,6 @@ static const std::string PARAM_HNSW_STREAMER_MIN_NEIGHBOR_COUNT(
"proxima.hnsw.streamer.min_neighbor_count");
static const std::string PARAM_HNSW_STREAMER_FORCE_PADDING_RESULT_ENABLE(
"proxima.hnsw.streamer.force_padding_result_enable");
static const std::string PARAM_HNSW_STREAMER_HYBRID_VECTOR_ENABLE(
"proxima.hnsw.streamer.hybrid_vector_enable");
static const std::string PARAM_HNSW_STREAMER_HYBRID_VECTOR_SEPARATE_NEIGHBOR(
"proxima.hnsw.streamer.hybrid_vector_separate_neighbor");
static const std::string PARAM_HNSW_STREAMER_SPARSE_NEIGHBOR_RATIO(
"proxima.hnsw.streamer.sparse_neighbor_ratio");
static const std::string PARAM_HNSW_STREAMER_ESTIMATE_DOC_COUNT(
"proxima.hnsw.streamer.estimate_doc_count");
static const std::string PARAM_HNSW_STREAMER_USE_ID_MAP(

View File

@ -223,11 +223,11 @@ int HnswSearcherEntity::load(const IndexStorage::Pointer &container,
LOG_INFO(
"Index info: docCnt=%u entryPoint=%u maxLevel=%d efConstruct=%zu "
"neighborCnt=%zu upperNeighborCnt=%zu scalingFactor=%zu "
"l0NeighborCnt=%zu upperNeighborCnt=%zu scalingFactor=%zu "
"vectorSize=%zu nodeSize=%zu vectorSegmentSize=%zu keySegmentSize=%zu "
"neighborsSegmentSize=%zu neighborsMetaSegmentSize=%zu ",
doc_cnt(), entry_point(), cur_max_level(), ef_construction(),
neighbor_cnt(), upper_neighbor_cnt(), scaling_factor(), vector_size(),
l0_neighbor_cnt(), upper_neighbor_cnt(), scaling_factor(), vector_size(),
node_size(), vectors_->data_size(), keys_->data_size(),
neighbors_ == nullptr ? 0 : neighbors_->data_size(),
neighbors_meta_ == nullptr ? 0 : neighbors_meta_->data_size());
@ -263,7 +263,7 @@ int HnswSearcherEntity::load_segments(bool check_crc) {
}
memcpy(&hd.hnsw, data, sizeof(hd.hnsw));
*mutable_header() = hd;
segment_datas_.resize(std::max(neighbor_cnt(), upper_neighbor_cnt()));
segment_datas_.resize(std::max(l0_neighbor_cnt(), upper_neighbor_cnt()));
vectors_ = storage_->get(kGraphFeaturesSegmentId);
if (!vectors_) {
@ -403,7 +403,7 @@ int HnswSearcherEntity::get_fixed_neighbors(
return IndexError_InvalidArgument;
}
size_t fixed_neighbor_cnt = neighbor_cnt();
size_t fixed_neighbor_cnt = l0_neighbor_cnt();
fixed_neighbors->resize((fixed_neighbor_cnt + 1) * doc_cnt(), kInvalidNodeId);
size_t neighbors_cnt_offset = fixed_neighbor_cnt * doc_cnt();

View File

@ -112,7 +112,7 @@ class HnswSearcherEntity : public HnswEntity {
upper_neighbors_(neighbor_group.upper_neighbors),
upper_neighbors_meta_(neighbor_group.upper_neighbors_meta),
neighbors_in_memory_enabled_(neighbors_in_memory_enabled) {
segment_datas_.resize(std::max(neighbor_cnt(), upper_neighbor_cnt()),
segment_datas_.resize(std::max(l0_neighbor_cnt(), upper_neighbor_cnt()),
IndexStorage::SegmentData(0U, 0U));
fixed_neighbors_ = fixed_neighbors;
}
@ -120,7 +120,7 @@ class HnswSearcherEntity : public HnswEntity {
bool do_crc_check(std::vector<SegmentPointer> &segments) const;
inline size_t neighbors_size() const {
return sizeof(NeighborsHeader) + neighbor_cnt() * sizeof(node_id_t);
return sizeof(NeighborsHeader) + l0_neighbor_cnt() * sizeof(node_id_t);
}
inline size_t upper_neighbors_size() const {

View File

@ -38,10 +38,15 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
meta_.set_streamer("HnswStreamer", HnswEntity::kRevision, params);
params.get(PARAM_HNSW_STREAMER_MAX_INDEX_SIZE, &max_index_size_);
params.get(PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT, &neighbor_cnt_);
float ratio = HnswEntity::kDefaultUpperNeighborRatio;
params.get(PARAM_HNSW_STREAMER_UPPER_NEIGHBOR_RATIO, &ratio);
upper_neighbor_cnt_ = ratio * neighbor_cnt_;
params.get(PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT, &upper_max_neighbor_cnt_);
float multiplier = HnswEntity::kDefaultL0MaxNeighborCntMultiplier;
params.get(PARAM_HNSW_STREAMER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER, &multiplier);
l0_max_neighbor_cnt_ = multiplier * upper_max_neighbor_cnt_;
multiplier = HnswEntity::kDefaultNeighborPruneMultiplier;
params.get(PARAM_HNSW_STREAMER_NEIGHBOR_PRUNE_MULTIPLIER, &multiplier);
size_t prune_cnt = multiplier * upper_max_neighbor_cnt_;
params.get(PARAM_HNSW_STREAMER_DOCS_HARD_LIMIT, &docs_hard_limit_);
params.get(PARAM_HNSW_STREAMER_EF, &ef_);
@ -55,9 +60,6 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
params.get(PARAM_HNSW_STREAMER_MAX_SCAN_LIMIT, &max_scan_limit_);
params.get(PARAM_HNSW_STREAMER_MIN_SCAN_LIMIT, &min_scan_limit_);
params.get(PARAM_HNSW_STREAMER_CHECK_CRC_ENABLE, &check_crc_enabled_);
ratio = HnswEntity::kDefaultNeighborPruneRatio;
params.get(PARAM_HNSW_STREAMER_NEIGHBOR_PRUNE_RATIO, &ratio);
size_t prune_cnt = ratio * neighbor_cnt_;
params.get(PARAM_HNSW_STREAMER_CHUNK_SIZE, &chunk_size_);
params.get(PARAM_HNSW_STREAMER_FILTER_SAME_KEY, &filter_same_key_);
params.get(PARAM_HNSW_STREAMER_GET_VECTOR_ENABLE, &get_vector_enabled_);
@ -84,27 +86,28 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
if (ef_construction_ == 0U) {
ef_construction_ = HnswEntity::kDefaultEfConstruction;
}
if (neighbor_cnt_ == 0U) {
neighbor_cnt_ = HnswEntity::kDefaultNeighborCnt;
if (upper_max_neighbor_cnt_ == 0U) {
upper_max_neighbor_cnt_ = HnswEntity::kDefaultUpperMaxNeighborCnt;
}
if (neighbor_cnt_ > HnswEntity::kMaxNeighborCnt) {
if (upper_max_neighbor_cnt_ > HnswEntity::kMaxNeighborCnt) {
LOG_ERROR("[%s] must be in range (0,%d)",
PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT.c_str(),
HnswEntity::kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
if (upper_neighbor_cnt_ == 0U) {
upper_neighbor_cnt_ = HnswEntity::kDefaultUpperNeighborCnt;
if (l0_max_neighbor_cnt_ == 0U) {
l0_max_neighbor_cnt_ = HnswEntity::kDefaultL0MaxNeighborCnt;
}
if (upper_neighbor_cnt_ > HnswEntity::kMaxNeighborCnt) {
LOG_ERROR("UpperNeighborCnt must be in range (0,%d)",
if (l0_max_neighbor_cnt_ > HnswEntity::kMaxNeighborCnt) {
LOG_ERROR("MaxL0NeighborCnt must be in range (0,%d)",
HnswEntity::kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
if (min_neighbor_cnt_ > neighbor_cnt_) {
if (min_neighbor_cnt_ > upper_max_neighbor_cnt_) {
LOG_ERROR("[%s]-[%u] must be <= [%s]-[%u]",
PARAM_HNSW_STREAMER_MIN_NEIGHBOR_COUNT.c_str(), min_neighbor_cnt_,
PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT.c_str(), neighbor_cnt_);
PARAM_HNSW_STREAMER_MAX_NEIGHBOR_COUNT.c_str(),
upper_max_neighbor_cnt_);
return IndexError_InvalidArgument;
}
@ -137,7 +140,7 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
}
if (prune_cnt == 0UL) {
prune_cnt = upper_neighbor_cnt_;
prune_cnt = upper_max_neighbor_cnt_;
}
if (chunk_size_ == 0UL) {
chunk_size_ = HnswEntity::kDefaultChunkSize;
@ -149,8 +152,8 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
}
entity_.set_ef_construction(ef_construction_);
entity_.set_neighbor_cnt(neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_max_neighbor_cnt_);
entity_.set_l0_neighbor_cnt(l0_max_neighbor_cnt_);
entity_.set_scaling_factor(scaling_factor_);
entity_.set_prune_cnt(prune_cnt);
@ -169,18 +172,18 @@ int HnswStreamer::init(const IndexMeta &imeta, const ailego::Params &params) {
LOG_DEBUG(
"Init params: maxIndexSize=%zu docsHardLimit=%zu docsSoftLimit=%zu "
"efConstruction=%u ef=%u neighborCnt=%u upperNeighborCnt=%u "
"efConstruction=%u ef=%u upperMaxNeighborCnt=%u l0MaxNeighborCnt=%u "
"scalingFactor=%u maxScanRatio=%.3f minScanLimit=%zu maxScanLimit=%zu "
"bfEnabled=%d bruteFoceThreshold=%zu bfNegativeProbility=%.5f "
"checkCrcEnabled=%d pruneSize=%zu vectorSize=%u chunkSize=%zu "
"filterSameKey=%u getVectorEnabled=%u minNeighborCount=%u "
"forcePadding=%u ",
max_index_size_, docs_hard_limit_, docs_soft_limit_, ef_construction_,
ef_, neighbor_cnt_, upper_neighbor_cnt_, scaling_factor_, max_scan_ratio_,
min_scan_limit_, max_scan_limit_, bf_enabled_, bruteforce_threshold_,
bf_negative_prob_, check_crc_enabled_, prune_cnt, meta_.element_size(),
chunk_size_, filter_same_key_, get_vector_enabled_, min_neighbor_cnt_,
force_padding_topk_enabled_);
ef_, upper_max_neighbor_cnt_, l0_max_neighbor_cnt_, scaling_factor_,
max_scan_ratio_, min_scan_limit_, max_scan_limit_, bf_enabled_,
bruteforce_threshold_, bf_negative_prob_, check_crc_enabled_, prune_cnt,
meta_.element_size(), chunk_size_, filter_same_key_, get_vector_enabled_,
min_neighbor_cnt_, force_padding_topk_enabled_);
alg_ = HnswAlgorithm::UPointer(new HnswAlgorithm(entity_));
@ -213,8 +216,8 @@ int HnswStreamer::cleanup(void) {
max_index_size_ = 0UL;
docs_hard_limit_ = HnswEntity::kDefaultDocsHardLimit;
docs_soft_limit_ = 0UL;
neighbor_cnt_ = HnswEntity::kDefaultNeighborCnt;
upper_neighbor_cnt_ = HnswEntity::kDefaultUpperNeighborCnt;
upper_max_neighbor_cnt_ = HnswEntity::kDefaultUpperMaxNeighborCnt;
l0_max_neighbor_cnt_ = HnswEntity::kDefaultL0MaxNeighborCnt;
ef_ = HnswEntity::kDefaultEf;
ef_construction_ = HnswEntity::kDefaultEfConstruction;
bf_enabled_ = false;

View File

@ -199,9 +199,9 @@ class HnswStreamer : public IndexStreamer {
size_t chunk_size_{HnswEntity::kDefaultChunkSize};
size_t docs_hard_limit_{HnswEntity::kDefaultDocsHardLimit};
size_t docs_soft_limit_{0UL};
uint32_t neighbor_cnt_{HnswEntity::kDefaultNeighborCnt};
uint32_t min_neighbor_cnt_{0u};
uint32_t upper_neighbor_cnt_{HnswEntity::kDefaultUpperNeighborCnt};
uint32_t upper_max_neighbor_cnt_{HnswEntity::kDefaultUpperMaxNeighborCnt};
uint32_t l0_max_neighbor_cnt_{HnswEntity::kDefaultL0MaxNeighborCnt};
uint32_t ef_{HnswEntity::kDefaultEf};
uint32_t ef_construction_{HnswEntity::kDefaultEfConstruction};
uint32_t scaling_factor_{HnswEntity::kDefaultScalingFactor};

View File

@ -359,9 +359,9 @@ int HnswStreamerEntity::open(IndexStorage::Pointer stg, uint64_t max_index_size,
}
LOG_INFO(
"Open index, neighborCnt=%zu upperneighborCnt=%zu "
"Open index, l0NeighborCnt=%zu upperNeighborCnt=%zu "
"efConstruction=%zu curDocCnt=%u totalVecs=%u maxLevel=%u",
neighbor_cnt(), upper_neighbor_cnt(), ef_construction(), doc_cnt(),
l0_neighbor_cnt(), upper_neighbor_cnt(), ef_construction(), doc_cnt(),
total_vecs, cur_max_level());
//! try to correct the docCnt if index not fully flushed
if (doc_cnt() != total_vecs) {
@ -440,10 +440,10 @@ int HnswStreamerEntity::dump(const IndexDumper::Pointer &dumper) {
}
int HnswStreamerEntity::check_hnsw_index(const HNSWHeader *hd) const {
if (neighbor_cnt() != hd->neighbor_cnt() ||
if (l0_neighbor_cnt() != hd->l0_neighbor_cnt() ||
upper_neighbor_cnt() != hd->upper_neighbor_cnt()) {
LOG_ERROR("Param neighbors:%zu:%zu mismatch index previous %zu:%zu",
neighbor_cnt(), upper_neighbor_cnt(), hd->neighbor_cnt(),
LOG_ERROR("Param neighbor cnt: %zu:%zu mismatch index previous %zu:%zu",
l0_neighbor_cnt(), upper_neighbor_cnt(), hd->l0_neighbor_cnt(),
hd->upper_neighbor_cnt());
return IndexError_Mismatch;
}

View File

@ -178,26 +178,16 @@ class HnswStreamerEntity : public HnswEntity {
std::cout << "key map ends" << std::endl;
}
//! Get neighbors size
//! Get l0 neighbors size
inline size_t neighbors_size() const {
return sizeof(NeighborsHeader) + neighbor_cnt() * sizeof(node_id_t);
return sizeof(NeighborsHeader) + l0_neighbor_cnt() * sizeof(node_id_t);
}
//! Get neighbors size in hybrid mode
inline size_t neighbors_size_hybrid() const {
return sizeof(NeighborsHeader) * 2 + neighbor_cnt() * sizeof(node_id_t);
}
//! Get upper neighbors size
//! Get neighbors size for level > 0
inline size_t upper_neighbors_size() const {
return sizeof(NeighborsHeader) + upper_neighbor_cnt() * sizeof(node_id_t);
}
//! Get upper neighbors size in hybrid mode
inline size_t upper_neighbors_size_hybrid() const {
return 2 *
(sizeof(NeighborsHeader) + upper_neighbor_cnt() * sizeof(node_id_t));
}
private:
union UpperNeighborIndexMeta {

View File

@ -38,44 +38,47 @@ int HnswSparseBuilder::init(const IndexMeta &meta,
size_t memory_quota = 0UL;
params.get(PARAM_HNSW_SPARSE_BUILDER_MEMORY_QUOTA, &memory_quota);
params.get(PARAM_HNSW_SPARSE_BUILDER_THREAD_COUNT, &thread_cnt_);
params.get(PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT, &neighbor_cnt_);
params.get(PARAM_HNSW_SPARSE_BUILDER_MIN_NEIGHBOR_COUNT, &min_neighbor_cnt_);
float ratio = HnswSparseEntity::kDefaultNeighborPruneRatio;
params.get(PARAM_HNSW_SPARSE_BUILDER_UPPER_NEIGHBOR_RATIO, &ratio);
upper_neighbor_cnt_ = ratio * neighbor_cnt_;
params.get(PARAM_HNSW_SPARSE_BUILDER_EFCONSTRUCTION, &ef_construction_);
params.get(PARAM_HNSW_SPARSE_BUILDER_SCALING_FACTOR, &scaling_factor_);
params.get(PARAM_HNSW_SPARSE_BUILDER_CHECK_INTERVAL_SECS,
&check_interval_secs_);
ratio = HnswSparseEntity::kDefaultNeighborPruneRatio;
params.get(PARAM_HNSW_SPARSE_BUILDER_NEIGHBOR_PRUNE_RATIO, &ratio);
size_t prune_cnt = neighbor_cnt_ * ratio;
params.get(PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT,
&upper_max_neighbor_cnt_);
float multiplier = HnswSparseEntity::kDefaultL0MaxNeighborCntMultiplier;
params.get(PARAM_HNSW_SPARSE_BUILDER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER,
&multiplier);
l0_max_neighbor_cnt_ = multiplier * upper_max_neighbor_cnt_;
multiplier = HnswSparseEntity::kDefaultNeighborPruneMultiplier;
params.get(PARAM_HNSW_SPARSE_BUILDER_NEIGHBOR_PRUNE_MULTIPLIER, &multiplier);
size_t prune_cnt = multiplier * upper_max_neighbor_cnt_;
if (ef_construction_ == 0) {
ef_construction_ = HnswSparseEntity::kDefaultEfConstruction;
}
if (neighbor_cnt_ == 0) {
neighbor_cnt_ = HnswSparseEntity::kDefaultNeighborCnt;
if (upper_max_neighbor_cnt_ == 0) {
upper_max_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperMaxNeighborCnt;
}
if (neighbor_cnt_ > kMaxNeighborCnt) {
if (upper_max_neighbor_cnt_ > kMaxNeighborCnt) {
LOG_ERROR("[%s] must be in range (0,%d]",
PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT.c_str(),
kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
if (min_neighbor_cnt_ > neighbor_cnt_) {
LOG_ERROR(
"[%s]-[%d] must be <= [%s]-[%d]",
PARAM_HNSW_SPARSE_BUILDER_MIN_NEIGHBOR_COUNT.c_str(), min_neighbor_cnt_,
PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT.c_str(), neighbor_cnt_);
if (min_neighbor_cnt_ > upper_max_neighbor_cnt_) {
LOG_ERROR("[%s]-[%d] must be <= [%s]-[%d]",
PARAM_HNSW_SPARSE_BUILDER_MIN_NEIGHBOR_COUNT.c_str(),
min_neighbor_cnt_,
PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT.c_str(),
upper_max_neighbor_cnt_);
return IndexError_InvalidArgument;
}
if (upper_neighbor_cnt_ == 0) {
neighbor_cnt_ = HnswSparseEntity::kDefaultUpperNeighborCnt;
if (l0_max_neighbor_cnt_ == 0) {
l0_max_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperMaxNeighborCnt;
}
if (upper_neighbor_cnt_ > HnswSparseEntity::kMaxNeighborCnt) {
LOG_ERROR("UpperNeighborCnt must be in range (0,%d)",
if (l0_max_neighbor_cnt_ > HnswSparseEntity::kMaxNeighborCnt) {
LOG_ERROR("L0MaxNeighborCnt must be in range (0,%d)",
HnswSparseEntity::kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
@ -96,7 +99,7 @@ int HnswSparseBuilder::init(const IndexMeta &meta,
std::thread::hardware_concurrency());
}
if (prune_cnt == 0UL) {
prune_cnt = upper_neighbor_cnt_;
prune_cnt = upper_max_neighbor_cnt_;
}
metric_ = IndexFactory::CreateMetric(meta_.metric_name());
@ -111,9 +114,9 @@ int HnswSparseBuilder::init(const IndexMeta &meta,
}
entity_.set_ef_construction(ef_construction_);
entity_.set_neighbor_cnt(neighbor_cnt_);
entity_.set_l0_neighbor_cnt(l0_max_neighbor_cnt_);
entity_.set_min_neighbor_cnt(min_neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_max_neighbor_cnt_);
entity_.set_scaling_factor(scaling_factor_);
entity_.set_memory_quota(memory_quota);
entity_.set_prune_cnt(prune_cnt);
@ -136,10 +139,10 @@ int HnswSparseBuilder::init(const IndexMeta &meta,
state_ = BUILD_STATE_INITED;
LOG_INFO(
"End HnswSparseBuilder::init, params: efConstruction=%u "
"neighborCnt=%u upperNeighborCnt=%u scalingFactor=%u "
"l0NeighborCnt=%u upperNeighborCnt=%u scalingFactor=%u "
"memoryQuota=%zu neighborPruneCnt=%zu measureName=%s ",
ef_construction_, neighbor_cnt_, upper_neighbor_cnt_, scaling_factor_,
memory_quota, prune_cnt, meta_.metric_name().c_str());
ef_construction_, l0_max_neighbor_cnt_, upper_max_neighbor_cnt_,
scaling_factor_, memory_quota, prune_cnt, meta_.metric_name().c_str());
return 0;
}
@ -147,9 +150,9 @@ int HnswSparseBuilder::init(const IndexMeta &meta,
int HnswSparseBuilder::cleanup(void) {
LOG_INFO("Begin HnswSparseBuilder::cleanup");
neighbor_cnt_ = HnswSparseEntity::kDefaultNeighborCnt;
l0_max_neighbor_cnt_ = HnswSparseEntity::kDefaultL0MaxNeighborCnt;
min_neighbor_cnt_ = 0;
upper_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperNeighborCnt;
upper_max_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperMaxNeighborCnt;
ef_construction_ = HnswSparseEntity::kDefaultEfConstruction;
scaling_factor_ = HnswSparseEntity::kDefaultScalingFactor;
check_interval_secs_ = kDefaultLogIntervalSecs;

View File

@ -80,9 +80,10 @@ class HnswSparseBuilder : public IndexBuilder {
HnswSparseBuilderEntity entity_{};
HnswSparseAlgorithm::UPointer alg_; // impl graph algorithm
uint32_t thread_cnt_{0};
uint32_t neighbor_cnt_{HnswSparseEntity::kDefaultNeighborCnt};
uint32_t l0_max_neighbor_cnt_{HnswSparseEntity::kDefaultL0MaxNeighborCnt};
uint32_t min_neighbor_cnt_{0};
uint32_t upper_neighbor_cnt_{HnswSparseEntity::kDefaultUpperNeighborCnt};
uint32_t upper_max_neighbor_cnt_{
HnswSparseEntity::kDefaultUpperMaxNeighborCnt};
uint32_t ef_construction_{HnswSparseEntity::kDefaultEfConstruction};
uint32_t scaling_factor_{HnswSparseEntity::kDefaultScalingFactor};
uint32_t check_interval_secs_{kDefaultLogIntervalSecs};

View File

@ -119,7 +119,7 @@ class HnswSparseBuilderEntity : public HnswSparseEntity {
//! Get neighbors size
inline size_t neighbors_size() const {
return sizeof(NeighborsHeader) + neighbor_cnt() * sizeof(node_id_t);
return sizeof(NeighborsHeader) + l0_neighbor_cnt() * sizeof(node_id_t);
}
//! Get upper neighbors size

View File

@ -41,7 +41,7 @@ int HnswSparseContext::init(ContextType type) {
return ret;
}
candidates_.limit(max_scan_num_);
update_heap_.limit(entity_->neighbor_cnt() + 1);
update_heap_.limit(entity_->l0_neighbor_cnt() + 1);
break;
case kSparseSearcherContext:
@ -68,7 +68,7 @@ int HnswSparseContext::init(ContextType type) {
return ret;
}
update_heap_.limit(entity_->neighbor_cnt() + 1);
update_heap_.limit(entity_->l0_neighbor_cnt() + 1);
candidates_.limit(max_scan_num_);
check_need_adjuct_ctx();
@ -238,7 +238,7 @@ int HnswSparseContext::update_context(ContextType type,
return IndexError_Runtime;
}
update_heap_.limit(entity->neighbor_cnt() + 1);
update_heap_.limit(entity->l0_neighbor_cnt() + 1);
candidates_.limit(max_scan_num_);
topk_heap_.limit(std::max(topk_, ef_));
break;

View File

@ -299,7 +299,7 @@ int64_t HnswSparseEntity::dump_graph_neighbors(
graph_meta.reserve(doc_cnt());
size_t offset = 0;
uint32_t crc = 0;
node_id_t mapping[neighbor_cnt()];
node_id_t mapping[l0_neighbor_cnt()];
uint32_t min_neighbor_count = 10000;
uint32_t max_neighbor_count = 0;
@ -309,7 +309,8 @@ int64_t HnswSparseEntity::dump_graph_neighbors(
const Neighbors neighbors =
get_neighbors(0, reorder_mapping.empty() ? id : reorder_mapping[id]);
ailego_assert_with(!!neighbors.data, "invalid neighbors");
ailego_assert_with(neighbors.size() <= neighbor_cnt(), "invalid neighbors");
ailego_assert_with(neighbors.size() <= l0_neighbor_cnt(),
"invalid neighbors");
uint32_t neighbor_count = neighbors.size();
if (neighbor_count < min_neighbor_count) {

View File

@ -43,7 +43,7 @@ struct SparseGraphHeader {
uint32_t doc_count;
uint32_t vector_size;
uint32_t node_size;
uint32_t max_neighbor_count;
uint32_t l0_neighbor_count;
uint32_t prune_type;
uint32_t prune_neighbor_count;
uint32_t ef_construction;
@ -51,10 +51,8 @@ struct SparseGraphHeader {
uint32_t min_neighbor_count;
uint32_t sparse_meta_size;
uint32_t sparse_unit_size;
uint16_t sparse_max_neighbor_count;
uint16_t sparse_min_neighbor_count;
uint32_t total_sparse_count;
// uint8_t reserved_[4];
uint8_t reserved[868];
};
static_assert(sizeof(SparseGraphHeader) % 32 == 0,
@ -64,14 +62,13 @@ static_assert(sizeof(SparseGraphHeader) % 32 == 0,
struct HnswSparseHeader {
uint32_t size; // header size
uint32_t revision; // current total docs of the graph
uint32_t thumb_neighbor_count;
uint32_t upper_neighbor_count;
uint32_t ef_construction;
uint32_t scaling_factor;
uint32_t max_level;
uint32_t entry_point;
uint32_t options;
uint16_t thumb_sparse_neighbor_count;
uint8_t reserved_[30];
uint8_t reserved[30];
};
struct SparseData {
@ -123,11 +120,11 @@ struct HNSWSparseHeader {
}
size_t neighbor_cnt() const {
return graph.max_neighbor_count;
return graph.l0_neighbor_count;
}
size_t upper_neighbor_cnt() const {
return hnsw.thumb_neighbor_count;
return hnsw.upper_neighbor_count;
}
size_t vector_size() const {
@ -158,18 +155,6 @@ struct HNSWSparseHeader {
return graph.total_sparse_count;
}
size_t sparse_neighbor_cnt() const {
return graph.sparse_max_neighbor_count;
}
size_t sparse_min_neighbor_cnt() const {
return graph.sparse_min_neighbor_count;
}
size_t upper_sparse_neighbor_cnt() const {
return hnsw.thumb_sparse_neighbor_count;
}
SparseGraphHeader graph;
HnswSparseHeader hnsw;
};
@ -240,13 +225,13 @@ class HnswSparseEntity {
//! Get max neighbor size of graph level
inline size_t neighbor_cnt(level_t level) const {
return level == 0 ? header_.graph.max_neighbor_count
: header_.hnsw.thumb_neighbor_count;
return level == 0 ? header_.graph.l0_neighbor_count
: header_.hnsw.upper_neighbor_count;
}
//! get max neighbor size of graph level 0
inline size_t neighbor_cnt() const {
return header_.graph.max_neighbor_count;
inline size_t l0_neighbor_cnt() const {
return header_.graph.l0_neighbor_count;
}
//! get min neighbor size of graph
@ -256,61 +241,7 @@ class HnswSparseEntity {
//! get upper neighbor size of graph level other than 0
inline size_t upper_neighbor_cnt() const {
return header_.hnsw.thumb_neighbor_count;
}
size_t sparse_neighbor_cnt() const {
return header_.graph.sparse_max_neighbor_count;
}
size_t sparse_min_neighbor_cnt() const {
return header_.graph.sparse_min_neighbor_count;
}
size_t upper_sparse_neighbor_cnt() const {
return header_.hnsw.thumb_sparse_neighbor_count;
}
size_t dense_neighbor_cnt() const {
return header_.graph.max_neighbor_count -
header_.graph.sparse_max_neighbor_count;
}
size_t dense_min_neighbor_cnt() const {
return header_.graph.min_neighbor_count -
header_.graph.sparse_min_neighbor_count;
}
size_t upper_dense_neighbor_cnt() const {
return header_.hnsw.thumb_neighbor_count -
header_.hnsw.thumb_sparse_neighbor_count;
}
size_t sparse_neighbor_offset() const {
return sizeof(NeighborsHeader) + dense_neighbor_cnt() * sizeof(node_id_t);
}
size_t upper_sparse_neighbor_offset() const {
return sizeof(NeighborsHeader) +
upper_dense_neighbor_cnt() * sizeof(node_id_t);
}
size_t dense_neighbor_size() const {
return sizeof(NeighborsHeader) + dense_neighbor_cnt() * sizeof(node_id_t);
}
size_t sparse_neighbor_size() const {
return sizeof(NeighborsHeader) + sparse_neighbor_cnt() * sizeof(node_id_t);
}
size_t upper_dense_neighbor_size() const {
return sizeof(NeighborsHeader) +
upper_dense_neighbor_cnt() * sizeof(node_id_t);
}
size_t upper_sparse_neighbor_size() const {
return sizeof(NeighborsHeader) +
upper_sparse_neighbor_cnt() * sizeof(node_id_t);
return header_.hnsw.upper_neighbor_count;
}
//! Get current total doc of the hnsw graph
@ -387,8 +318,8 @@ class HnswSparseEntity {
header_.hnsw.scaling_factor = val;
}
void set_neighbor_cnt(size_t cnt) {
header_.graph.max_neighbor_count = cnt;
void set_l0_neighbor_cnt(size_t cnt) {
header_.graph.l0_neighbor_count = cnt;
}
void set_min_neighbor_cnt(size_t cnt) {
@ -396,7 +327,7 @@ class HnswSparseEntity {
}
void set_upper_neighbor_cnt(size_t cnt) {
header_.hnsw.thumb_neighbor_count = cnt;
header_.hnsw.upper_neighbor_count = cnt;
}
void set_ef_construction(size_t ef) {
@ -411,19 +342,6 @@ class HnswSparseEntity {
header_.graph.sparse_unit_size = size;
}
void set_sparse_neighbor_cnt(size_t cnt) {
header_.graph.sparse_max_neighbor_count = cnt;
}
void set_sparse_min_neighbor_cnt(size_t cnt) {
header_.graph.sparse_min_neighbor_count = cnt;
}
void set_upper_sparse_neighbor_cnt(size_t cnt) {
header_.hnsw.thumb_sparse_neighbor_count = cnt;
}
protected:
inline const HNSWSparseHeader &header() const {
return header_;
@ -670,8 +588,8 @@ class HnswSparseEntity {
constexpr static size_t kMaxGraphLayers = 15;
constexpr static uint32_t kDefaultEfConstruction = 500;
constexpr static uint32_t kDefaultEf = 500;
constexpr static uint32_t kDefaultNeighborCnt = 100;
constexpr static uint32_t kDefaultUpperNeighborCnt = 50;
constexpr static uint32_t kDefaultUpperMaxNeighborCnt = 50; // M of HNSW
constexpr static uint32_t kDefaultL0MaxNeighborCnt = 100;
constexpr static uint32_t kMaxNeighborCnt = 65535;
constexpr static float kDefaultScanRatio = 0.1f;
constexpr static uint32_t kDefaultMinScanLimit = 10000;
@ -685,12 +603,10 @@ class HnswSparseEntity {
constexpr static size_t kMaxChunkSize = 0xFFFFFFFF;
constexpr static size_t kDefaultChunkSize = 2UL * 1024UL * 1024UL;
constexpr static size_t kDefaultMaxChunkCnt = 50000UL;
constexpr static float kDefaultNeighborPruneRatio =
0.5f; // prune count / neighbor count
constexpr static float kDefaultUpperNeighborRatio =
0.5f; // upper neighbor count / neighbor count
constexpr static float kHybridNeighborRatio = 0.5f;
constexpr static float kDefaultNeighborPruneMultiplier =
1.0f; // prune_cnt = upper_max_neighbor_cnt * multiplier
constexpr static float kDefaultL0MaxNeighborCntMultiplier =
2.0f; // l0_max_neighbor_cnt = upper_max_neighbor_cnt * multiplier
constexpr static uint32_t kSparseMetaSize = 2u * sizeof(uint64_t);
constexpr static float kDefaultSparseNeighborRatio = 0.5f;

View File

@ -28,14 +28,15 @@ static const std::string PARAM_HNSW_SPARSE_BUILDER_SCALING_FACTOR(
"proxima.hnsw.sparse_builder.scaling_factor");
static const std::string PARAM_HNSW_SPARSE_BUILDER_CHECK_INTERVAL_SECS(
"proxima.hnsw.sparse_builder.check_interval_secs");
static const std::string PARAM_HNSW_SPARSE_BUILDER_NEIGHBOR_PRUNE_RATIO(
"proxima.hnsw.sparse_builder.neighbor_prune_ratio");
static const std::string PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT(
"proxima.hnsw.sparse_builder.max_neighbor_count");
static const std::string PARAM_HNSW_SPARSE_BUILDER_NEIGHBOR_PRUNE_MULTIPLIER(
"proxima.hnsw.sparse_builder.neighbor_prune_multiplier");
static const std::string PARAM_HNSW_SPARSE_BUILDER_MIN_NEIGHBOR_COUNT(
"proxima.hnsw.sparse_builder.min_neighbor_count");
static const std::string PARAM_HNSW_SPARSE_BUILDER_UPPER_NEIGHBOR_RATIO(
"proxima.hnsw.sparse_builder.upper_neighbor_ratio");
static const std::string PARAM_HNSW_SPARSE_BUILDER_MAX_NEIGHBOR_COUNT(
"proxima.hnsw.sparse_builder.max_neighbor_count");
static const std::string
PARAM_HNSW_SPARSE_BUILDER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER(
"proxima.hnsw.sparse_builder.l0_max_neighbor_count_multiplier");
static const std::string PARAM_HNSW_SPARSE_SEARCHER_EF(
"proxima.hnsw.sparse_searcher.ef");
@ -69,8 +70,9 @@ static const std::string PARAM_HNSW_SPARSE_STREAMER_EFCONSTRUCTION(
"proxima.hnsw.sparse_streamer.efconstruction");
static const std::string PARAM_HNSW_SPARSE_STREAMER_MAX_NEIGHBOR_COUNT(
"proxima.hnsw.sparse_streamer.max_neighbor_count");
static const std::string PARAM_HNSW_SPARSE_STREAMER_UPPER_NEIGHBOR_RATIO(
"proxima.hnsw.sparse_streamer.upper_neighbor_ratio");
static const std::string
PARAM_HNSW_SPARSE_STREAMER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER(
"proxima.hnsw.sparse_streamer.l0_max_neighbor_count_multiplier");
static const std::string PARAM_HNSW_SPARSE_STREAMER_SCALING_FACTOR(
"proxima.hnsw.sparse_streamer.scaling_factor");
static const std::string PARAM_HNSW_SPARSE_STREAMER_BRUTE_FORCE_THRESHOLD(
@ -88,8 +90,8 @@ static const std::string
"proxima.hnsw.sparse_streamer.visit_bloomfilter_negative_prob");
static const std::string PARAM_HNSW_SPARSE_STREAMER_CHECK_CRC_ENABLE(
"proxima.hnsw.sparse_streamer.check_crc_enable");
static const std::string PARAM_HNSW_SPARSE_STREAMER_NEIGHBOR_PRUNE_RATIO(
"proxima.hnsw.sparse_streamer.neighbor_prune_ratio");
static const std::string PARAM_HNSW_SPARSE_STREAMER_NEIGHBOR_PRUNE_MULTIPLIER(
"proxima.hnsw.sparse_streamer.neighbor_prune_multiplier");
static const std::string PARAM_HNSW_SPARSE_STREAMER_CHUNK_SIZE(
"proxima.hnsw.sparse_streamer.chunk_size");
static const std::string PARAM_HNSW_SPARSE_STREAMER_FILTER_SAME_KEY(

View File

@ -232,12 +232,12 @@ int HnswSparseSearcherEntity::load(const IndexStorage::Pointer &container,
LOG_INFO(
"Index info: docCnt=%u entryPoint=%u maxLevel=%d efConstruct=%zu "
"neighborCnt=%zu upperNeighborCnt=%zu scalingFactor=%zu "
"l0NeighborCnt=%zu upperNeighborCnt=%zu scalingFactor=%zu "
"nodeSize=%zu sparesMetaSegmentSize=%zu keySegmentSize=%zu "
"neighborsSegmentSize=%zu neighborsMetaSegmentSize=%zu "
"sparseVectorSegmentSize=%zu",
doc_cnt(), entry_point(), cur_max_level(), ef_construction(),
neighbor_cnt(), upper_neighbor_cnt(), scaling_factor(), node_size(),
l0_neighbor_cnt(), upper_neighbor_cnt(), scaling_factor(), node_size(),
sparse_vector_meta_->data_size(), keys_->data_size(),
neighbors_->data_size(), neighbors_meta_->data_size(),
sparse_vectors_->data_size());
@ -274,7 +274,7 @@ int HnswSparseSearcherEntity::load_segments(bool check_crc) {
}
memcpy(&hd.hnsw, data, sizeof(hd.hnsw));
*mutable_header() = hd;
segment_datas_.resize(std::max(neighbor_cnt(), upper_neighbor_cnt()));
segment_datas_.resize(std::max(l0_neighbor_cnt(), upper_neighbor_cnt()));
sparse_vector_meta_ = container_->get(kSparseGraphVectorMetaSegmentId);
if (!sparse_vector_meta_) {
@ -427,7 +427,7 @@ int HnswSparseSearcherEntity::get_fixed_neighbors(
return IndexError_InvalidArgument;
}
size_t fixed_neighbor_cnt = neighbor_cnt();
size_t fixed_neighbor_cnt = l0_neighbor_cnt();
fixed_neighbors->resize((fixed_neighbor_cnt + 1) * doc_cnt(), kInvalidNodeId);
size_t neighbors_cnt_offset = fixed_neighbor_cnt * doc_cnt();

View File

@ -137,7 +137,7 @@ class HnswSparseSearcherEntity : public HnswSparseEntity {
sparse_vector_meta_(sparse_vector_meta),
sparse_vectors_(sparse_vectors),
neighbors_in_memory_enabled_(neighbors_in_memory_enabled) {
segment_datas_.resize(std::max(neighbor_cnt(), upper_neighbor_cnt()),
segment_datas_.resize(std::max(l0_neighbor_cnt(), upper_neighbor_cnt()),
IndexStorage::SegmentData(0U, 0U));
fixed_neighbors_ = fixed_neighbors;
}
@ -145,7 +145,7 @@ class HnswSparseSearcherEntity : public HnswSparseEntity {
bool do_crc_check(std::vector<SegmentPointer> &segments) const;
inline size_t neighbors_size() const {
return sizeof(NeighborsHeader) + neighbor_cnt() * sizeof(node_id_t);
return sizeof(NeighborsHeader) + l0_neighbor_cnt() * sizeof(node_id_t);
}
inline size_t upper_neighbors_size() const {

View File

@ -38,10 +38,16 @@ int HnswSparseStreamer::init(const IndexMeta &imeta,
meta_.set_streamer("HnswSparseStreamer", HnswSparseEntity::kRevision, params);
params.get(PARAM_HNSW_SPARSE_STREAMER_MAX_INDEX_SIZE, &max_index_size_);
params.get(PARAM_HNSW_SPARSE_STREAMER_MAX_NEIGHBOR_COUNT, &neighbor_cnt_);
float ratio = HnswSparseEntity::kDefaultUpperNeighborRatio;
params.get(PARAM_HNSW_SPARSE_STREAMER_UPPER_NEIGHBOR_RATIO, &ratio);
upper_neighbor_cnt_ = ratio * neighbor_cnt_;
params.get(PARAM_HNSW_SPARSE_STREAMER_MAX_NEIGHBOR_COUNT,
&upper_max_neighbor_cnt_);
float multiplier = HnswSparseEntity::kDefaultL0MaxNeighborCntMultiplier;
params.get(PARAM_HNSW_SPARSE_STREAMER_L0_MAX_NEIGHBOR_COUNT_MULTIPLIER,
&multiplier);
l0_max_neighbor_cnt_ = multiplier * upper_max_neighbor_cnt_;
multiplier = HnswSparseEntity::kDefaultNeighborPruneMultiplier;
params.get(PARAM_HNSW_SPARSE_STREAMER_NEIGHBOR_PRUNE_MULTIPLIER, &multiplier);
size_t prune_cnt = multiplier * upper_max_neighbor_cnt_;
params.get(PARAM_HNSW_SPARSE_STREAMER_DOCS_HARD_LIMIT, &docs_hard_limit_);
params.get(PARAM_HNSW_SPARSE_STREAMER_EF, &ef_);
@ -56,9 +62,7 @@ int HnswSparseStreamer::init(const IndexMeta &imeta,
params.get(PARAM_HNSW_SPARSE_STREAMER_MAX_SCAN_LIMIT, &max_scan_limit_);
params.get(PARAM_HNSW_SPARSE_STREAMER_MIN_SCAN_LIMIT, &min_scan_limit_);
params.get(PARAM_HNSW_SPARSE_STREAMER_CHECK_CRC_ENABLE, &check_crc_enabled_);
ratio = HnswSparseEntity::kDefaultNeighborPruneRatio;
params.get(PARAM_HNSW_SPARSE_STREAMER_NEIGHBOR_PRUNE_RATIO, &ratio);
size_t prune_cnt = ratio * neighbor_cnt_;
params.get(PARAM_HNSW_SPARSE_STREAMER_CHUNK_SIZE, &chunk_size_);
params.get(PARAM_HNSW_SPARSE_STREAMER_FILTER_SAME_KEY, &filter_same_key_);
params.get(PARAM_HNSW_SPARSE_STREAMER_GET_VECTOR_ENABLE,
@ -88,29 +92,29 @@ int HnswSparseStreamer::init(const IndexMeta &imeta,
if (ef_construction_ == 0U) {
ef_construction_ = HnswSparseEntity::kDefaultEfConstruction;
}
if (neighbor_cnt_ == 0U) {
neighbor_cnt_ = HnswSparseEntity::kDefaultNeighborCnt;
if (upper_max_neighbor_cnt_ == 0U) {
upper_max_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperMaxNeighborCnt;
}
if (neighbor_cnt_ > HnswSparseEntity::kMaxNeighborCnt) {
if (upper_max_neighbor_cnt_ > HnswSparseEntity::kMaxNeighborCnt) {
LOG_ERROR("[%s] must be in range (0,%d)",
PARAM_HNSW_SPARSE_STREAMER_MAX_NEIGHBOR_COUNT.c_str(),
HnswSparseEntity::kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
if (upper_neighbor_cnt_ == 0U) {
upper_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperNeighborCnt;
if (l0_max_neighbor_cnt_ == 0U) {
l0_max_neighbor_cnt_ = HnswSparseEntity::kDefaultL0MaxNeighborCnt;
}
if (upper_neighbor_cnt_ > HnswSparseEntity::kMaxNeighborCnt) {
if (l0_max_neighbor_cnt_ > HnswSparseEntity::kMaxNeighborCnt) {
LOG_ERROR("UpperNeighborCnt must be in range (0,%d)",
HnswSparseEntity::kMaxNeighborCnt);
return IndexError_InvalidArgument;
}
if (min_neighbor_cnt_ > neighbor_cnt_) {
if (min_neighbor_cnt_ > upper_max_neighbor_cnt_) {
LOG_ERROR("[%s]-[%u] must be <= [%s]-[%u]",
PARAM_HNSW_SPARSE_STREAMER_MIN_NEIGHBOR_COUNT.c_str(),
min_neighbor_cnt_,
PARAM_HNSW_SPARSE_STREAMER_MAX_NEIGHBOR_COUNT.c_str(),
neighbor_cnt_);
upper_max_neighbor_cnt_);
return IndexError_InvalidArgument;
}
@ -144,7 +148,7 @@ int HnswSparseStreamer::init(const IndexMeta &imeta,
}
if (prune_cnt == 0UL) {
prune_cnt = upper_neighbor_cnt_;
prune_cnt = upper_max_neighbor_cnt_;
}
if (chunk_size_ == 0UL) {
chunk_size_ = HnswSparseEntity::kDefaultChunkSize;
@ -164,8 +168,8 @@ int HnswSparseStreamer::init(const IndexMeta &imeta,
}
entity_.set_ef_construction(ef_construction_);
entity_.set_neighbor_cnt(neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_neighbor_cnt_);
entity_.set_l0_neighbor_cnt(l0_max_neighbor_cnt_);
entity_.set_upper_neighbor_cnt(upper_max_neighbor_cnt_);
entity_.set_scaling_factor(scaling_factor_);
entity_.set_prune_cnt(prune_cnt);
@ -184,17 +188,17 @@ int HnswSparseStreamer::init(const IndexMeta &imeta,
}
LOG_DEBUG(
"Init params: maxIndexSize=%zu docsHardLimit=%zu docsSoftLimit=%zu "
"efConstruction=%u ef=%u neighborCnt=%u upperNeighborCnt=%u "
"efConstruction=%u ef=%u l0NeighborCnt=%u upperNeighborCnt=%u "
"scalingFactor=%u maxScanRatio=%.3f minScanLimit=%zu maxScanLimit=%zu "
"bfEnabled=%d bruteFoceThreshold=%zu bfNegativeProbility=%.5f "
"checkCrcEnabled=%d pruneSize=%zu chunkSize=%zu "
"filterSameKey=%u getVectorEnabled=%u "
"minNeighborCount=%u forcePadding=%u filteringRatio=%f",
max_index_size_, docs_hard_limit_, docs_soft_limit_, ef_construction_,
ef_, neighbor_cnt_, upper_neighbor_cnt_, scaling_factor_, max_scan_ratio_,
min_scan_limit_, max_scan_limit_, bf_enabled_, bruteforce_threshold_,
bf_negative_prob_, check_crc_enabled_, prune_cnt, chunk_size_,
filter_same_key_, get_vector_enabled_, min_neighbor_cnt_,
ef_, l0_max_neighbor_cnt_, upper_max_neighbor_cnt_, scaling_factor_,
max_scan_ratio_, min_scan_limit_, max_scan_limit_, bf_enabled_,
bruteforce_threshold_, bf_negative_prob_, check_crc_enabled_, prune_cnt,
chunk_size_, filter_same_key_, get_vector_enabled_, min_neighbor_cnt_,
force_padding_topk_enabled_, query_filtering_ratio_);
alg_ = HnswSparseAlgorithm::UPointer(new HnswSparseAlgorithm(entity_));
@ -228,8 +232,7 @@ int HnswSparseStreamer::cleanup(void) {
max_index_size_ = 0UL;
docs_hard_limit_ = HnswSparseEntity::kDefaultDocsHardLimit;
docs_soft_limit_ = 0UL;
neighbor_cnt_ = HnswSparseEntity::kDefaultNeighborCnt;
upper_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperNeighborCnt;
upper_max_neighbor_cnt_ = HnswSparseEntity::kDefaultUpperMaxNeighborCnt;
ef_ = HnswSparseEntity::kDefaultEf;
ef_construction_ = HnswSparseEntity::kDefaultEfConstruction;
bf_enabled_ = false;

View File

@ -190,9 +190,10 @@ class HnswSparseStreamer : public IndexStreamer {
size_t chunk_size_{HnswSparseEntity::kDefaultChunkSize};
size_t docs_hard_limit_{HnswSparseEntity::kDefaultDocsHardLimit};
size_t docs_soft_limit_{0UL};
uint32_t neighbor_cnt_{HnswSparseEntity::kDefaultNeighborCnt};
uint32_t min_neighbor_cnt_{0u};
uint32_t upper_neighbor_cnt_{HnswSparseEntity::kDefaultUpperNeighborCnt};
uint32_t upper_max_neighbor_cnt_{
HnswSparseEntity::kDefaultUpperMaxNeighborCnt};
uint32_t l0_max_neighbor_cnt_{HnswSparseEntity::kDefaultL0MaxNeighborCnt};
uint32_t ef_{HnswSparseEntity::kDefaultEf};
uint32_t ef_construction_{HnswSparseEntity::kDefaultEfConstruction};
uint32_t scaling_factor_{HnswSparseEntity::kDefaultScalingFactor};

View File

@ -472,9 +472,9 @@ int HnswSparseStreamerEntity::open(IndexStorage::Pointer stg, bool check_crc) {
}
LOG_INFO(
"Open index, neighborCnt=%zu upperneighborCnt=%zu "
"Open index, l0NeighborCnt=%zu upperneighborCnt=%zu "
"efConstruction=%zu curDocCnt=%u totalVecs=%u maxLevel=%u",
neighbor_cnt(), upper_neighbor_cnt(), ef_construction(), doc_cnt(),
l0_neighbor_cnt(), upper_neighbor_cnt(), ef_construction(), doc_cnt(),
total_vecs, cur_max_level());
//! try to correct the docCnt if index not fully flushed
if (doc_cnt() != total_vecs) {
@ -551,10 +551,10 @@ int HnswSparseStreamerEntity::dump(const IndexDumper::Pointer &dumper) {
int HnswSparseStreamerEntity::check_hnsw_index(
const HNSWSparseHeader *hd) const {
if (neighbor_cnt() != hd->neighbor_cnt() ||
if (l0_neighbor_cnt() != hd->neighbor_cnt() ||
upper_neighbor_cnt() != hd->upper_neighbor_cnt()) {
LOG_ERROR("Param neighbors:%zu:%zu mismatch index previous %zu:%zu",
neighbor_cnt(), upper_neighbor_cnt(), hd->neighbor_cnt(),
l0_neighbor_cnt(), upper_neighbor_cnt(), hd->neighbor_cnt(),
hd->upper_neighbor_cnt());
return IndexError_Mismatch;
}

View File

@ -195,7 +195,7 @@ class HnswSparseStreamerEntity : public HnswSparseEntity {
//! Get neighbors size
inline size_t neighbors_size() const {
return sizeof(NeighborsHeader) + neighbor_cnt() * sizeof(node_id_t);
return sizeof(NeighborsHeader) + l0_neighbor_cnt() * sizeof(node_id_t);
}
//! Get upper neighbors size