Skip to content

feat: add AutoTune V1 orchestration - #2569

Open
jac0626 wants to merge 8 commits into
antgroup:mainfrom
jac0626:codex/autotune-v1-minimal
Open

feat: add AutoTune V1 orchestration#2569
jac0626 wants to merge 8 commits into
antgroup:mainfrom
jac0626:codex/autotune-v1-minimal

Conversation

@jac0626

@jac0626 jac0626 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Change Type

  • Bug fix
  • New feature
  • Improvement/Refactor
  • Documentation
  • CI/Build/Infra

Linked Issue

What Changed

  • Add an experimental AutoTune V1 CLI and build-tree-only typed C++ API.
  • Generate HGraph and IVF build/search candidates, reuse equivalent builds, evaluate real VSAG indexes, apply constraints, and select recommendation or best-effort evidence.
  • Support search-only tuning for existing HGraph, IVF, and Pyramid indexes.
  • Extract reusable in-memory build/search evaluation entry points from eval_performance while preserving its existing file-based flow.
  • Add structured reports, artifact lifecycle handling, examples, focused tests, and synchronized English/Chinese documentation.

V1 intentionally supports one dense float32 KNN workload. SINDI, Multi, sparse vectors, cross-request caching, model-based proposals, and a public Factory::CreateIndexWithConstraints API remain out of scope.

Test Evidence

  • make fmt
  • make lint
  • make test
  • make cov, run tests, and collect coverage
  • Other (describe below)

Test details:

clang-format-15 --dry-run --Werror <24 changed C++ files>
  passed

git diff --check
  passed

./build/tools/autotune/autotune_test --rng-seed 12345
  All tests passed (242 assertions in 23 test cases)

./build/tools/eval/eval_dataset_test --rng-seed 12345
  All tests passed (119 assertions in 14 test cases)

cmake --build build-release --target autotune \
  326_feature_create_index_with_constraints \
  327_feature_autotune_existing_index \
  328_feature_autotune_existing_pyramid -j$(nproc)
  passed

All three Release examples completed successfully.

Full `make test` and ASan were not run for this review cycle.

Compatibility Impact

  • API/ABI compatibility: No installed SDK API or ABI change. The typed AutoTune API is experimental and build-tree-only.
  • Behavior changes: Adds an opt-in tool and reusable eval entry points; existing eval_performance commands remain supported.

Performance and Concurrency Impact

  • Performance impact: Production query paths are unchanged. AutoTune itself performs real build/search trials and can be expensive by design.
  • Concurrency/thread-safety impact: AutoTune requests are serialized around non-thread-safe HDF5 loading and shared evaluation state.

Documentation Impact

  • No docs update needed
  • Updated docs:
    • README.md
    • DEVELOPMENT.md
    • CONTRIBUTING.md
    • Other: docs/docs/{en,zh}/src/resources/autotune*.md and tools/autotune/README*.md

Risk and Rollback

  • Risk level: medium
  • Rollback plan: Revert this PR; the feature is isolated behind the tools build and does not alter installed SDK APIs.

Checklist

  • I have linked the relevant issue (required for kind/bug and kind/feature; see "Linked Issue" above)
  • I have added/updated tests for new behavior or bug fixes
  • I have considered API compatibility impact
  • I have updated docs if behavior/workflow changed
  • My commit messages follow project conventions (Conventional Commits, optional [skip ci] prefix)

Copilot AI review requested due to automatic review settings July 29, 2026 09:38
@vsag-bot

vsag-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

/label status/waiting-for-review
/waiting-on reviewer
/request-review @jiaweizone
/request-review @wxyucs
/request-review @inabao

@vsag-bot

vsag-bot commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Automated pull request review completed.

Review effort: high (6838 changed lines across 43 files).

Submitted 5 inline comments.
Review: #2569 (review)

@jac0626 jac0626 added kind/feature Brand-new functionality or capabilities 引入全新的功能、新特性或新能力 version/1.1 module/tools module/testing module/docs module/example labels Jul 29, 2026
@mergify

mergify Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 3 merge protections satisfied — ready to merge.

Show 3 satisfied protections

🟢 Require kind label

  • label~=^kind/

🟢 Require version label

  • label~=^version/

🟢 Require linked issue for feature/bug PRs

  • body~=(?im)(?:^|[\s\-\*])(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*:?\s+(?:#\d+|[\w.\-]+/[\w.\-]+#\d+|https?://github\.com/[\w.\-]+/[\w.\-]+/issues/\d+)

@jac0626
jac0626 marked this pull request as ready for review July 29, 2026 09:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated inline review completed.

Review effort: high (6342 changed lines across 41 files).
Submitted 1 inline comment.

Reviewed commit 998445f.

Comment thread tools/eval/case/build_eval_case.cpp
Comment thread tools/autotune/autotune_evaluation.cpp Outdated
Comment thread tools/eval/case/search_eval_case.cpp
Comment thread tools/autotune/autotune.cpp
Comment thread tools/autotune/autotune_candidate.cpp
Comment thread tools/autotune/autotune.cpp
Comment thread tools/eval/eval_dataset.cpp
Comment thread tools/autotune/autotune.cpp
Comment thread tools/autotune/autotune_evaluation.cpp
Comment thread tools/eval/eval_dataset.cpp
uint64_t maximum = 1;
auto low = range->start;
while (low < range->stop) {
const auto high = low > range->stop / 2 ? range->stop : low * 2;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] maximum_trial_count: same low * 2 overflow as EvaluateEfSearchRange

Same signed integer overflow risk at line 334: low * 2 when low > INT64_MAX / 2. The fix should mirror whatever approach is taken for the corresponding expression in autotune_evaluation.cpp:161.

report_path = context.result_path.empty()
? context.workspace_path + "/" + run_name + ".json"
: context.result_path;
stage = "report";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[note] stage = "report" set before prepare_report_path may misclassify path errors

At line 893, stage is set to "report" before prepare_report_path(report_path) is called at line 894. If prepare_report_path throws (e.g., permission denied), the failure lambda will use stage == "report" and classify the error as "execution_failed" rather than "invalid_request". Since the report path is part of the user-provided request configuration, a path error is arguably a validation failure.

Consider moving stage = "report" to after the prepare_report_path call, or using a more specific stage name for the report-path validation.

Copilot AI review requested due to automatic review settings July 31, 2026 13:41
@vsag-bot
vsag-bot self-requested a review July 31, 2026 13:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated inline review completed.

Review effort: high (6838 changed lines across 43 files).
Submitted 3 inline comments.

Reviewed commit 5f1c4cf.


if (range["start"].is_number_integer() && range["stop"].is_number_integer() &&
range["step"].is_number_integer()) {
const auto start = range["start"].get<int64_t>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] [P2] Reject unsigned range endpoints before signed conversion

is_number_integer() also accepts nlohmann's unsigned-number type, so a JSON bound such as 9223372036854775808 reaches get<int64_t>() and is converted out of range (the reproducer emits INT64_MIN). A one-point range at that value is consequently reported and evaluated as a negative parameter instead of being rejected or preserved, silently changing the requested candidate space. Validate unsigned start/stop/step against INT64_MAX before conversion, or expand them using an unsigned representation.

(step < 0.0 && current < stop && stop - current > tolerance)) {
return;
}
if (std::abs(current - stop) <= tolerance) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] [P3] Do not snap the beginning of a floating range to its end

The tolerance is scaled by the absolute endpoint magnitude, so it can exceed the entire range span. For example, {start: 1e15, stop: 1e15+1, step: 0.25} generates exactly one candidate—the stop—even though the start and intermediate values are representable. This violates the documented inclusive-range behavior and can skip the best candidate; endpoint snapping should only apply when arithmetic has reached the expected final step, not merely whenever a value is within several endpoint ULPs.

for (const auto& item : search.items()) {
require(normalize(item.key()) == index.name,
path + ".search_parameter_space." + item.key() + " is unsupported");
index.search_params[index.name] = item.value();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] [P3] Reject duplicate case-normalized search namespaces

Each top-level search key is normalized and then assigned to the same index.search_params[index.name] slot. Thus {"HGRAPH": {...}, "hgraph": {...}} passes validation but the later entry silently overwrites the earlier one, causing the planned Cartesian product to omit requested candidates. Reject multiple keys that normalize to the index name, or merge them with explicit duplicate-field validation.

Copilot AI review requested due to automatic review settings July 31, 2026 15:07
@jac0626
jac0626 force-pushed the codex/autotune-v1-minimal branch from 5f1c4cf to f9d1e64 Compare July 31, 2026 15:07
@vsag-bot
vsag-bot self-requested a review July 31, 2026 15:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

}

void
known_keys(const JsonType& object,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] known_keys iterates by value, copying each string

The function signature is bool known_keys(const JsonType& json, std::unordered_set<std::string> known). The known parameter is passed by value, which is fine since the caller may want to keep the original set. However, inside the loop for (const auto& key : json.items()), each known.count(key.key()) call constructs a temporary std::string because std::unordered_set<std::string> does not support heterogeneous lookup by default.

Consider changing the parameter to const std::unordered_set<std::string>& known and having callers pass a copy explicitly when needed. This is a minor performance note for the validation path.

}

auto dataset = std::make_shared<EvalDataset>();
dataset->base_dataset_ = base;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] EvalDataset::FromSearchDatasets leaves number_of_base_ as 0 but GetNumberOfBase() is called by SearchEvalCase

FromSearchDatasets sets train_shape_ = {0, dataset->dim_} and number_of_base_ is default-initialized to 0. This is semantically correct for a search-only dataset, but SearchEvalCase::RunInMemory() calls this->dataset_ptr_->GetNumberOfBase() indirectly through the recall monitor. If use_id_based_recall is false, RecallMonitor will try to access base vectors via GetOneTrainById(), which returns nullptr when train_ is null — this is handled gracefully. However, if someone adds a code path that assumes GetNumberOfBase() > 0 for a search-only dataset, it would fail silently.

Consider documenting that GetNumberOfBase() returns 0 for search-only datasets, or adding a HasBase() predicate to make the contract explicit.

serialize_index(index, index_path);
shared_metrics = build_metrics(raw, index_path);
if (request.include_raw_eval) {
build["raw_eval_result"] = std::move(raw);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] EvaluateCandidates for IndexTuningRequest does not validate search_param before passing to EvaluateSearch

In autotune_evaluation.cpp, when evaluating an IndexTuningRequest, the code constructs eval_config.search_param from trial["search_params"].dump(). If the candidate generation produces a trial with empty search_params (e.g., due to a bug in fill_* functions), EvaluateSearch will receive an empty string as the search parameter, which may cause confusing errors deep in the index implementation rather than a clear validation failure.

Consider adding a check that search_param is non-empty before calling EvaluateSearch, or validating it at candidate generation time.

Comment thread tools/eval/evaluator.cpp
JsonType
EvaluateBuild(const IndexPtr& index, const EvalDatasetPtr& dataset, const EvalConfig& config) {
validate(index, dataset);
BuildEvalCase eval_case("", "", index, config, dataset);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] EvaluateBuild in evaluator.cpp does not set num_threads_building from the config

In evaluator.cpp, EvaluateBuild() creates a BuildEvalCase and calls RunInMemory(). The EvalConfig passed in has num_threads_building defaulting to 1, but the caller in autotune_evaluation.cpp's build_config() function does not set this field. The do_build() method in BuildEvalCase does not call omp_set_num_threads() before building, unlike do_knn_search() which does. This means the build always uses the ambient OpenMP thread count rather than a controlled value.

If the intent is to use the ambient thread count (set by ScopedOpenMpThreads), consider documenting this. If the intent is to control build parallelism, consider plumbing num_threads_building through build_config().

Comment thread tools/autotune/main.cpp
std::ifstream input(argv[1]);
if (!input.good()) {
throw std::runtime_error("failed to open request file: " + std::string(argv[1]));
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] main.cpp disables the VSAG logger globally but never restores it

In main.cpp, vsag::Options::Instance().logger()->SetLevel(vsag::Logger::kOFF) disables all VSAG logging for the duration of the process. This is fine for a CLI tool that exits immediately, but if the AutoTune library is ever embedded in a longer-running process (e.g., through the C++ API), this side effect persists after RunAutoTune() returns. The C++ API entry points (TuneIndex, TuneSearch) do not disable the logger, which is correct — but the inconsistency between CLI and API behavior could be surprising.

Consider using a RAII scoped logger-level guard that restores the original level on return, or documenting that the CLI disables all VSAG logging globally.

}
if (emitted && current == previous) {
throw std::invalid_argument("$range step is too small to advance");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] autotune_candidate.cpp expand_range for floating-point uses step default of 1.0 which may produce too many candidates

In autotune_candidate.cpp, the floating-point expand_range function defaults step to 1.0 when not specified. For a range like {"$range": {"start": 1, "stop": 1000}} on a float parameter (e.g., alpha), this would generate 1000 candidates. The max_trials check catches this before evaluation, but the candidate generation itself allocates a large vector.

Consider using a smarter default step for floating-point ranges (e.g., based on the magnitude of stop - start), or validating the step against the range span at parse time.

@LHT129 LHT129 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This is a well-structured PR that adds AutoTune V1 orchestration with both CLI and C++ API. The architecture cleanly separates concerns: request parsing, candidate generation, evaluation, and result selection. The test coverage is thorough, covering validation edge cases, error paths, and the full tuning pipeline.

What I reviewed

  • All core implementation files: autotune.cpp, autotune_candidate.cpp, autotune_evaluation.cpp, main.cpp, autotune_test.cpp
  • New in-memory evaluation entry points: evaluator.cpp, eval_dataset.cpp, search_eval_case.cpp, build_eval_case.cpp
  • Build system changes: CMakeLists.txt, CI workflow
  • Public API headers: autotune.h, evaluator.h

New findings (6 inline comments)

  1. known_keys parameter passing — minor perf note about string copies in validation
  2. FromSearchDatasets base countGetNumberOfBase() returns 0 for search-only datasets, which is correct but implicit
  3. EvaluateCandidates search_param validation — empty search params could cause confusing errors downstream
  4. EvaluateBuild thread countnum_threads_building not plumbed through build_config()
  5. main.cpp logger side effect — global logger disable persists after RunAutoTune() returns
  6. expand_range float step default — default step of 1.0 could generate excessive candidates for wide ranges

Existing findings (already addressed or under discussion)

The earlier review round by vsag-bot and LHT129 covered: TPS monitoring in BuildEvalCase (fixed), ScopedOpenMpThreads destructor (confirmed correct per OpenMP spec), SearchFailure deadlock (confirmed no issue), aliasing shared_ptr lifetime, same_path edge cases, integer overflow in expand_range (confirmed not present), global mutex serialization, ef_search binary search efficiency, and exception type consistency.

Overall assessment

The PR is in good shape. The new findings are all suggestions (not critical), focused on code clarity and defensive validation. The core logic is sound and the test coverage gives confidence in correctness.

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated inline review completed.

Review effort: high (6838 changed lines across 43 files).
No actionable inline findings were found.

Reviewed commit f9d1e64.

@jac0626
jac0626 force-pushed the codex/autotune-v1-minimal branch from f9d1e64 to 4254cfc Compare July 31, 2026 16:55
@vsag-bot
vsag-bot self-requested a review July 31, 2026 16:56

@LHT129 LHT129 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: additional findings

Evaluation evaluation;
std::map<std::string, std::vector<uint64_t>> groups;
for (uint64_t i = 0; i < candidates.size(); ++i) {
const auto key = candidates[i].index_name + "\n" + candidates[i].create_params.dump();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] Build deduplication key depends on JSON key ordering

EvaluateCandidates(IndexTuningRequest) groups candidates by index_name + "\n" + create_params.dump(). nlohmann::json::dump() does not guarantee stable key ordering — two JsonType objects with the same logical content but keys inserted in different orders will produce different strings, causing unnecessary duplicate builds.

Consider using a canonical form for the key, e.g. sorting keys before serialization or computing a hash over the sorted key-value pairs.

}

void
check_file(const std::string& path, const std::string& field) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] check_file has a TOCTOU race between validation and use

check_file verifies the file is a readable regular file, but the actual consumer (EvalDataset::Load or std::ifstream for index deserialization) opens it later. On shared filesystems or under concurrent writers, the file could be truncated or replaced between the check and the open, leading to a hard-to-diagnose failure.

Since this is a CLI tool and the race window is small, this is low-risk. Consider opening the file once and passing the stream, or documenting that the caller is responsible for filesystem stability.

}

Evaluation
EvaluateCandidates(const SearchTuningRequest& tuning_request,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] EvaluateCandidates(SearchTuningRequest) does not validate that the index is built

The function calls eval::EvaluateSearch(tuning_request.index, ...) without checking whether the index has been built. If a caller passes an unbuilt index, the failure surfaces as a generic search_evaluation_failed error deep in the eval layer rather than a clear early validation error.

Consider adding a pre-condition check (e.g. GetNumElements() > 0) and failing with a descriptive message before entering the trial loop.

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated inline review completed.

Review effort: high (6838 changed lines across 43 files).
Submitted 1 inline comment.

Reviewed commit 4254cfc.

Comment thread tools/eval/evaluator.cpp
if (config.num_threads_searching <= 0) {
throw std::invalid_argument("evaluation search thread count must be positive");
}
if (config.search_query_count == 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Bound the query count to the signed search loop range

search_query_count is a uint64_t, but SearchEvalCase::do_knn_search() iterates over the resulting bound with an int64_t id. A direct EvaluateSearch caller can therefore pass a value above INT64_MAX; with latency disabled and recall or memory monitoring enabled, the counter eventually overflows, causing undefined behavior or nontermination. Reject counts above INT64_MAX here, or use a loop/index type capable of representing every accepted count.

Signed-off-by: jc543239 <jc543239@antgroup.com>
Assisted-by: Codex:gpt-5
@jac0626
jac0626 force-pushed the codex/autotune-v1-minimal branch from 4254cfc to dc59b20 Compare July 31, 2026 19:13
@vsag-bot
vsag-bot self-requested a review July 31, 2026 19:13

@vsag-bot vsag-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated inline review completed.

Review effort: high (6838 changed lines across 43 files).
Submitted 5 inline comments.

Reviewed commit dc59b20.


if (range["start"].is_number_integer() && range["stop"].is_number_integer() &&
range["step"].is_number_integer()) {
const auto start = range["start"].get<int64_t>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Reject unsigned range values before converting to int64_t

is_number_integer() also accepts nlohmann unsigned numbers, so a bound such as 9223372036854775808 reaches get<int64_t>() and becomes an out-of-range signed value. The resulting candidate space can silently contain a negative parameter instead of the requested value. Check unsigned start/stop/step against INT64_MAX before conversion or represent the range as unsigned.

(step < 0.0 && current < stop && stop - current > tolerance)) {
return;
}
if (std::abs(current - stop) <= tolerance) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Do not snap the first floating candidate to the endpoint

The tolerance scales with the absolute endpoint magnitude and can exceed the entire range span. For example, start=1e15, stop=1e15+1, and step=0.25 immediately replaces the representable start with the stop, omitting all earlier candidates. Endpoint snapping should only apply after arithmetic reaches the expected final step.

for (const auto& item : search.items()) {
require(normalize(item.key()) == index.name,
path + ".search_parameter_space." + item.key() + " is unsupported");
index.search_params[index.name] = item.value();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Reject duplicate case-normalized search namespaces

A parameter object containing both HGRAPH and hgraph passes the normalization check, but each iteration assigns the same index.search_params[index.name] field, so one requested space silently overwrites the other. Reject a second normalized namespace instead of dropping candidates.

Comment thread tools/eval/evaluator.cpp
if (config.num_threads_searching <= 0) {
throw std::invalid_argument("evaluation search thread count must be positive");
}
if (config.search_query_count == 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Bound query counts to the signed OpenMP loop range

search_query_count is unsigned, but SearchEvalCase::do_knn_search() iterates to that bound with an int64_t counter. With latency disabled and recall or memory monitoring enabled, a value above INT64_MAX is accepted and eventually causes signed overflow or nontermination. Reject such counts here or use an unsigned-capable iteration scheme.

Comment thread tools/eval/evaluator.cpp
validate_search(dataset, config);
ScopedOpenMpThreads openmp_threads;
SearchEvalCase eval_case("", "", index, config, dataset);
return eval_case.RunInMemory();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Execute a search when all monitors are disabled

If recall, latency/QPS, percentile, and memory monitoring are all disabled, SearchEvalCase has no monitors and also skips its fallback pass. EvaluateSearch then reports success without invoking KnnSearch, so invalid parameters such as ef_search=0 are silently accepted. Require a monitor or perform an unconditional validation/statistics search pass.

}

const auto buckets = create_params["index_param"]["buckets_count"].get<uint64_t>();
if (buckets <= 16) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] fill_ivf_search calls get<uint64_t>() on buckets_count which may be stored as int64_t by nlohmann::json, causing a type_error at runtime.

nlohmann::json stores small integers (like buckets_count) as int64_t by default. Calling get<uint64_t>() on an int64_t-stored value will throw nlohmann::json::type_error because the library does not perform implicit sign conversion between integer types.

// tools/autotune/autotune_candidate.cpp:245
const auto buckets = create_params["index_param"]["buckets_count"].get<uint64_t>();

Fix: Use get<int64_t>() and cast to uint64_t if needed, or use .get<uint64_t>() only after confirming the value is stored as unsigned. Since buckets_count is a count value that should always be non-negative, using get<int64_t>() with an assertion or get<uint64_t>() with a try/catch fallback would be safer.

params["scan_buckets_count"] = std::set<uint64_t>{1, 4, 16, 64};
return;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] fill_ivf_search calls get<uint64_t>() on buckets_count which may be stored as int64_t by nlohmann::json

In autotune_candidate.cpp:243, create_params["index_param"]["buckets_count"].get<uint64_t>() will throw nlohmann::json::type_error if the value was parsed from a JSON number without a decimal point and fits in int64_t (nlohmann::json's default storage for small integers). The positive_int64() helper at line 57 correctly handles both uint64_t and int64_t JSON types. Consider using positive_int64() or a similar dual-type accessor here to avoid a runtime crash when buckets_count is a small integer like 16.

// Suggested fix:
const auto buckets = static_cast<uint64_t>(positive_int64(create_params["index_param"], "buckets_count"));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Brand-new functionality or capabilities 引入全新的功能、新特性或新能力 module/docs module/example module/testing module/tools size/XXL version/1.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat](other): add constraint-driven AutoTune V1

4 participants