summaryrefslogtreecommitdiff
path: root/libc/src/__support/GPU
diff options
context:
space:
mode:
authorJon Chesterfield <jonathanchesterfield@gmail.com>2023-08-31 14:34:01 +0100
committerJon Chesterfield <jonathanchesterfield@gmail.com>2023-08-31 14:34:02 +0100
commit1143da22450a1f8de510ef75613648fb614faee7 (patch)
tree670cdff04f4e0544d07008e515d27c4444678972 /libc/src/__support/GPU
parent37a3de1e2eedf848b8442217ef3790436f69a7db (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.h3
-rw-r--r--libc/src/__support/GPU/generic/utils.h7
-rw-r--r--libc/src/__support/GPU/nvptx/utils.h17
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.