1818#include < raft/core/host_mdspan.hpp>
1919#include < raft/core/logger.hpp>
2020#include < raft/core/resource/cuda_stream.hpp>
21+ #include < raft/util/cuda_rt_essentials.hpp>
2122#include < raft/util/integer_utils.hpp>
2223
2324#include < cuvs/cluster/kmeans.hpp>
@@ -429,35 +430,84 @@ void ace_adjust_sub_graph_ids(
429430 }
430431}
431432
432- // ACE: Adjust ids in sub search graph in place for disk version
433- template <typename IdxT>
433+ // ACE: Adjust IDs into global reordered IDs on the device for the disk version.
434+ template <typename GraphIdxT, typename IdxT>
435+ __global__ void ace_adjust_sub_graph_ids_disk_kernel (const GraphIdxT* sub_search_graph,
436+ IdxT* adjusted_search_graph,
437+ size_t graph_edges,
438+ size_t core_sub_dataset_size,
439+ IdxT core_partition_offset,
440+ const IdxT* augmented_reordered_ids)
441+ {
442+ const size_t edge_id = static_cast <size_t >(blockIdx .x ) * blockDim .x + threadIdx .x ;
443+ if (edge_id >= graph_edges) { return ; }
444+
445+ const GraphIdxT neighbor = sub_search_graph[edge_id];
446+ if (static_cast <size_t >(neighbor) < core_sub_dataset_size) {
447+ adjusted_search_graph[edge_id] = static_cast <IdxT>(neighbor) + core_partition_offset;
448+ } else {
449+ adjusted_search_graph[edge_id] =
450+ augmented_reordered_ids[static_cast <size_t >(neighbor) - core_sub_dataset_size];
451+ }
452+ }
453+
454+ template <typename IdxT, typename GraphIdxT>
434455void ace_adjust_sub_graph_ids_disk (
456+ raft::resources const & res,
435457 size_t core_sub_dataset_size,
436458 size_t augmented_sub_dataset_size,
437459 size_t graph_degree,
438460 size_t partition_id,
439- raft::host_matrix_view<IdxT, int64_t , raft::row_major> sub_search_graph,
461+ raft::device_matrix_view<const GraphIdxT, int64_t , raft::row_major> sub_search_graph,
462+ raft::device_matrix_view<IdxT, int64_t , raft::row_major> adjusted_search_graph,
440463 raft::host_vector_view<IdxT, int64_t , raft::row_major> core_partition_offsets,
441464 raft::host_vector_view<IdxT, int64_t , raft::row_major> augmented_partition_offsets,
442465 raft::host_vector_view<IdxT, int64_t , raft::row_major> augmented_backward_mapping,
443466 raft::host_vector_view<IdxT, int64_t , raft::row_major> core_forward_mapping)
444467{
468+ RAFT_EXPECTS (static_cast <size_t >(sub_search_graph.extent (0 )) >= core_sub_dataset_size,
469+ " ACE: source graph has fewer rows than the core partition" );
470+ RAFT_EXPECTS (static_cast <size_t >(sub_search_graph.extent (1 )) == graph_degree,
471+ " ACE: source graph degree does not match the requested graph degree" );
472+ RAFT_EXPECTS (static_cast <size_t >(adjusted_search_graph.extent (0 )) == core_sub_dataset_size &&
473+ static_cast <size_t >(adjusted_search_graph.extent (1 )) == graph_degree,
474+ " ACE: adjusted graph shape does not match the core partition" );
475+
476+ const size_t augmented_offset = augmented_partition_offsets (partition_id);
477+ RAFT_EXPECTS (augmented_offset + augmented_sub_dataset_size <=
478+ static_cast <size_t >(augmented_backward_mapping.extent (0 )),
479+ " ACE: augmented partition exceeds the backward mapping" );
480+
481+ // Resolve the global mapping once per augmented vector. This compact table is small enough to
482+ // copy to the GPU even when the full dataset mappings are too large to fit in device memory.
483+ auto augmented_reordered_ids = raft::make_host_vector<IdxT, int64_t >(augmented_sub_dataset_size);
445484#pragma omp parallel for
446- for (size_t i = 0 ; i < core_sub_dataset_size; i++) {
447- for (size_t k = 0 ; k < graph_degree; k++) {
448- size_t j = sub_search_graph (i, k);
449- if (j < core_sub_dataset_size) {
450- // core partition neighbor: local → core reordered
451- sub_search_graph (i, k) = j + core_partition_offsets (partition_id);
452- } else {
453- // Augmented partition neighbor: local → augmented reordered→ original → core reordered
454- size_t j_augmented = j - core_sub_dataset_size;
455- size_t j_original =
456- augmented_backward_mapping (j_augmented + augmented_partition_offsets (partition_id));
457- sub_search_graph (i, k) = core_forward_mapping (j_original);
458- }
459- }
485+ for (size_t i = 0 ; i < augmented_sub_dataset_size; i++) {
486+ const size_t original_id = augmented_backward_mapping (augmented_offset + i);
487+ augmented_reordered_ids (i) = core_forward_mapping (original_id);
460488 }
489+
490+ auto augmented_reordered_ids_dev =
491+ raft::make_device_vector<IdxT, int64_t >(res, augmented_sub_dataset_size);
492+ raft::copy (res, augmented_reordered_ids_dev.view (), augmented_reordered_ids.view ());
493+
494+ const size_t graph_edges = core_sub_dataset_size * graph_degree;
495+ if (graph_edges == 0 ) { return ; }
496+
497+ constexpr uint32_t block_size = 256 ;
498+ const dim3 grid_size (raft::ceildiv (graph_edges, static_cast <size_t >(block_size)));
499+ ace_adjust_sub_graph_ids_disk_kernel<<<grid_size,
500+ block_size,
501+ 0 ,
502+ raft::resource::get_cuda_stream (res)>>>(
503+ sub_search_graph.data_handle(),
504+ adjusted_search_graph.data_handle(),
505+ graph_edges,
506+ core_sub_dataset_size,
507+ core_partition_offsets(partition_id),
508+ augmented_reordered_ids_dev.data_handle());
509+ RAFT_CUDA_TRY (cudaPeekAtLastError());
510+ raft::resource::sync_stream (res);
461511}
462512
463513// ACE: Reorder dataset based on partition assignments and store to disk
@@ -1440,28 +1490,44 @@ index<T, IdxT> build_ace(raft::resources const& res,
14401490 auto optimize_elapsed =
14411491 std::chrono::duration_cast<std::chrono::milliseconds>(optimize_end - read_end).count ();
14421492
1443- // Copy graph edges for core members of this partition
1444- auto sub_search_graph =
1445- raft::make_host_matrix<IdxT, int64_t >(core_sub_dataset_size, graph_degree);
1446- cudaStream_t stream = raft::resource::get_cuda_stream (res);
1447- raft::copy (
1448- res,
1449- raft::make_host_vector_view (sub_search_graph.data_handle (), sub_search_graph.size ()),
1450- raft::make_device_vector_view (sub_index.graph ().data_handle (), sub_search_graph.size ()));
1451- raft::resource::sync_stream (res, stream);
1452-
1493+ auto adjust_end = optimize_end;
1494+ auto write_elapsed = 0L ;
1495+ const size_t graph_bytes = core_sub_dataset_size * graph_degree * sizeof (IdxT);
14531496 if (use_disk_mode) {
1454- // Adjust IDs in sub_search_graph in place for disk storage
1455- ace_adjust_sub_graph_ids_disk<IdxT>(core_sub_dataset_size,
1497+ // Keep the graph on the GPU, adjust IDs there, and write it directly through GDS.
1498+ auto adjusted_search_graph =
1499+ raft::make_device_matrix<IdxT, int64_t >(res, core_sub_dataset_size, graph_degree);
1500+ ace_adjust_sub_graph_ids_disk<IdxT>(res,
1501+ core_sub_dataset_size,
14561502 augmented_sub_dataset_size,
14571503 graph_degree,
14581504 partition_id,
1459- sub_search_graph.view (),
1505+ sub_index.graph (),
1506+ adjusted_search_graph.view (),
14601507 core_partition_offsets.view (),
14611508 augmented_partition_offsets.view (),
14621509 augmented_backward_mapping.view (),
14631510 core_forward_mapping.view ());
1511+ adjust_end = std::chrono::high_resolution_clock::now ();
1512+
1513+ const size_t graph_offset =
1514+ static_cast <size_t >(core_partition_offsets (partition_id)) * graph_degree * sizeof (IdxT) +
1515+ graph_header_size;
1516+ cuvs::util::write_large_file (
1517+ graph_fd, adjusted_search_graph.data_handle (), graph_bytes, graph_offset);
1518+ auto write_end = std::chrono::high_resolution_clock::now ();
1519+ write_elapsed =
1520+ std::chrono::duration_cast<std::chrono::milliseconds>(write_end - adjust_end).count ();
14641521 } else {
1522+ // The in-memory result is host-resident, so copy the core graph back before remapping IDs.
1523+ auto sub_search_graph =
1524+ raft::make_host_matrix<IdxT, int64_t >(core_sub_dataset_size, graph_degree);
1525+ raft::copy (
1526+ res,
1527+ raft::make_host_vector_view (sub_search_graph.data_handle (), sub_search_graph.size ()),
1528+ raft::make_device_vector_view (sub_index.graph ().data_handle (), sub_search_graph.size ()));
1529+ raft::resource::sync_stream (res);
1530+
14651531 // Adjust IDs in sub_search_graph and save to search_graph
14661532 ace_adjust_sub_graph_ids<IdxT>(core_sub_dataset_size,
14671533 augmented_sub_dataset_size,
@@ -1473,33 +1539,20 @@ index<T, IdxT> build_ace(raft::resources const& res,
14731539 augmented_partition_offsets.view (),
14741540 core_backward_mapping.view (),
14751541 augmented_backward_mapping.view ());
1542+ adjust_end = std::chrono::high_resolution_clock::now ();
14761543 }
14771544
1478- auto adjust_end = std::chrono::high_resolution_clock::now ();
14791545 auto adjust_elapsed =
14801546 std::chrono::duration_cast<std::chrono::milliseconds>(adjust_end - optimize_end).count ();
14811547
1482- if (use_disk_mode) {
1483- const size_t graph_offset =
1484- static_cast <size_t >(core_partition_offsets (partition_id)) * graph_degree * sizeof (IdxT) +
1485- graph_header_size;
1486- const size_t graph_bytes = core_sub_dataset_size * graph_degree * sizeof (IdxT);
1487- cuvs::util::write_large_file (
1488- graph_fd, sub_search_graph.data_handle (), graph_bytes, graph_offset);
1489- }
1490-
1491- auto end = std::chrono::high_resolution_clock::now ();
1492- auto write_elapsed =
1493- std::chrono::duration_cast<std::chrono::milliseconds>(end - adjust_end).count ();
1548+ auto end = std::chrono::high_resolution_clock::now ();
14941549 auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count ();
14951550 double read_throughput =
14961551 read_elapsed > 0
14971552 ? to_mib (sub_dataset_size * dataset_dim * sizeof (T)) / (read_elapsed / 1000.0 )
14981553 : 0.0 ;
14991554 double write_throughput =
1500- write_elapsed > 0
1501- ? to_mib (core_sub_dataset_size * dataset_dim * sizeof (T)) / (write_elapsed / 1000.0 )
1502- : 0.0 ;
1555+ write_elapsed > 0 ? to_mib (graph_bytes) / (write_elapsed / 1000.0 ) : 0.0 ;
15031556 RAFT_LOG_INFO (
15041557 " ACE: Partition %4lu (%8lu + %8lu) completed in %6ld ms: read %6ld ms (%7.1f MiB/s), "
15051558 " optimize %6ld ms, adjust %6ld ms, write %6ld ms (%7.1f MiB/s)" ,
0 commit comments