Skip to content

[libcu++] Use CCCL Runtime in CUB metric pre-runs pt 1#9802

Open
pciolkosz wants to merge 1 commit into
NVIDIA:mainfrom
pciolkosz:adopt-cccl-runtime-2800-set6
Open

[libcu++] Use CCCL Runtime in CUB metric pre-runs pt 1#9802
pciolkosz wants to merge 1 commit into
NVIDIA:mainfrom
pciolkosz:adopt-cccl-runtime-2800-set6

Conversation

@pciolkosz

Copy link
Copy Markdown
Contributor

#2800 hitting cub benchmarks

@pciolkosz pciolkosz requested review from a team as code owners July 11, 2026 04:52
@pciolkosz pciolkosz requested a review from jrhemstad July 11, 2026 04:52
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Jul 11, 2026
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Review in CCCL Jul 11, 2026
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Performance

    • Improved CUDA benchmark memory handling and stream synchronization for more consistent performance measurements.
    • Updated reduction, encoding, and selection benchmarks to use streamlined device and pinned-host buffering.
  • Improvements

    • Standardized CUDA benchmark execution across supported workloads.
    • Added reusable device-buffer generation utilities to simplify benchmark data preparation.
    • Improved handling of generated data, including complex and segmented inputs.

Walkthrough

CUDA benchmarks now allocate through device memory pools and CUDA buffers, synchronize benchmark streams directly, and construct stream-aware CUB environments. Shared helpers add stream access, pinned memory, default bounds, and device-buffer generators.

Changes

CUDA benchmark memory and execution updates

Layer / File(s) Summary
Stream and memory-resource helpers
nvbench_helper/nvbench_helper/nvbench_helper.cuh
Adds stream-reference, pinned-memory, default-bound, and updated CUDA environment helpers.
Device buffer generator APIs
nvbench_helper/nvbench_helper/nvbench_helper.cuh
Adds CUDA device-buffer generation for generic, vector, complex-default, and uniform key-segment generators.
Reduce and run-length benchmarks
cub/benchmarks/bench/reduce/by_key.cu, cub/benchmarks/bench/run_length_encode/*.cu
Replaces Thrust allocations and device-wide synchronization with CUDA buffers, memory resources, and stream-specific CUB environments.
Select benchmarks
cub/benchmarks/bench/select/unique*.cu
Migrates unique and unique-by-key inputs, outputs, and counters to CUDA buffers with stream-aware execution setup.

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (4)
cub/benchmarks/bench/run_length_encode/encode.cu (1)

50-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: in_keys (lines 57-58) is only read via the const pointer d_in_keys; declare it const auto per the const-correctness guideline (same note as by_key.cu).

diff
-  auto in_keys =
+  const auto in_keys =
     generate.uniform.key_segments(elements, min_segment_size, max_segment_size).device_buffer<T>(stream, device);

Source: Coding guidelines

cub/benchmarks/bench/run_length_encode/non_trivial_runs.cu (1)

51-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: in_keys (lines 58-59) is read-only (only const T* d_in_keys is ever extracted); declare it const auto per the const-correctness guideline, same as the sibling encode.cu/by_key.cu benchmarks.

diff
-  auto in_keys =
+  const auto in_keys =
     generate.uniform.key_segments(elements, min_segment_size, max_segment_size).device_buffer<T>(stream, device);

Source: Coding guidelines

cub/benchmarks/bench/select/unique_by_key.cu (1)

59-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: in_vals (line 64) and in_keys (lines 67-68) are only read through const pointers (d_in_vals, d_in_keys); declare them const auto per the const-correctness guideline (same note as by_key.cu).

diff
-  auto in_vals      = cuda::make_device_buffer<ValueT>(stream, device, elements, ValueT{});
+  const auto in_vals = cuda::make_device_buffer<ValueT>(stream, device, elements, ValueT{});
   auto out_vals     = cuda::make_device_buffer<ValueT>(stream, device, elements, cuda::no_init);
   auto out_keys     = cuda::make_device_buffer<KeyT>(stream, device, elements, cuda::no_init);
-  auto in_keys =
+  const auto in_keys =
     generate.uniform.key_segments(elements, min_segment_size, max_segment_size).device_buffer<KeyT>(stream, device);

Source: Coding guidelines

cub/benchmarks/bench/reduce/by_key.cu (1)

49-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: Make read-only buffers const in_vals and in_keys are only read here, so declare both const auto to match the const data() overloads and avoid accidental mutation.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9b15726a-0223-4e92-be80-d82ddfd1b442

📥 Commits

Reviewing files that changed from the base of the PR and between e6abcc8 and 13e2c3f.

📒 Files selected for processing (6)
  • cub/benchmarks/bench/reduce/by_key.cu
  • cub/benchmarks/bench/run_length_encode/encode.cu
  • cub/benchmarks/bench/run_length_encode/non_trivial_runs.cu
  • cub/benchmarks/bench/select/unique.cu
  • cub/benchmarks/bench/select/unique_by_key.cu
  • nvbench_helper/nvbench_helper/nvbench_helper.cuh

Comment on lines 65 to +73
// Run once to get num_runs for memory accounting
(void) cub::DeviceRunLengthEncode::Encode(
d_in_keys, d_out_keys, d_out_counts, d_num_runs_out, static_cast<OffsetT>(elements));
cudaDeviceSynchronize();
d_in_keys,
d_out_keys,
d_out_counts,
d_num_runs_out,
static_cast<OffsetT>(elements),
cub_bench_env(memory_resource, stream));
stream.sync();

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

important: this pre-run Encode call discards the return status via (void), while the equivalent one-time correctness call in every sibling benchmark file (by_key.cu, non_trivial_runs.cu, unique.cu, unique_by_key.cu) uses _CCCL_TRY_CUDA_API. A failed launch (e.g. temp-storage allocation failure through memory_resource) goes unnoticed, and num_runs_out[0] read right after may reflect an uninitialized/garbage value, silently corrupting the reported memory-access counters.

diff
-  (void) cub::DeviceRunLengthEncode::Encode(
-    d_in_keys,
-    d_out_keys,
-    d_out_counts,
-    d_num_runs_out,
-    static_cast<OffsetT>(elements),
-    cub_bench_env(memory_resource, stream));
+  _CCCL_TRY_CUDA_API(
+    cub::DeviceRunLengthEncode::Encode,
+    "Encode failed",
+    d_in_keys,
+    d_out_keys,
+    d_out_counts,
+    d_num_runs_out,
+    static_cast<OffsetT>(elements),
+    cub_bench_env(memory_resource, stream));

As per path instructions, "Focus on algorithm correctness, temporary-storage protocol, dispatch/policy selection, stream behavior, CUDA error handling, synchronization, memory access safety, performance regressions, and test coverage" for cub/**/*.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Run once to get num_runs for memory accounting
(void) cub::DeviceRunLengthEncode::Encode(
d_in_keys, d_out_keys, d_out_counts, d_num_runs_out, static_cast<OffsetT>(elements));
cudaDeviceSynchronize();
d_in_keys,
d_out_keys,
d_out_counts,
d_num_runs_out,
static_cast<OffsetT>(elements),
cub_bench_env(memory_resource, stream));
stream.sync();
// Run once to get num_runs for memory accounting
_CCCL_TRY_CUDA_API(
cub::DeviceRunLengthEncode::Encode,
"Encode failed",
d_in_keys,
d_out_keys,
d_out_counts,
d_num_runs_out,
static_cast<OffsetT>(elements),
cub_bench_env(memory_resource, stream));
stream.sync();

Source: Path instructions

Comment on lines +329 to +343

#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
template <typename T>
auto device_buffer(cuda::stream_ref stream, cuda::device_ref device, T min, T max)
{
auto buffer = cuda::make_device_buffer<T>(stream, device, m_elements, cuda::no_init);
stream.sync();

cuda::std::span<T> span(buffer.data(), buffer.size());
gen_device(m_seed, span, m_entropy, min, max);

++m_seed;
return buffer;
}
#endif // THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect gen_device implementation for stream/sync semantics
fd -e cu -e cuh . nvbench_helper --exec rg -nP -C4 'gen_device' {} \;
rg -nP -C4 '\bgen_device\b' -g '*.cu' | head -60

Repository: NVIDIA/cccl

Length of output: 2228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the generator dispatch and the device executor implementation.
rg -n -C 6 'template <typename T>\s*void gen\(|gen\(executor::device|struct executor|class executor|namespace executor' nvbench_helper/nvbench_helper/nvbench_helper.cuh
rg -n -C 8 'executor::device|gen\(' nvbench_helper/nvbench_helper/nvbench_helper.cuh

# Show the relevant surrounding sections with line numbers.
sed -n '500,700p' nvbench_helper/nvbench_helper/nvbench_helper.cuh
sed -n '700,860p' nvbench_helper/nvbench_helper/nvbench_helper.cuh

Repository: NVIDIA/cccl

Length of output: 149


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the executor::device implementation and how gen dispatches.
rg -n -C 6 'gen\(executor::device|executor::device|struct device|class device' nvbench_helper
rg -n -C 6 'template <typename.*>\s*void gen\(' nvbench_helper/nvbench_helper/nvbench_helper.cuh

# Read the relevant ranges around gen() and any executor definitions.
sed -n '540,660p' nvbench_helper/nvbench_helper/nvbench_helper.cuh
sed -n '660,780p' nvbench_helper/nvbench_helper/nvbench_helper.cuh

Repository: NVIDIA/cccl

Length of output: 14002


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the device generator implementation and the executor dispatch around it.
sed -n '97,220p' nvbench_helper/nvbench_helper/nvbench_helper.cu
sed -n '260,320p' nvbench_helper/nvbench_helper/nvbench_helper.cu

Repository: NVIDIA/cccl

Length of output: 5245


🌐 Web query:

CURAND curandGenerateUniformDouble asynchronous stream semantics curandSetStream documentation

💡 Result:

In the cuRAND library, functions such as curandGenerateUniformDouble operate asynchronously by launching kernels onto the specified CUDA stream [1][2]. When these functions are called, they initiate the random number generation process and return control to the host immediately, without waiting for the GPU to complete the operation [1]. The stream associated with these operations is controlled by the curandSetStream function [2]. Key details regarding this mechanism include: Stream Association: The curandSetStream(generator, stream) function configures the generator to use a specific cudaStream_t for all subsequent kernel launches [2]. If curandSetStream is not explicitly called for a given generator, the library defaults to using the null stream (stream 0) [2]. Asynchronous Semantics and Synchronization: Because these generation functions return asynchronously, the host-side code must ensure proper synchronization if subsequent operations depend on the generated results [1]. If other kernels or operations (e.g., memory copies) in a different stream need to consume the random numbers, the developer must use standard CUDA synchronization primitives, such as cudaDeviceSynchronize or stream/event management routines, to ensure the generation kernel has finished before the dependent operations begin [1]. In summary, curandGenerateUniformDouble and related generation functions queue work in the stream assigned via curandSetStream and provide asynchronous execution, requiring explicit CUDA synchronization for dependent tasks [1][2].

Citations:


important: gen_device can return before cuRAND finishes filling the buffer. curandGenerate* is async, and m_gen is never tied to stream, so the first CUB consumer on stream can race. Tie the generator to stream or sync after generation.

@github-actions

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 2h 46m: Pass: 100%/263 | Total: 12d 11h | Max: 2h 45m | Hits: 14%/1141632

See results here.

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

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants