Skip to content

[FEA] Add missing pytests for cuvs-bench algorithms#1460

Open
tarang-jain wants to merge 50 commits into
NVIDIA:mainfrom
tarang-jain:cuvs-bench-pytests
Open

[FEA] Add missing pytests for cuvs-bench algorithms#1460
tarang-jain wants to merge 50 commits into
NVIDIA:mainfrom
tarang-jain:cuvs-bench-pytests

Conversation

@tarang-jain

Copy link
Copy Markdown
Contributor

No description provided.

@copy-pr-bot

copy-pr-bot Bot commented Oct 24, 2025

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@tarang-jain tarang-jain self-assigned this Oct 24, 2025
@tarang-jain tarang-jain added feature request New feature or request non-breaking Introduces a non-breaking change labels Oct 24, 2025
@tarang-jain tarang-jain marked this pull request as ready for review January 27, 2026 11:48
@tarang-jain tarang-jain requested a review from a team as a code owner January 27, 2026 11:48
@cjnolet cjnolet moved this from Todo to In Progress in Unstructured Data Processing Jan 29, 2026
@tarang-jain tarang-jain requested a review from a team as a code owner March 25, 2026 18:11

@bkarsin bkarsin 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.

Looks good to me, just the 1 note about using raft::copy vs. cudaMemcpy.

Comment thread cpp/src/neighbors/detail/vamana/vamana_serialize.cuh Outdated
@cjnolet

cjnolet commented Mar 27, 2026 via email

Copy link
Copy Markdown
Contributor

@cjnolet

cjnolet commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Please make a wrapper for this.

@tfeher tfeher changed the base branch from release/26.04 to main April 1, 2026 16:58
@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 027b611c-674f-47b6-8687-da2dcc50b0e1

📥 Commits

Reviewing files that changed from the base of the PR and between e239ea1 and a17986e.

📒 Files selected for processing (2)
  • cpp/bench/ann/src/cuvs/cuvs_vamana_wrapper.h
  • cpp/bench/ann/src/diskann/diskann_wrapper.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • cpp/bench/ann/src/cuvs/cuvs_vamana_wrapper.h
  • cpp/bench/ann/src/diskann/diskann_wrapper.h

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added lightweight "test" benchmark configurations for faster runs.
    • Improved validation and logging for index artifacts, builds, loads, saves, and first-call searches.
    • Enhanced benchmark error detection to surface row-level failures early.
  • Bug Fixes

    • Fixed incorrect assignment of a build parameter (alpha) during parsing.
    • Adjusted index-loading behavior for more robust loads; save now fails gracefully instead of crashing.
  • Tests / Documentation

    • Detect ARM at runtime to skip DiskANN/Vamana tests.
    • Export: skip missing-result exports and log/report missing CSV/JSON columns.

Walkthrough

Fixes DiskANN build-parameter parsing and load arg; adds artifact checks, structured logging, and exception-safety in C++ wrappers and harness; adds narrow test configs; improves CSV export validation and gbench error surfacing; and makes CLI tests conditional on ARM CPUs.

Changes

DiskANN Benchmark Configuration and Testing

Layer / File(s) Summary
DiskANN C++ parameter fixes & wrapper safety
cpp/bench/ann/src/diskann/diskann_benchmark.cpp, cpp/bench/ann/src/diskann/diskann_wrapper.h
parse_build_param now assigns JSON "alpha" to param.alpha; diskann wrapper adds artifact-existence/size helpers, persisted build/search fields, structured logging, try/catch around build/save/load, first-call search logging, and changes load third-arg to 100.
Benchmark harness logging & exception safety
cpp/bench/ann/src/common/benchmark.hpp
Adds thread-0 start/finish logs, wraps algo create/load/build/save/set_param/copy/search with exception handlers that log and skip via state.SkipWithError, and adds recall start/finish logs.
Vamana artifact validation & safe save/load
cpp/bench/ann/src/cuvs/cuvs_vamana_wrapper.h
Adds filesystem helpers, logs artifact sizes/existence, validates built index and .data sidecar, throws on invalid artifacts, delegates load to diskann wrapper with try/catch, and logs first-call search.
Test configuration sections
python/cuvs_bench/cuvs_bench/config/algos/*.yaml
Adds test subsections with narrow single-value parameter grids for faster test runs in multiple algorithm configs.
ARM-aware CLI tests
python/cuvs_bench/cuvs_bench/tests/test_cli.py
Adds is_arm_cpu() and conditions the simulated --algorithms lists and expected output assertions to exclude cuvs_vamana/diskann_memory on ARM machines.
Google Benchmark row-error detection & verbose CLI logs
python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py
Adds _gbench_error_message and _index_debug_summary, prints verbose launch and subprocess diagnostics, and raises RuntimeError when gbench rows indicate error_occurred.
Data export validation & num_threads handling
python/cuvs_bench/cuvs_bench/run/data_export.py
Adds log_missing_export_columns, early-exits when result/<method> is missing, validates required columns for build/search exports, updates SPDX year, and maps num_threads into build_num_threads only.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

improvement, benchmarking

Suggested reviewers

  • cjnolet
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to verify that the changeset intent is clearly communicated. Add a detailed pull request description explaining the rationale for all changes including test additions, logging enhancements, YAML configurations, and the alpha parameter bug fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title mentions adding pytests for cuvs-bench algorithms, but the changeset includes extensive logging/debugging additions, YAML test configurations, bug fixes, and build parameter corrections alongside test modifications. Consider a more specific title that reflects the full scope: the title should better capture the combined changes (tests, logging, configs, bug fixes) or clarify whether testing is the primary change.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py`:
- Around line 276-280: The code currently calls self._gbench_error_message(...)
after persisting/merging benchmark outputs, which allows failed rows to be
saved; move the validation step to before any file write/merge operations so you
raise RuntimeError and abort persistence when error_message is truthy.
Specifically, call self._gbench_error_message(benchmarks, "Build",
benchmark_out) (and the analogous call used for the later stage) immediately
after obtaining benchmark_out and before invoking the merge/write routines (the
functions that persist results), and raise RuntimeError(error_message) to stop
further processing; apply the same reorder/fix for the second occurrence around
the later result-writing block.

In `@python/cuvs_bench/cuvs_bench/tests/test_cli.py`:
- Around line 15-17: The is_arm_cpu() helper currently only checks for
("aarch64", "arm64") and misses common ARM variants like "armv7l", causing false
negatives; update is_arm_cpu() to detect ARM more broadly by checking
platform.machine() (in the is_arm_cpu function) for known values and patterns —
e.g., include "armv7l", "armv6l", and treat any value that starts with "arm"
(case-insensitive) or contains "aarch" as ARM — so replace the current
membership test in is_arm_cpu() with a broader pattern check to reliably detect
ARM architectures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: dc05fe10-338a-47c7-9bbd-dd05f211d6c2

📥 Commits

Reviewing files that changed from the base of the PR and between cda3731 and fb443c1.

📒 Files selected for processing (4)
  • cpp/bench/ann/src/common/benchmark.hpp
  • cpp/bench/ann/src/cuvs/cuvs_vamana_wrapper.h
  • python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py
  • python/cuvs_bench/cuvs_bench/tests/test_cli.py

Comment thread python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py
Comment on lines +15 to +17
def is_arm_cpu():
"""Check if running on ARM Linux architecture."""
return platform.machine() in ("aarch64", "arm64")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Broaden ARM architecture detection to avoid false negatives.

The helper currently misses common ARM variants like armv7l, which can incorrectly run DiskANN/Vamana tests on unsupported ARM runners.

Proposed fix
 def is_arm_cpu():
     """Check if running on ARM Linux architecture."""
-    return platform.machine() in ("aarch64", "arm64")
+    machine = platform.machine().lower()
+    return machine in ("aarch64", "arm64") or machine.startswith("arm")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuvs_bench/cuvs_bench/tests/test_cli.py` around lines 15 - 17, The
is_arm_cpu() helper currently only checks for ("aarch64", "arm64") and misses
common ARM variants like "armv7l", causing false negatives; update is_arm_cpu()
to detect ARM more broadly by checking platform.machine() (in the is_arm_cpu
function) for known values and patterns — e.g., include "armv7l", "armv6l", and
treat any value that starts with "arm" (case-insensitive) or contains "aarch" as
ARM — so replace the current membership test in is_arm_cpu() with a broader
pattern check to reliably detect ARM architectures.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/bench/ann/src/common/benchmark.hpp (1)

321-369: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Error paths can deadlock worker threads at the load barrier.

Thread 0 can return after SkipWithError(...) before calling load_barrier.arrive(state.threads()), while other threads are blocked in load_barrier.wait(...).

💡 Proposed fix direction
   progress_barrier load_barrier{};
-  if (load_barrier.arrive(1) == 0) {
+  bool init_failed = false;
+  if (load_barrier.arrive(1) == 0) {
+    struct barrier_release {
+      progress_barrier& b;
+      int n;
+      ~barrier_release() { b.arrive(n); }
+    } release{load_barrier, state.threads()};
+
     ...
     } catch (const std::exception& e) {
       state.SkipWithError("Failed to create an algo: " + std::string(e.what()));
-      return;
+      init_failed = true;
     }
     ...
-    load_barrier.arrive(state.threads());
   } else {
     load_barrier.wait(state.threads() * 2);
   }
+  if (state.skipped()) { return; }

Also applies to: 378-397, 399-423, 425-426

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/bench/ann/src/common/benchmark.hpp` around lines 321 - 369, The
error-handling paths can return while other threads remain blocked on
load_barrier.wait, so ensure the barrier is always released on failure: in the
block that checks load_barrier.arrive(1) and in the similar blocks around
create_algo/create_search_param and state.SkipWithError, wrap the load/create
logic in a try/catch (or use RAII) and on any exception or early return call
load_barrier.arrive(state.threads()) (or otherwise unblock the waiting threads)
before calling state.SkipWithError and returning; apply the same change wherever
you call create_algo, create_search_param, current_algo loading, or call
state.SkipWithError so the barrier is not left waiting.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/bench/ann/src/common/benchmark.hpp`:
- Around line 197-200: The info logs inside the timed build loop (the log_info
call guarded by state.thread_index() == 0 in the bench_build loop around
index.algo/index.name) will skew benchmark results and should be removed from
the critical path; move or change these logs so they do not execute
per-iteration—either relocate the logging outside the timed loop (before it
starts or after it completes) or restrict it to a single non-timed event (e.g.,
only on the first iteration/thread), and apply the same fix to the similar
log_info block at the other occurrence (the block referenced at ~205-209).

In `@cpp/bench/ann/src/diskann/diskann_wrapper.h`:
- Around line 119-123: The code sets configured_build_pq_bytes_ from
param.build_pq_bytes but never copies it into build_pq_bytes_, so
initialize_index_ reads the still-zero build_pq_bytes_ and PQ is disabled;
update the initialization logic to assign configured_build_pq_bytes_ into
build_pq_bytes_ (and likewise ensure any similar pairs around the other
occurrences) before calling initialize_index_ so the configured value is used;
locate the members configured_build_pq_bytes_, build_pq_bytes_ and the function
initialize_index_ (and the other similar block around the second occurrence) and
add the assignment(s) that propagate the configured value to the active
build_pq_bytes_ field.

---

Outside diff comments:
In `@cpp/bench/ann/src/common/benchmark.hpp`:
- Around line 321-369: The error-handling paths can return while other threads
remain blocked on load_barrier.wait, so ensure the barrier is always released on
failure: in the block that checks load_barrier.arrive(1) and in the similar
blocks around create_algo/create_search_param and state.SkipWithError, wrap the
load/create logic in a try/catch (or use RAII) and on any exception or early
return call load_barrier.arrive(state.threads()) (or otherwise unblock the
waiting threads) before calling state.SkipWithError and returning; apply the
same change wherever you call create_algo, create_search_param, current_algo
loading, or call state.SkipWithError so the barrier is not left waiting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 13368e60-4e10-4549-887a-320f0e7794e5

📥 Commits

Reviewing files that changed from the base of the PR and between fb443c1 and e239ea1.

📒 Files selected for processing (5)
  • cpp/bench/ann/src/common/benchmark.hpp
  • cpp/bench/ann/src/cuvs/cuvs_vamana_wrapper.h
  • cpp/bench/ann/src/diskann/diskann_wrapper.h
  • python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py
  • python/cuvs_bench/cuvs_bench/run/data_export.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • cpp/bench/ann/src/cuvs/cuvs_vamana_wrapper.h
  • python/cuvs_bench/cuvs_bench/backends/cpp_gbench.py

Comment on lines +197 to +200
if (state.thread_index() == 0) {
log_info(
"bench_build iteration start: algo=%s index=%s", index.algo.c_str(), index.name.c_str());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Hot-loop info logging will skew benchmark measurements.

These log_info calls run inside the timed build loop and can materially inflate latency/throughput numbers (and flood logs). Keep iteration logging off the critical path.

💡 Proposed fix
-      if (state.thread_index() == 0) {
-        log_info(
-          "bench_build iteration start: algo=%s index=%s", index.algo.c_str(), index.name.c_str());
-      }
       [[maybe_unused]] auto ntx_lap = nvtx.lap();
       [[maybe_unused]] auto gpu_lap = gpu_timer.lap(!no_lap_sync);
       try {
         algo->build(base_set, index_size);
-        if (state.thread_index() == 0) {
-          log_info("bench_build iteration finish: algo=%s index=%s",
-                   index.algo.c_str(),
-                   index.name.c_str());
-        }
       } catch (const std::exception& e) {

Also applies to: 205-209

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/bench/ann/src/common/benchmark.hpp` around lines 197 - 200, The info logs
inside the timed build loop (the log_info call guarded by state.thread_index()
== 0 in the bench_build loop around index.algo/index.name) will skew benchmark
results and should be removed from the critical path; move or change these logs
so they do not execute per-iteration—either relocate the logging outside the
timed loop (before it starts or after it completes) or restrict it to a single
non-timed event (e.g., only on the first iteration/thread), and apply the same
fix to the similar log_info block at the other occurrence (the block referenced
at ~205-209).

Comment on lines +119 to 123
configured_build_pq_bytes_ = param.build_pq_bytes;
graph_degree_ = param.R;
L_build_ = param.L_build;
alpha_ = param.alpha;
num_threads_ = param.num_threads;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

build_pq_bytes is configured but never applied to index initialization.

configured_build_pq_bytes_ is set from param.build_pq_bytes, but initialize_index_ reads build_pq_bytes_, which remains 0. This silently disables PQ behavior even when configured.

💡 Proposed fix
   configured_build_pq_bytes_  = param.build_pq_bytes;
+  build_pq_bytes_             = param.build_pq_bytes;
   graph_degree_               = param.R;
   L_build_                    = param.L_build;
   alpha_                      = param.alpha;

Also applies to: 145-147

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/bench/ann/src/diskann/diskann_wrapper.h` around lines 119 - 123, The code
sets configured_build_pq_bytes_ from param.build_pq_bytes but never copies it
into build_pq_bytes_, so initialize_index_ reads the still-zero build_pq_bytes_
and PQ is disabled; update the initialization logic to assign
configured_build_pq_bytes_ into build_pq_bytes_ (and likewise ensure any similar
pairs around the other occurrences) before calling initialize_index_ so the
configured value is used; locate the members configured_build_pq_bytes_,
build_pq_bytes_ and the function initialize_index_ (and the other similar block
around the second occurrence) and add the assignment(s) that propagate the
configured value to the active build_pq_bytes_ field.

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

Labels

feature request New feature or request non-breaking Introduces a non-breaking change stale-active

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

5 participants