11/*
2- * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
2+ * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved .
33 * SPDX-License-Identifier: Apache-2.0
44 */
55#pragma once
@@ -262,6 +262,31 @@ class AnnHnswAceTest : public ::testing::TestWithParam<AnnHnswAceInputs> {
262262 std::filesystem::remove_all (temp_dir);
263263 }
264264
265+ void testHnswAceRejectsTooManyPartitions ()
266+ {
267+ auto database_host = raft::make_host_matrix<DataT, int64_t >(ps.n_rows , ps.dim );
268+ raft::copy (database_host.data_handle (), database_dev.data (), ps.n_rows * ps.dim , stream_);
269+ raft::resource::sync_stream (handle_);
270+
271+ hnsw::index_params hnsw_params;
272+ hnsw_params.metric = ps.metric ;
273+ hnsw_params.hierarchy = hnsw::HnswHierarchy::GPU ;
274+ hnsw_params.M = 32 ;
275+ hnsw_params.ef_construction = ps.ef_construction ;
276+
277+ auto ace_params = graph_build_params::ace_params ();
278+ ace_params.npartitions = static_cast <size_t >(ps.n_rows ) + 1 ;
279+ hnsw_params.graph_build_params = ace_params;
280+
281+ try {
282+ [[maybe_unused]] auto hnsw_index =
283+ hnsw::build (handle_, hnsw_params, raft::make_const_mdspan (database_host.view ()));
284+ FAIL () << " ACE accepted more partitions than dataset rows" ;
285+ } catch (const std::exception& e) {
286+ EXPECT_NE (std::string (e.what ()).find (" cannot exceed dataset size" ), std::string::npos);
287+ }
288+ }
289+
265290 // Verify the in-memory CAGRA -> HNSW conversion spills to disk when the resulting HNSW
266291 // index would not fit in (an artificially constrained) host memory. This exercises
267292 // serialize_to_hnswlib_from_inmem and the batched serializer core, covering:
@@ -472,7 +497,7 @@ inline std::vector<AnnHnswAceInputs> generate_hnsw_ace_inputs()
472497 {5000 }, // n_rows
473498 {64 , 128 }, // dim
474499 {10 }, // k
475- {2 , 4 }, // npartitions
500+ {0 , 1 , 2 , 4 }, // npartitions (auto, adjusted, and explicit)
476501 {100 }, // ef_construction
477502 {false , true }, // use_disk (test both modes)
478503 {cuvs::distance::DistanceType::L2Expanded,
@@ -502,6 +527,19 @@ inline std::vector<AnnHnswAceInputs> generate_hnsw_ace_memory_fallback_inputs()
502527 };
503528}
504529
530+ inline std::vector<AnnHnswAceInputs> generate_hnsw_ace_invalid_partition_inputs ()
531+ {
532+ return {{10 ,
533+ 5000 ,
534+ 64 ,
535+ 10 ,
536+ 0 , // Set to n_rows + 1 by testHnswAceRejectsTooManyPartitions.
537+ 100 ,
538+ false ,
539+ cuvs::distance::DistanceType::L2Expanded,
540+ 0.0 }};
541+ }
542+
505543// Inputs for testing the in-memory CAGRA -> HNSW disk-spill conversion path.
506544inline std::vector<AnnHnswAceInputs> generate_hnsw_inmem_spill_inputs ()
507545{
@@ -524,6 +562,8 @@ inline std::vector<AnnHnswAceInputs> generate_hnsw_inmem_spill_inputs()
524562const std::vector<AnnHnswAceInputs> hnsw_ace_inputs = generate_hnsw_ace_inputs();
525563const std::vector<AnnHnswAceInputs> hnsw_ace_memory_fallback_inputs =
526564 generate_hnsw_ace_memory_fallback_inputs ();
565+ const std::vector<AnnHnswAceInputs> hnsw_ace_invalid_partition_inputs =
566+ generate_hnsw_ace_invalid_partition_inputs ();
527567const std::vector<AnnHnswAceInputs> hnsw_inmem_spill_inputs = generate_hnsw_inmem_spill_inputs();
528568
529569} // namespace cuvs::neighbors::hnsw
0 commit comments