Skip to content

[ROCm] Fix the experimental HIP backend for ROCm 7.x on AMD GPUs#484

Open
jeffdaily wants to merge 10 commits into
stotko:masterfrom
jeffdaily:moat-port
Open

[ROCm] Fix the experimental HIP backend for ROCm 7.x on AMD GPUs#484
jeffdaily wants to merge 10 commits into
stotko:masterfrom
jeffdaily:moat-port

Conversation

@jeffdaily

Copy link
Copy Markdown

This fixes the existing experimental HIP backend so it builds on ROCm 7.x and runs correctly across both AMD wavefront sizes: wave64 (CDNA) and wave32 (RDNA). It does not add a new backend; it repairs the one already present (STDGPU_BACKEND_HIP).

Three independent issues are addressed. Review them in order:

  1. CMake HIP language propagation (build break). On ROCm 7.x, linking against rocThrust pulls in hip::device, whose INTERFACE_COMPILE_OPTIONS add HIP flags (-x hip, --offload-arch=...) gated on $<COMPILE_LANGUAGE:CXX>. Those flags reach the system C++ compiler building stdgpu's .cpp files, which rejects --offload-arch and fails. The fix marks the .cpp sources that include HIP headers as LANGUAGE HIP (shared impl, HIP backend impl, examples, tests) so the HIP compiler handles them. All edits are guarded on STDGPU_BACKEND_HIP; the CUDA and OpenMP builds are untouched.

  2. wave64/wave32 spinlock livelock (hang in concurrent containers). The lock-free containers retry per-slot try_lock() in spin loops. AMD GPUs give no per-lane forward-progress guarantee: when several lanes of one wavefront contend for the same lock, the winning lane is stuck at SIMT reconvergence waiting on the losers, who spin forever. CUDA Volta+ Independent Thread Scheduling lets the winner proceed; AMD's execution model does not. A new helper, detail::wave_lock_serialize, elects one active lane at a time via __ballot and runs the spin-loop body for that lane, so each lane completes its critical section in turn. It is wave-size agnostic (the ballot mask covers whatever lanes are active, 32 or 64) and is a zero-cost pass-through on CUDA, where ITS already handles this. It wraps the insert/erase loops in unordered_base and the push/pop loops in deque and vector.

  3. RDNA hang in valid() (degenerate single-slot reduction). Container valid() checks route through transform_reduce_index, a wrapper over thrust::transform_reduce. On a degenerate single-slot table that is full or has an emptied excess list, this lowers to a single-block rocPRIM reduce whose internal hipMemcpyWithStream copy of the result never completes on some RDNA GPUs (the launch returns success but the result copy wedges until the watchdog fires). For the device execution policy on the HIP backend only, this replaces the thrust reduce with a small hand-written kernel plus a plain hipMemcpy device-to-host and a host-side fold, computing the identical reduction (same init, same associative binary op). The host policy and the CUDA backend keep the thrust path unchanged.

The shared headers (wave_lock.h, numeric_detail.h) gate every AMD-specific path behind HIP_PLATFORM_AMD / STDGPU_BACKEND_HIP, so CUDA and OpenMP behavior is unchanged.

This work was authored with the assistance of Claude, an AI assistant by Anthropic.

Test Plan

Built and ran the full test suite on three AMD GPUs spanning both wavefront sizes:

gfx90a (MI250X, CDNA2, wave64), ROCm 7.2.x, Linux:

cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
  -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a
cmake --build build --parallel
./build/bin/teststdgpu

gfx1100 (RDNA3, wave32), ROCm, Linux:

cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
  -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx1100
cmake --build build --parallel
./build/bin/teststdgpu

gfx1201 (RX 9070 XT, RDNA4, wave32), ROCm 7.14, Windows:

HIP_VISIBLE_DEVICES=0 build-gfx1201/bin/teststdgpu.exe

All 702 tests pass on every arch, deterministic across repeated runs. The previously hanging concurrent-container tests (insert/erase/emplace while full or with an emptied excess list, and the simultaneous push/pop suites) now pass.

The CUDA backend was additionally compile-checked with nvcc (CUDA 13.3) to confirm these shared-header changes do not affect it: the library and an instantiation of deque, vector, unordered_map and unordered_set compile, with wave_lock_serialize taking its zero-cost non-AMD pass-through. No NVIDIA GPU was available, so the CUDA backend was not run. The OpenMP backend is likewise unaffected: the valid() reduction change is STDGPU_BACKEND_HIP-guarded, and wave_lock_serialize is a host pass-through off AMD.

This fixes the existing experimental HIP backend so it builds on ROCm 7.x and runs correctly across both AMD wavefront sizes: wave64 (CDNA) and wave32 (RDNA). It does not add a new backend; it repairs the one already present (STDGPU_BACKEND_HIP).

Three independent issues are addressed. Review them in order:

1. CMake HIP language propagation (build break). On ROCm 7.x, linking against rocThrust pulls in hip::device, whose INTERFACE_COMPILE_OPTIONS add HIP flags (-x hip, --offload-arch=...) gated on $<COMPILE_LANGUAGE:CXX>. Those flags reach the system C++ compiler building stdgpu's .cpp files, which rejects --offload-arch and fails. The fix marks the .cpp sources that include HIP headers as LANGUAGE HIP (shared impl, HIP backend impl, examples, tests) so the HIP compiler handles them. All edits are guarded on STDGPU_BACKEND_HIP; the CUDA and OpenMP builds are untouched.

2. wave64/wave32 spinlock livelock (hang in concurrent containers). The lock-free containers retry per-slot try_lock() in spin loops. AMD GPUs give no per-lane forward-progress guarantee: when several lanes of one wavefront contend for the same lock, the winning lane is stuck at SIMT reconvergence waiting on the losers, who spin forever. CUDA Volta+ Independent Thread Scheduling lets the winner proceed; AMD's execution model does not. A new helper, detail::wave_lock_serialize, elects one active lane at a time via __ballot and runs the spin-loop body for that lane, so each lane completes its critical section in turn. It is wave-size agnostic (the ballot mask covers whatever lanes are active, 32 or 64) and is a zero-cost pass-through on CUDA, where ITS already handles this. It wraps the insert/erase loops in unordered_base and the push/pop loops in deque and vector.

3. RDNA hang in valid() (degenerate single-slot reduction). Container valid() checks route through transform_reduce_index, a wrapper over thrust::transform_reduce. On a degenerate single-slot table that is full or has an emptied excess list, this lowers to a single-block rocPRIM reduce whose internal hipMemcpyWithStream copy of the result never completes on some RDNA GPUs (the launch returns success but the result copy wedges until the watchdog fires). For the device execution policy on the HIP backend only, this replaces the thrust reduce with a small hand-written kernel plus a plain hipMemcpy device-to-host and a host-side fold, computing the identical reduction (same init, same associative binary op). The host policy and the CUDA backend keep the thrust path unchanged.

The shared headers (wave_lock.h, numeric_detail.h) gate every AMD-specific path behind __HIP_PLATFORM_AMD__ / STDGPU_BACKEND_HIP, so CUDA and OpenMP behavior is unchanged.

This work was authored with the assistance of Claude, an AI assistant by Anthropic.

Test Plan

Built and ran the full test suite on three AMD GPUs spanning both wavefront sizes:

gfx90a (MI250X, CDNA2, wave64), ROCm 7.2.x, Linux:

    cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
      -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a
    cmake --build build --parallel
    ./build/bin/teststdgpu

gfx1100 (RDNA3, wave32), ROCm, Linux:

    cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
      -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx1100
    cmake --build build --parallel
    ./build/bin/teststdgpu

gfx1201 (RX 9070 XT, RDNA4, wave32), ROCm 7.14, Windows:

    HIP_VISIBLE_DEVICES=0 build-gfx1201/bin/teststdgpu.exe

All 702 tests pass on every arch, deterministic across repeated runs. The previously hanging concurrent-container tests (insert/erase/emplace while full or with an emptied excess list, and the simultaneous push/pop suites) now pass.

The CUDA backend was additionally compile-checked with nvcc (CUDA 13.3) to confirm these shared-header changes do not affect it: the library and an instantiation of deque, vector, unordered_map and unordered_set compile, with wave_lock_serialize taking its zero-cost non-AMD pass-through. No NVIDIA GPU was available, so the CUDA backend was not run. The OpenMP backend is likewise unaffected: the valid() reduction change is STDGPU_BACKEND_HIP-guarded, and wave_lock_serialize is a host pass-through off AMD.
@stotko stotko added this to the 2.0.0 milestone Jun 20, 2026

@stotko stotko left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for contributing fixes for the experimental HIP backend! This is much appreciated.

Some of the fixes appear to be workarounds and I would be happy if you can comment on how this can potentially be fixed upstream in ROCm/HIP as I try to keep workarounds and backend-specific code at a minimum.

Comment thread src/stdgpu/impl/wave_lock.h Outdated
Comment thread src/stdgpu/impl/wave_lock.h Outdated
Comment thread src/stdgpu/impl/wave_lock.h Outdated
Comment thread src/stdgpu/impl/wave_lock.h Outdated
Comment thread src/stdgpu/impl/wave_lock.h Outdated
Comment thread examples/CMakeLists.txt
# For HIP backend, mark .cpp examples as HIP language to avoid
# flag mismatch from hip::device interface propagation.
if(STDGPU_BACKEND STREQUAL STDGPU_BACKEND_HIP)
set_source_files_properties("${ARGV0}.cpp" PROPERTIES LANGUAGE HIP)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

So far, I worked around this by setting the C++ compiler to HIP Clang:

sh tools/backend/helper/configure.sh $CONFIG -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DSTDGPU_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON -DCMAKE_CXX_COMPILER=hipcc $@

I wonder why hip::device adds these incompatible flags to the C++ compiler even with first-class HIP language support enabled in CMake. Therefore, I believe hip::device should be fixed upstream to support such mixed compiler scenarios. IIRC, some very old ROCm versions (perhaps the 3.x or 4.x era) supported this scenario. Do you know more about the reason of the target's behavior and why is the flags are exposed?

In general, I would suggest to patch roc::rocthrust/hip::device or update stdgpu's thrust::thrust target, if possible, as downstream user who link against stdgpu may also face this issue.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

On closer investigation, these LANGUAGE HIP markings are not actually a workaround for the -x hip propagation. I stripped hip::device's -x hip/--offload-arch interface flags entirely and these translation units still fail to compile with the host C++ compiler: the rocThrust/rocPRIM headers they include use device-compiler builtins (e.g. __builtin_amdgcn_wavefrontsize) and clang template extensions that g++ cannot parse. So compiling these sources with the HIP compiler is required regardless of the flag -- the standard host-compiler + per-source LANGUAGE HIP pattern (the C++ compiler stays the host compiler; only device sources go through the HIP compiler).

The -x hip propagation you noticed is real and separate: hip::device adds it to its CXX interface and rocThrust links hip::device publicly, so it reaches consumers. I agree that is awkward for the mixed-compiler case and looks like an upstream hip::device matter rather than something stdgpu can patch cleanly, since stripping it does not remove the need to compile these TUs as HIP. I corrected the comments in 5fe849b to state the actual reason instead of attributing the markings to the flag.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the explanation. I still find this behavior questionable and believe that such HIP-exclusive compiler builtins should not be included unconditionally. thrust with its CUDA and OpenMP backends perfectly supports mixed-compiler scenario and especially running host-side algorithms (via thrust::host execution policy and proper dispatching) in plain CXX files without the need for the CUDA compiler. My impression so far was that rocThrust adds an equivalent HIP backend to thrust that works similarly.

That said, I am not against the proposed changes and they are fine with me. However, I still consider this approach as a workaround to a fundamental limitation and not the intended way for users. Thus, there is much room for improvement for rocThrust to catch up with CUDA thrust. So, could you raise the attention for this limitation as I am for sure not the first one mentioning this issue?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Understood, and I agree host-side thrust algorithms should not require the HIP compiler. I have logged the mixed-compiler / host-only-thrust limitation to raise with the rocThrust maintainers so downstream stdgpu users are not forced through the HIP compiler for host-side code. Nothing to change here for it.

Comment thread src/stdgpu/impl/numeric_detail.h Outdated
@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.68%. Comparing base (0bebd1f) to head (9f1c6ef).

Files with missing lines Patch % Lines
src/stdgpu/impl/deque_detail.cuh 92.00% 4 Missing ⚠️
src/stdgpu/impl/vector_detail.cuh 92.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #484      +/-   ##
==========================================
- Coverage   96.79%   96.68%   -0.12%     
==========================================
  Files          33       35       +2     
  Lines        2622     2652      +30     
==========================================
+ Hits         2538     2564      +26     
- Misses         84       88       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The wave-serialization changes added to deque, vector, and unordered_base
detail headers were not run through the project's clang-format (v18) config,
failing the Clang-Format lint job. This reformats only those touched regions
to the project style; no functional change.

Authored with assistance from Claude.

Test Plan: clang-format 18.1.3 (matching CI's clang-format-18) reports clean:
```
clang-format --dry-run --Werror src/stdgpu/impl/{deque,vector,unordered_base}_detail.cuh src/stdgpu/impl/numeric_detail.h
```
@jeffdaily

Copy link
Copy Markdown
Author

Thanks for taking a look, and for wanting to keep the backend-specific surface small; I agree that's the right goal. Here is the rationale for each change and whether it can move upstream.

1. Wave-serialized locking (wave_lock.h and the container retry loops)

This is the one change that is not a workaround for a ROCm defect but an adaptation to the hardware execution model, so it cannot be addressed in ROCm/HIP. The concurrent containers use a try_lock/retry spin loop. On NVIDIA Volta and later, Independent Thread Scheduling gives each lane an independent program counter, so a lane that acquires the lock makes forward progress while its peers keep spinning. AMD CDNA (wave64) and RDNA (wave32) execute a wavefront in lockstep with no per-lane forward-progress guarantee: the lane that wins the CAS cannot advance past the reconvergence point until the losing lanes also exit the loop, which never happens, so the kernel livelocks. wave_lock_serialize elects one active lane at a time via ballot so only one lane contends per iteration. It is guarded by __HIP_DEVICE_COMPILE__ && __HIP_PLATFORM_AMD__, so the CUDA path is unchanged (it compiles to a direct call of the body). The limitation is architectural, so I don't expect ROCm to remove the need for it.

2. transform_reduce_index_device (numeric_detail.h)

This is a workaround for a rocThrust bug and can be removed once that is fixed. Where valid() reduces over a degenerate capacity-1 collision-chain table, rocThrust's single-block thrust::transform_reduce hangs in its result-copy path on RDNA4 and the RDNA3.5 APU; gfx90a and gfx1100 traverse the same structure without issue. The replacement is a plain reduction kernel plus a plain hipMemcpy that computes the identical reduction (same init, same associative operator), so behavior is unchanged on every architecture. I'm pursuing this with rocThrust; if it's fixed upstream we can drop the hand-written path and call thrust::transform_reduce unconditionally again.

3. CMake LANGUAGE HIP markings

These are less a workaround than the standard way to build HIP sources with the CMake HIP language: rocThrust's hip::device target propagates the HIP compile options to its consumers, so the .cpp translation units that include the backend headers have to be compiled by the HIP compiler, and LANGUAGE HIP expresses that without renaming them to .hip. That said, the current form spreads per-file lists across four CMakeLists, which is more ad hoc than it needs to be. I'll consolidate them into a single device-source list applied once, mirroring the existing STDGPU_DEVICE_HEADERS block, so the intent lives in one place.

…ernel

Addresses review feedback on transform_reduce_index: the hand-written
block-reduction kernel was only needed because rocThrust's single-block
thrust::transform_reduce can hang in its result-copy path on some RDNA GPUs
for small, degenerate reductions (validity checks over a nearly-full
capacity-1 table). Rather than carry a full custom reduction (which also
ignored the execution policy's stream), only the small-size case is special
cased: the transformed values are materialized with a plain thrust::transform
(an element-wise launch that is unaffected) and folded on the host. Larger
reductions keep using the regular multi-block thrust::transform_reduce path.

This removes the custom kernel and its stream-0 behavior while preserving the
identical reduction (same init, same associative operator).

Authored with assistance from Claude.

Test Plan: built the HIP backend for gfx90a (ROCm 7.2) and ran the full test
suite; all 702 tests pass.
```
cmake -B build -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a \
  -DSTDGPU_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j && ./build/bin/teststdgpu
```
The HIP device path of transform_reduce_index folds reductions of size
<= transform_reduce_index_host_threshold on the host to avoid a rocThrust
single-block transform_reduce hang on RDNA4. The threshold value (4096)
was previously a round guess "above" rocPRIM's single-block cutoff.

Comment-only change: the value is unchanged; the comment now documents
that 4096 is exactly rocPRIM's measured single-block reduce capacity, so
the bound is derived rather than guessed. rocPRIM runs the single-block
reduce when the size does not exceed its reduce-config items_per_block
(block_size * items_per_thread); above that the reduce is multi-block and
does not hit the hang. Measuring that config on the target GPU with
rocPRIM's own debug output gives items_per_block = 256 * 16 = 4096 for
both element types reduced here (bool for the validity AND-checks,
index_t for bitset::count). Folding sizes up to 4096 therefore covers
exactly the single-block path and never folds a larger reduction.

Authored with the assistance of Claude (Anthropic).

Test Plan: measured rocPRIM's single-block cutoff on gfx1201 (RX 9070 XT,
ROCm 7.14 / rocPRIM 4.4.0) by calling rocprim::reduce with
debug_synchronous=true for bool and int inputs:

```
clang++ -std=c++17 -x hip probe.cpp -o probe.exe --offload-arch=gfx1201 \
  -I$ROCM/include -L$ROCM/lib -lamdhip64 -D__HIP_PLATFORM_AMD__
HIP_VISIBLE_DEVICES=0 ./probe.exe
# input_type=bool: block_size 256, items_per_block 4096, number of blocks 1
# input_type=int : block_size 256, items_per_block 4096, number of blocks 1
```

Device code is unchanged (comment only), so the existing GPU validation
of teststdgpu (all 702 pass on gfx90a / gfx1100 / gfx1201) still holds.
…atch

Addresses review feedback on the wave-serialization helper:

- Renames the helper from wave_lock_serialize to warp_convergent_execute: the
  body is not always serialized (the CUDA and OpenMP backends run it directly),
  and "warp" is the more common term, used throughout HIP as well.
- Renames impl/wave_lock.h to impl/execution_detail.h, following the naming of
  the closest standard-library headers, and registers it in the installed header
  set (the previous file was never added to a CMake FILE_SET).
- Moves the backend-specific implementation into the respective backend
  subdirectories and namespaces (stdgpu::hip / stdgpu::cuda / stdgpu::openmp),
  dispatched via STDGPU_BACKEND_NAMESPACE, mirroring the atomic and memory
  facilities. The generic stdgpu::detail wrapper now shields the backend macros
  from the containers. The dispatch is by backend rather than by
  __HIP_DEVICE_COMPILE__, so the device-intrinsic guards are no longer needed.

The HIP implementation is unchanged (same ballot-based election); this only
relocates and renames it.

Authored with assistance from Claude.

Test Plan: built the HIP backend for gfx90a (ROCm 7.2) and ran the full test
suite; all 702 tests pass.
```
cmake -B build -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a \
  -DSTDGPU_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j && ./build/bin/teststdgpu
```
Addresses review feedback on the HIP warp-convergence helper:

- Replaces __ballot(1) with __activemask(). On HIP __activemask() is defined as
  __ballot(true), so this is equivalent while using the more portable name that
  also matches the modern CUDA spelling.
- Replaces __lane_id() (an undocumented built-in) with the lane computed from the
  documented threadIdx/warpSize builtins. HIP numbers threads within a wavefront
  in row-major order, so the lane is the linear thread index modulo the wavefront
  size, which is exactly what __lane_id() returns.

Both produce the same election as before; this only changes how the active mask
and lane index are obtained.

Authored with assistance from Claude.

Test Plan: built the HIP backend for gfx90a (ROCm 7.2, wave64) and ran the full
test suite; all 702 tests pass, including the concurrent contention tests that
previously livelocked (simultaneous_push_*, insert/erase_range_unique_parallel,
insert_parallel_while_one_free).
```
cmake -B build -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a \
  -DSTDGPU_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build -j && ./build/bin/teststdgpu
```
The comments claimed these sources are marked LANGUAGE HIP to avoid a flag
mismatch from hip::device propagating -x hip to CXX consumers. That is not the
actual reason: these translation units include rocThrust/rocPRIM headers, which
use device-compiler builtins (e.g. __builtin_amdgcn_wavefrontsize) and clang
template extensions that the host C++ compiler cannot parse at all, independent
of any -x hip flag. Compiling them with the HIP compiler is therefore required,
not a workaround. This is the standard host-compiler + per-source LANGUAGE HIP
pattern. Comment-only change; the markings are unchanged.

Authored with assistance from Claude.

@stotko stotko left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The current version looks much better, thanks 👍 I have a couple of minor comments, mostly some stylistic cleanups that I would like to get in before merging this.

Comment thread examples/CMakeLists.txt
# For HIP backend, mark .cpp examples as HIP language to avoid
# flag mismatch from hip::device interface propagation.
if(STDGPU_BACKEND STREQUAL STDGPU_BACKEND_HIP)
set_source_files_properties("${ARGV0}.cpp" PROPERTIES LANGUAGE HIP)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the explanation. I still find this behavior questionable and believe that such HIP-exclusive compiler builtins should not be included unconditionally. thrust with its CUDA and OpenMP backends perfectly supports mixed-compiler scenario and especially running host-side algorithms (via thrust::host execution policy and proper dispatching) in plain CXX files without the need for the CUDA compiler. My impression so far was that rocThrust adds an equivalent HIP backend to thrust that works similarly.

That said, I am not against the proposed changes and they are fine with me. However, I still consider this approach as a workaround to a fundamental limitation and not the intended way for users. Thus, there is much room for improvement for rocThrust to catch up with CUDA thrust. So, could you raise the attention for this limitation as I am for sure not the first one mentioning this issue?

Comment thread src/stdgpu/impl/deque_detail.cuh Outdated
Comment thread src/stdgpu/impl/deque_detail.cuh Outdated
Comment thread src/stdgpu/impl/deque_detail.cuh Outdated
Comment thread src/stdgpu/impl/deque_detail.cuh Outdated
Comment thread src/stdgpu/impl/vector_detail.cuh Outdated
Comment thread src/stdgpu/impl/vector_detail.cuh Outdated
Comment thread src/stdgpu/impl/deque_detail.cuh Outdated
Comment thread src/stdgpu/impl/unordered_base_detail.cuh Outdated
Comment thread src/stdgpu/impl/vector_detail.cuh Outdated
jeffdaily added 2 commits July 2, 2026 18:39
Address review feedback on the concurrent-container spinlock port.

The detail headers (deque/unordered_base/vector) included the private
impl/execution_detail.h directly. Per the project convention that a
detail header is pulled in only through its user-facing header, they now
include stdgpu/execution.h, which gains an include of
impl/execution_detail.h at the bottom (mirroring atomic.cuh/memory.h).

Remove the inline "wave-serialize" comments at each warp_convergent_execute
call site and the backend-specific explanation in execution_detail.h; the
per-backend rationale already lives in the stdgpu::hip / stdgpu::cuda /
stdgpu::openmp execution headers, so it is not duplicated at the call
sites or the backend-agnostic wrapper.

No functional change to device code; only header include paths and
comments are touched.

Authored with the assistance of Claude (Anthropic).

Test Plan:
Built and ran the full test suite on AMD gfx90a (ROCm 7.2.1, wave64):

  cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
    -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a \
    -DSTDGPU_BUILD_TESTS=ON -DSTDGPU_BUILD_EXAMPLES=ON
  cmake --build build -j$(nproc)
  build/bin/teststdgpu

  702 tests passed, 0 memory leaks.

Also configured and ran the OpenMP backend (CCCL thrust) on the same host:

  cmake -B build-omp -S . -DSTDGPU_BACKEND=STDGPU_BACKEND_OPENMP \
    -DSTDGPU_BUILD_TESTS=ON
  cmake --build build-omp -j$(nproc)
  build-omp/bin/teststdgpu

  608 tests passed, 0 memory leaks.
The 72687fe commit added #include <stdgpu/impl/execution_detail.h> at the
bottom of execution.h so that user-facing header files (deque.cuh, etc.)
can include execution.h instead of reaching into execution_detail.h
directly.  However, execution_detail.h and the cuda/execution.cuh it pulls
in both declare functions with STDGPU_DEVICE_ONLY, which expands to
STDGPU_CUDA_DEVICE_ONLY -- a macro that is intentionally left undefined
when the host C++ compiler (g++) compiles plain .cpp translation units
(i.e. TUs that are not processed by nvcc).  This caused build failures in
the CUDA backend for every .cpp file that transitively includes execution.h.

Fix: add #include <stdgpu/platform.h> before the guard (so the numeric
values STDGPU_BACKEND_CUDA, STDGPU_BACKEND_OPENMP, etc. are defined) and
wrap the execution_detail.h include in a preprocessor guard that fires only
when a device compiler is active (__CUDACC__ for nvcc / clang-cuda, __HIP__
for clang++/HIP) or when the OpenMP backend is in use (which has no
device-only qualifiers).  The guard is a no-op for HIP and OpenMP builds
(both compilers define the relevant macro), and fixes the CUDA backend by
suppressing the problematic include in g++-compiled TUs.

Verified: CUDA backend (nvcc 12.6, arch 80) builds to completion including
teststdgpu; HIP backend (ROCm 7.2.1, gfx90a) and OpenMP backend (CCCL
Thrust 2.6.1) rebuild cleanly with no device-code change (incremental
builds only relink, no object recompilation).

Authored with the assistance of Claude (Anthropic).

Test Plan:
CUDA backend (compile-only, no NVIDIA GPU):
  cmake -B build-cuda -S . -DSTDGPU_BACKEND=STDGPU_BACKEND_CUDA \
    -DCMAKE_CUDA_COMPILER=/opt/conda/envs/cuda/bin/nvcc \
    -DCMAKE_CUDA_ARCHITECTURES=80 -DSTDGPU_BUILD_TESTS=ON
  cmake --build build-cuda --parallel $(nproc)
  -> All targets built, including teststdgpu (linked but not run)

HIP backend (incremental rebuild):
  cmake --build build -j$(nproc)
  -> Only relinking, no HIP object recompilation; confirms no device-code change

OpenMP backend (incremental rebuild):
  cmake --build build-omp -j$(nproc)
  -> Only relinking; confirms no device-code change

@stotko stotko left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for addressing the comments and testing the code! Could you incorporate the final cleanup suggested below and ensure that formatting via clang-format is correct? I believe the PR should be ready then. The Windows-specific build failures are unrelated to this PR and I will take care of them.

Comment thread src/stdgpu/execution.h Outdated
Comment thread src/stdgpu/execution.h Outdated
Two cleanups to the device-only execution helper include layering.

Replace the hand-rolled guard in execution.h with the existing
STDGPU_DETAIL_IS_DEVICE_COMPILED helper from platform.h, which already
checks whether the current translation unit is compiled by a device
compiler and covers every backend (CUDA/HIP are 1 only under the device
compiler, OpenMP is always 1). This keeps the host-only .cpp translation
units in the CUDA backend from seeing the __device__-qualified helper
while still pulling it in for OpenMP.

Rename impl/execution_detail.h to impl/execution_detail.cuh to make its
device-only nature explicit, matching the other detail headers that
define device code, and shorten the now-redundant comment above the
conditional include. Also sort the execution.h include added to
unordered_base_detail.h so clang-format is clean.

This work was authored with the assistance of Claude, an AI assistant.

Test Plan:

Built and ran the test suite on an AMD Instinct MI250X (gfx90a),
ROCm 7.2.1, with interface header-set verification enabled:

    cmake -B build -S . -DCMAKE_BUILD_TYPE=Release \
      -DSTDGPU_BACKEND=STDGPU_BACKEND_HIP -DCMAKE_HIP_ARCHITECTURES=gfx90a \
      -DSTDGPU_BUILD_TESTS=ON -DSTDGPU_BUILD_EXAMPLES=ON \
      -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON
    cmake --build build -j
    ./build/bin/teststdgpu      # 702/702 passed, 0 leaks

Compile-checked the CUDA backend with nvcc 12.6 (host TUs via g++) and
built the OpenMP backend, both with header-set verification:

    cmake -B build-cuda -S . -DSTDGPU_BACKEND=STDGPU_BACKEND_CUDA \
      -DCMAKE_CUDA_COMPILER=nvcc -DCMAKE_CUDA_ARCHITECTURES=70 \
      -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON
    cmake --build build-cuda -j     # builds and links

    cmake -B build-omp -S . -DSTDGPU_BACKEND=STDGPU_BACKEND_OPENMP \
      -DCMAKE_VERIFY_INTERFACE_HEADER_SETS=ON
    cmake --build build-omp -j
    ./build-omp/bin/teststdgpu      # 608/608 passed, 0 leaks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants