diff options
| author | Jon Chesterfield <jonathanchesterfield@gmail.com> | 2023-08-31 14:34:01 +0100 |
|---|---|---|
| committer | Jon Chesterfield <jonathanchesterfield@gmail.com> | 2023-08-31 14:34:02 +0100 |
| commit | 1143da22450a1f8de510ef75613648fb614faee7 (patch) | |
| tree | 670cdff04f4e0544d07008e515d27c4444678972 /libc/src/__support/GPU | |
| parent | 37a3de1e2eedf848b8442217ef3790436f69a7db (diff) | |
[libc][gpu] Thread divergence fix on volta
The inbox/outbox loads are performed by the current warp, not a single thread.
The outbox load indicates whether a port has been successfully opened. If some
lanes in the warp think it has and others think the port open failed, as the
warp happened to be diverged when the load occurred, all the subsequent control
flow will be incorrect.
The inbox load indicates whether the machine on the other side of the RPC channel
has progressed. If lanes in the warp have different ideas about that, some will
try to progress their state transition while others won't. As far as the RPC layer
is concerned this is a performance problem and not a correctness one - none of the lanes
can start the transition early, only miss it and start late - but in practice the calls
layered on top of RPC do not have the interface required to detect this event and retry
the load on the stalled lanes, so the calls layered on top will be broken.
None of this is broken on amdgpu, but it's likely that the readfirstlane will have
beneficial performance properties there. Possible significant enough that it's
worth landing this ahead of fixing gpu::broadcast_value on volta.
Essentially volta wasn't adequately considered when writing this part of the protocol.
It's a bug present in the initial prototype and propagated thus far, because none of
the test cases push volta into a warp diverged state in the middle of the RPC sequence.
We should have some test cases for volta where port_open and equivalent are called
from diverged warps.
Reviewed By: jhuber6
Differential Revision: https://reviews.llvm.org/D159276
Diffstat (limited to 'libc/src/__support/GPU')
| -rw-r--r-- | libc/src/__support/GPU/amdgpu/utils.h | 3 | ||||
| -rw-r--r-- | libc/src/__support/GPU/generic/utils.h | 7 | ||||
| -rw-r--r-- | libc/src/__support/GPU/nvptx/utils.h | 17 |
3 files changed, 12 insertions, 15 deletions
diff --git a/libc/src/__support/GPU/amdgpu/utils.h b/libc/src/__support/GPU/amdgpu/utils.h index b8e193a793c6..532a47b4a990 100644 --- a/libc/src/__support/GPU/amdgpu/utils.h +++ b/libc/src/__support/GPU/amdgpu/utils.h @@ -125,7 +125,8 @@ LIBC_INLINE uint32_t get_lane_size() { return LANE_SIZE; } } /// Copies the value from the first active thread in the wavefront to the rest. -[[clang::convergent]] LIBC_INLINE uint32_t broadcast_value(uint32_t x) { +[[clang::convergent]] LIBC_INLINE uint32_t broadcast_value(uint64_t, + uint32_t x) { return __builtin_amdgcn_readfirstlane(x); } diff --git a/libc/src/__support/GPU/generic/utils.h b/libc/src/__support/GPU/generic/utils.h index ba985afa696e..5c7913176b7c 100644 --- a/libc/src/__support/GPU/generic/utils.h +++ b/libc/src/__support/GPU/generic/utils.h @@ -61,12 +61,9 @@ LIBC_INLINE uint32_t get_lane_id() { return 0; } LIBC_INLINE uint64_t get_lane_mask() { return 1; } -LIBC_INLINE uint32_t broadcast_value(uint32_t x) { return x; } +LIBC_INLINE uint32_t broadcast_value(uint64_t, uint32_t x) { return x; } -LIBC_INLINE uint64_t ballot(uint64_t lane_mask, bool x) { - (void)lane_mask; - return x; -} +LIBC_INLINE uint64_t ballot(uint64_t, bool x) { return x; } LIBC_INLINE void sync_threads() {} diff --git a/libc/src/__support/GPU/nvptx/utils.h b/libc/src/__support/GPU/nvptx/utils.h index 20b247e98605..5c6cf3a3a8e1 100644 --- a/libc/src/__support/GPU/nvptx/utils.h +++ b/libc/src/__support/GPU/nvptx/utils.h @@ -111,14 +111,12 @@ LIBC_INLINE uint32_t get_lane_size() { return LANE_SIZE; } } /// Copies the value from the first active thread in the warp to the rest. -[[clang::convergent]] LIBC_INLINE uint32_t broadcast_value(uint32_t x) { - // NOTE: This is not sufficient in all cases on Volta hardware or later. The - // lane mask returned here is not always the true lane mask used by the - // intrinsics in cases of incedental or enforced divergence by the user. - uint32_t lane_mask = static_cast<uint32_t>(get_lane_mask()); - uint32_t id = __builtin_ffs(lane_mask) - 1; +[[clang::convergent]] LIBC_INLINE uint32_t broadcast_value(uint64_t lane_mask, + uint32_t x) { + uint32_t mask = static_cast<uint32_t>(lane_mask); + uint32_t id = __builtin_ffs(mask) - 1; #if __CUDA_ARCH__ >= 600 - return __nvvm_shfl_sync_idx_i32(lane_mask, x, id, get_lane_size() - 1); + return __nvvm_shfl_sync_idx_i32(mask, x, id, get_lane_size() - 1); #else return __nvvm_shfl_idx_i32(x, id, get_lane_size() - 1); #endif @@ -126,10 +124,11 @@ LIBC_INLINE uint32_t get_lane_size() { return LANE_SIZE; } /// Returns a bitmask of threads in the current lane for which \p x is true. [[clang::convergent]] LIBC_INLINE uint64_t ballot(uint64_t lane_mask, bool x) { + uint32_t mask = static_cast<uint32_t>(lane_mask); #if __CUDA_ARCH__ >= 600 - return __nvvm_vote_ballot_sync(static_cast<uint32_t>(lane_mask), x); + return __nvvm_vote_ballot_sync(mask, x); #else - return static_cast<uint32_t>(lane_mask) & __nvvm_vote_ballot(x); + return mask & __nvvm_vote_ballot(x); #endif } /// Waits for all the threads in the block to converge and issues a fence. |
