Skip to content

Optimize transfer/compute overlap in out-of-core KMeans - #2538

Open
viclafargue wants to merge 4 commits into
NVIDIA:mainfrom
viclafargue:ooc-kmeans-overlap
Open

Optimize transfer/compute overlap in out-of-core KMeans#2538
viclafargue wants to merge 4 commits into
NVIDIA:mainfrom
viclafargue:ooc-kmeans-overlap

Conversation

@viclafargue

@viclafargue viclafargue commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR improves data-transfer and compute overlap for host-resident out-of-core KMeans using a cyclic two-buffer pipeline.

  • Batch 1 follows batch 0 on the copy stream while batch 0 compute starts as soon as its transfer completes.
  • Subsequent transfers overlap computation of the current batch.
  • Buffer recycling continues across iteration boundaries, allowing batch 0 of the next pass to be prefetched during the previous pass.
  • Device-resident inputs remain zero-copy.

Implementation

  • Adds a private KMeans batch loader with explicit staged, acquired, and reusable buffer states.
  • Uses CUDA events so compute waits for H2D completion and a buffer cannot be overwritten until all of its consumers have been submitted.
  • Applies the same dependency-driven scheduling to every batch, without special APIs or states for the first two batches.
  • Uses persistent device scratch to avoid per-batch deallocation and its potential device-wide synchronization.
  • Computes final inertia through the regular batched assignment and reduction pipeline, accumulates it on device, and copies only the final result to host.
  • Leaves the shared ANN batch iterator unchanged.

Benchmark under similar configuration

10 GiB pinned-host FP32 dataset (10,485,760 × 256), 2,560 clusters, three iterations, ten 1 GiB out-of-core batches, and 131,072-sample assignment tiles.

Metric main PR
Median runtime 1.7795 s 0.7962 s
Speedup 1.00× 2.23×
Effective bulk throughput 22.48 GiB/s 50.24 GiB/s
Profiled GPU span 1773.12 ms 785.65 ms
H2D time 752.29 ms 752.38 ms
Kernel time 1003.08 ms 760.97 ms
H2D/kernel overlap 0.00 ms 727.91 ms
H2D overlapped by kernels 0.0% 96.75%
Kernels overlapped by H2D 0.0% 95.66%

Profile

Main branch :
profile_main

This PR :
profile_pr

This PR (multi-GPU) :
multi_gpu_profile

@copy-pr-bot

copy-pr-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@viclafargue viclafargue added improvement Improves an existing functionality non-breaking Introduces a non-breaking change labels Sep 1, 2026
@viclafargue
viclafargue marked this pull request as ready for review September 1, 2026 16:37
@viclafargue
viclafargue requested a review from a team as a code owner September 1, 2026 16:37
@viclafargue

Copy link
Copy Markdown
Contributor Author

/ok to test 4b8a5c5

raft::resource::get_stream_pool_size(handle) >= 1) {
batch_copy_stream = raft::resource::get_stream_from_stream_pool(handle);
} else {
owned_batch_copy_stream.emplace(rmm::cuda_stream::flags::non_blocking);

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.

Lets not create a prefetch stream on our own. It is solely controlled by the raft resource. The resource should give full control of concurrent streams to the user.

IndexT row_width,
IndexT batch_size,
rmm::cuda_stream_view copy_stream,
rmm::device_async_resource_ref mr)

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.

hardcoding rmm::device_async_resource_ref wont give us problems? What if the currently set MR is not an async resource?

{
auto const& batch = batches_[pos];
const auto bytes = batch.size * row_width_ * sizeof(DataT);
RAFT_CUDA_TRY(cudaMemcpyAsync(

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.

raft::copy()

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.

Correct me if I am wrong but the main new addition here in comparison to the ANN batch loader is the recycling at the final slots, right? So prefetch continues for the next iteration. Having an entirely new batch loader makes me a bit uneasy. Prefetch should not be algorithm specific. Can we combine this with the existing ANN batch loader (this can be done in a follow up)?

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

Labels

improvement Improves an existing functionality non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants