aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc11
-rw-r--r--tensorflow/core/BUILD7
-rw-r--r--tensorflow/core/kernels/bias_op_gpu.cu.cc18
-rw-r--r--tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc13
-rw-r--r--tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc21
-rw-r--r--tensorflow/core/kernels/svd_op_gpu.cu.cc4
-rw-r--r--tensorflow/core/util/cuda_device_functions.h502
-rw-r--r--tensorflow/core/util/cuda_kernel_helper.h856
-rw-r--r--tensorflow/core/util/cuda_kernel_helper_test.cu.cc60
-rw-r--r--tensorflow/core/util/cuda_launch_config.h284
10 files changed, 786 insertions, 990 deletions
diff --git a/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc b/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc
index 501cddb8c8..8e6870fadd 100644
--- a/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc
+++ b/tensorflow/contrib/reduce_slice_ops/kernels/reduce_slice_ops_gpu.cu.cc
@@ -34,9 +34,9 @@ namespace functor {
__global__ void ReduceSliceDeviceKernel##reduceop( \
Cuda3DLaunchConfig config, Index indices_width, Index bound, \
const T begin, const Index *indices, const T *input, T *out) { \
- CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count.x, X) { \
- CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count.y, Y) { \
- CUDA_AXIS_KERNEL_LOOP(z, config.virtual_thread_count.z, Z) { \
+ CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) { \
+ CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) { \
+ CUDA_AXIS_KERNEL_LOOP(z, config.virtual_thread_count, z) { \
Index outidx = x * config.virtual_thread_count.y * \
config.virtual_thread_count.z + \
y * config.virtual_thread_count.z + z; \
@@ -68,9 +68,8 @@ namespace functor {
if (sizex * sizey * sizez == 0) { \
return; \
} \
- Cuda3DLaunchConfig config = GetCuda3DLaunchConfig( \
- sizex, sizey, sizez, d, ReduceSliceDeviceKernel##reduceop<T, Index>, \
- 0, 0); \
+ Cuda3DLaunchConfig config = GetCuda3DLaunchConfig(sizex, sizey, sizez, d,\
+ ReduceSliceDeviceKernel##reduceop<T, Index>, 0, 0); \
\
ReduceSliceDeviceKernel##reduceop<T, Index> \
<<<config.block_count, config.thread_per_block, 0, d.stream()>>>( \
diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD
index 7a20e36a0b..c855bb05bc 100644
--- a/tensorflow/core/BUILD
+++ b/tensorflow/core/BUILD
@@ -1847,13 +1847,6 @@ cc_library(
],
)
-tf_cuda_library(
- name = "cuda_device_functions",
- hdrs = ["util/cuda_device_functions.h"],
- visibility = ["//visibility:public"],
- deps = [":framework_lite"],
-)
-
# TODO(josh11b): Is this needed, or can we just use ":protos_all_cc"?
cc_library(
name = "protos_cc",
diff --git a/tensorflow/core/kernels/bias_op_gpu.cu.cc b/tensorflow/core/kernels/bias_op_gpu.cu.cc
index 2ca194a77f..42f3db1d79 100644
--- a/tensorflow/core/kernels/bias_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/bias_op_gpu.cu.cc
@@ -173,13 +173,19 @@ __global__ void BiasGradNCHW_SharedAtomics(const T* output_backprop,
// Accumulate the results in the shared memory into the first element.
// No syncthreads is needed since this is only in the same warp.
int32 thread_index = threadIdx.x;
- if (thread_index < 32) {
- AccT data = s_data[thread_index];
- for (int32 delta = warpSize / 2; delta > 0; delta /= 2) {
- data += CudaShuffleXorSync(kCudaWarpAll, data, delta);
- }
+ if (thread_index < 16) {
+ s_data[thread_index] += s_data[thread_index + 16];
+ __syncwarp(0xFFFF);
+ if (thread_index < 8) s_data[thread_index] += s_data[thread_index + 8];
+ __syncwarp(0xFF);
+ if (thread_index < 4) s_data[thread_index] += s_data[thread_index + 4];
+ __syncwarp(0xF);
+ if (thread_index < 2) s_data[thread_index] += s_data[thread_index + 2];
+ __syncwarp(0x3);
if (thread_index == 0) {
- CudaAtomicAdd(bias_backprop + bias_index, T(data));
+ T val = T(s_data[0] + s_data[1]);
+ // The first thread writes out the accumulated result to global location.
+ CudaAtomicAdd(bias_backprop + bias_index, val);
}
}
}
diff --git a/tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc b/tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc
index 5493e33532..903aac5d68 100644
--- a/tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/depthwise_conv_op_gpu.cu.cc
@@ -34,7 +34,6 @@ limitations under the License.
namespace tensorflow {
-typedef Eigen::GpuDevice GPUDevice;
using Eigen::GpuDevice;
// Returns whether depthwise convolution forward or backward input pass can be
@@ -1029,7 +1028,7 @@ __device__ __forceinline__ T WarpSumReduce(T val) {
int zeros = sub_warp * kWidth;
unsigned mask = ((1UL << kWidth) - 1) << zeros;
for (int delta = kWidth / 2; delta > 0; delta /= 2) {
- val += CudaShuffleXorSync(mask, val, delta);
+ val += CudaShuffleXor(mask, val, delta);
}
return val;
}
@@ -1146,7 +1145,7 @@ __launch_bounds__(1024, 2) void DepthwiseConv2dBackpropFilterGPUKernelNHWCSmall(
// Note: the condition to reach this is uniform across the entire block.
__syncthreads();
- unsigned active_threads = CudaBallotSync(kCudaWarpAll, depth_in_range);
+ unsigned active_threads = CudaBallot(CUDA_WARP_ALL, depth_in_range);
if (depth_in_range) {
const T* const out_ptr = inout_offset + output;
@@ -1160,7 +1159,7 @@ __launch_bounds__(1024, 2) void DepthwiseConv2dBackpropFilterGPUKernelNHWCSmall(
T val = out1 * tile_ptr[0] + out2 * tile_ptr[tile_offset];
// Warp-accumulate pixels of the same depth and write to accumulator.
for (int delta = 16; delta >= kBlockSlices; delta /= 2) {
- val += CudaShuffleXorSync(active_threads, val, delta);
+ val += CudaShuffleDown(active_threads, val, delta);
}
if (!(thread_idx & 32 - kBlockSlices) /* lane_idx < kBlockSlices */) {
*accum_ptr = val;
@@ -1400,7 +1399,7 @@ __launch_bounds__(1024, 2) void DepthwiseConv2dBackpropFilterGPUKernelNCHWSmall(
// Note: the condition to reach this is uniform across the entire block.
__syncthreads();
- unsigned active_threads = CudaBallotSync(kCudaWarpAll, slice_in_range);
+ unsigned active_threads = CudaBallot(CUDA_WARP_ALL, slice_in_range);
if (slice_in_range) {
const T* const out_ptr = inout_offset + output;
@@ -1414,10 +1413,10 @@ __launch_bounds__(1024, 2) void DepthwiseConv2dBackpropFilterGPUKernelNCHWSmall(
T val = out1 * tile_ptr[0] + out2 * tile_ptr[tile_offset];
// Warp-accumulate pixels of the same depth and write to accumulator.
for (int delta = 16 / kBlockSlices; delta > 0; delta /= 2) {
- val += CudaShuffleXorSync(active_threads, val, delta);
+ val += CudaShuffleDown(active_threads, val, delta);
}
if (!(thread_idx & 32 / kBlockSlices - 1)) {
- *accum_ptr = val; // kBlockSlices threads per warp.
+ *accum_ptr = val;
}
++shared_offset;
accum_ptr += accum_increment;
diff --git a/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc b/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc
index a3c21edc15..31f74671ca 100644
--- a/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/scatter_nd_op_gpu.cu.cc
@@ -55,27 +55,6 @@ struct LeftUpdate<T, scatter_nd_op::UpdateOp::SUB> {
}
};
-// Specializations for std::complex, updating real and imaginary part
-// individually. Even though this is not an atomic op anymore, it is safe
-// because there is only one type of op per kernel.
-template <typename T>
-struct LeftUpdate<std::complex<T>, scatter_nd_op::UpdateOp::ADD> {
- EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC void operator()(
- std::complex<T>* out, const std::complex<T>& val) {
- T* ptr = reinterpret_cast<T*>(out);
- CudaAtomicAdd(ptr, val.real());
- CudaAtomicAdd(ptr, val.imag());
- }
-};
-
-template <typename T>
-struct LeftUpdate<std::complex<T>, scatter_nd_op::UpdateOp::SUB> {
- EIGEN_STRONG_INLINE EIGEN_DEVICE_FUNC void operator()(
- std::complex<T>* out, const std::complex<T>& val) {
- LeftUpdate<std::complex<T>, scatter_nd_op::UpdateOp::ADD>()(out, -val);
- }
-};
-
} // namespace
template <typename T, typename Index, scatter_nd_op::UpdateOp op, int IXDIM>
diff --git a/tensorflow/core/kernels/svd_op_gpu.cu.cc b/tensorflow/core/kernels/svd_op_gpu.cu.cc
index 8c3a58b108..dedc2da60b 100644
--- a/tensorflow/core/kernels/svd_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/svd_op_gpu.cu.cc
@@ -63,8 +63,8 @@ __global__ void ComputeValueOfVKernel(Cuda2DLaunchConfig config, int64 m,
int64 ldu, const Scalar* M,
const Scalar* U, const Scalar* S,
Scalar* V) {
- CUDA_AXIS_KERNEL_LOOP(batch, config.virtual_thread_count.x, X) {
- CUDA_AXIS_KERNEL_LOOP(i, config.virtual_thread_count.y, Y) {
+ CUDA_AXIS_KERNEL_LOOP(batch, config.virtual_thread_count, x) {
+ CUDA_AXIS_KERNEL_LOOP(i, config.virtual_thread_count, y) {
Scalar v = M[i + m * batch] * U[ldu * (i + m * batch)] * S[batch];
CudaAtomicAdd(V + batch, v);
}
diff --git a/tensorflow/core/util/cuda_device_functions.h b/tensorflow/core/util/cuda_device_functions.h
deleted file mode 100644
index 8f75ddb2cd..0000000000
--- a/tensorflow/core/util/cuda_device_functions.h
+++ /dev/null
@@ -1,502 +0,0 @@
-/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#ifndef TENSORFLOW_CORE_UTIL_CUDA_DEVICE_FUNCTIONS_H_
-#define TENSORFLOW_CORE_UTIL_CUDA_DEVICE_FUNCTIONS_H_
-
-/**
- * Wrappers and helpers for CUDA device code.
- *
- * Wraps the warp-cooperative intrinsics introduced in CUDA 9 to provide
- * backwards compatibility, see go/volta-porting for details.
- * Provides atomic operations on types that aren't natively supported.
- */
-
-#if GOOGLE_CUDA
-
-#include <algorithm>
-#include <complex>
-#include "cuda/include/cuda.h"
-#include "cuda/include/device_functions.h"
-#include "tensorflow/core/platform/types.h"
-
-#if __CUDACC_VER_MAJOR__ >= 9
-#include "cuda/include/cuda_fp16.h"
-#elif __CUDACC_VER__ >= 7050
-#include "cuda/include/cuda_fp16.h"
-#else
-#endif
-
-namespace tensorflow {
-
-namespace detail {
-
-// Helper for range-based for loop using 'delta' increments.
-// Usage: see CudaGridRange?() functions below.
-template <typename T>
-class CudaGridRange {
- struct Iterator {
- __device__ Iterator(T index, T delta) : index_(index), delta_(delta) {}
- __device__ T operator*() const { return index_; }
- __device__ Iterator& operator++() {
- index_ += delta_;
- return *this;
- }
- __device__ bool operator!=(const Iterator& other) const {
- bool greater = index_ > other.index_;
- bool less = index_ < other.index_;
- // Anything past an end iterator (delta_ == 0) is equal.
- // In range-based for loops, this optimizes to 'return less'.
- if (!other.delta_) {
- return less;
- }
- if (!delta_) {
- return greater;
- }
- return less || greater;
- }
-
- private:
- T index_;
- const T delta_;
- };
-
- public:
- __device__ CudaGridRange(T begin, T delta, T end)
- : begin_(begin), delta_(delta), end_(end) {}
-
- __device__ Iterator begin() const { return Iterator{begin_, delta_}; }
- __device__ Iterator end() const { return Iterator{end_, 0}; }
-
- private:
- T begin_;
- T delta_;
- T end_;
-};
-
-} // namespace detail
-
-// Helper to visit indices in the range 0 <= i < count, using the x-coordinate
-// of the global thread index. That is, each index i is visited by all threads
-// with the same x-coordinate.
-// Usage: for(int i : CudaGridRangeX(count)) { visit(i); }
-template <typename T>
-__device__ detail::CudaGridRange<T> CudaGridRangeX(T count) {
- return detail::CudaGridRange<T>(blockIdx.x * blockDim.x + threadIdx.x,
- gridDim.x * blockDim.x, count);
-}
-
-// Helper to visit indices in the range 0 <= i < count using the y-coordinate.
-// Usage: for(int i : CudaGridRangeY(count)) { visit(i); }
-template <typename T>
-__device__ detail::CudaGridRange<T> CudaGridRangeY(T count) {
- return detail::CudaGridRange<T>(blockIdx.y * blockDim.y + threadIdx.y,
- gridDim.y * blockDim.y, count);
-}
-
-// Helper to visit indices in the range 0 <= i < count using the z-coordinate.
-// Usage: for(int i : CudaGridRangeZ(count)) { visit(i); }
-template <typename T>
-__device__ detail::CudaGridRange<T> CudaGridRangeZ(T count) {
- return detail::CudaGridRange<T>(blockIdx.z * blockDim.z + threadIdx.z,
- gridDim.z * blockDim.z, count);
-}
-
-// Mask for all 32 threads in a warp.
-const unsigned kCudaWarpAll = 0xffffffff;
-
-// Returns the warp lane ID of the calling thread
-__device__ inline unsigned CudaLaneId() {
- unsigned int lane_id;
- asm("mov.u32 %0, %%laneid;" : "=r"(lane_id));
- return lane_id;
-}
-
-namespace detail {
-// Returns true if mask is a valid parameter for __shfl*sync to return a well
-// defined value, assuming the calling lane will read from src_lane as part of
-// the shuffle operation.
-//
-// Specifically, returns true iff mask has the calling lane bit and the src_lane
-// bit set, and the src_lane calls this function with the same mask value
-// (required for the two threads to wait for each other).
-//
-// On Volta, for some invalid masks, this function hangs or returns false
-// positives, because the implementation shuffles with the same mask that
-// we are validating. Run on Pascal if you suspect that the mask is incorrect.
-__device__ inline bool CudaValidateShuffleSyncMask(unsigned mask,
- unsigned src_lane) {
- unsigned src_dst_mask = 1u << CudaLaneId() | 1u << src_lane;
-#if CUDA_VERSION >= 9000
- unsigned src_lane_mask = __shfl_sync(mask, mask, src_lane);
-#else
- unsigned src_lane_mask = __shfl(mask, src_lane);
-#endif
- return (src_dst_mask & ~mask) == 0 && src_lane_mask == mask;
-}
-
-// Returns the actual source lane for shuffle.
-__device__ inline unsigned CudaShuffleGetSrcLane(int src_lane, int width) {
- int lane_id = CudaLaneId();
- int lane_base = lane_id & ~width + 1;
- int lane_offset = src_lane & width - 1;
- return lane_base + lane_offset;
-}
-
-// Returns the source lane for shuffle up.
-__device__ inline unsigned CudaShuffleUpGetSrcLane(unsigned delta, int width) {
- unsigned lane_id = CudaLaneId();
- if ((lane_id & width - 1) < delta) {
- return lane_id;
- }
- return lane_id - delta;
-}
-
-// Returns the source lane for shuffle down.
-__device__ inline unsigned CudaShuffleDownGetSrcLane(unsigned delta,
- int width) {
- unsigned lane_id = CudaLaneId();
- if ((lane_id & width - 1) + delta >= width) {
- return lane_id;
- }
- return lane_id + delta;
-}
-
-// Returns the source lane for shuffle xor.
-__device__ inline unsigned CudaShuffleXorGetSrcLane(int lane_mask, int width) {
- int lane_id = CudaLaneId();
- int src_lane = lane_id ^ lane_mask;
- if (src_lane > (lane_id | width - 1)) {
- return lane_id;
- }
- return src_lane;
-}
-} // namespace detail
-
-// For all *_sync wrappers below, it is illegal to synchronize threads from
-// different program locations, because that is not supported before sm_70.
-// In other words, all threads in 'mask' must call the functions in convergence.
-// Code that requires sm_70 (and CUDA 9) may use the intrinsic directly.
-//
-// It is also illegal to shuffle with a mask that produces an undefined result
-// for any of the threads. Specifically, all source threads of the shuffle
-// must have their corresponding bit in 'mask' set.
-
-// Wrapper for __syncwarp. No-op for CUDA 8 and earlier.
-__device__ inline void CudaSyncWarp(unsigned mask = kCudaWarpAll) {
- assert(mask & 1u << CudaLaneId());
-#if CUDA_VERSION >= 9000
- __syncwarp(mask);
-#endif
-}
-
-// Wrapper for __ballot_sync. All threads in 'mask' must call this function in
-// convergence, see comment above for details.
-__device__ inline unsigned CudaBallotSync(unsigned mask, int pred) {
- assert(mask & 1u << CudaLaneId());
-#if CUDA_VERSION >= 9000
- return __ballot_sync(mask, pred);
-#else
- return __ballot(pred) & mask; // Apply mask to match __ballot_sync's spec.
-#endif
-}
-
-// Wrapper for __any_sync. All threads in 'mask' must call this function in
-// convergence, see comment above for details.
-__device__ inline int CudaAnySync(unsigned mask, int pred) {
- assert(mask & 1u << CudaLaneId());
-#if CUDA_VERSION >= 9000
- return __any_sync(mask, pred);
-#else
- return __any(pred);
-#endif
-}
-
-// Wrapper for __all_sync. All threads in 'mask' must call this function in
-// convergence, see comment above for details.
-__device__ inline int CudaAllSync(unsigned mask, int pred) {
- assert(mask & 1u << CudaLaneId());
-#if CUDA_VERSION >= 9000
- return __all_sync(mask, pred);
-#else
- return __all(pred);
-#endif
-}
-
-// Wrapper for __shfl_sync. All threads in 'mask' must call this function in
-// convergence, see comment above for details.
-template <typename T>
-__device__ T CudaShuffleSync(unsigned mask, T value, int src_lane,
- int width = warpSize) {
- assert(!(width & width - 1));
- assert(detail::CudaValidateShuffleSyncMask(
- mask, detail::CudaShuffleGetSrcLane(src_lane, width)));
-#if CUDA_VERSION >= 9000
- return __shfl_sync(mask, value, src_lane, width);
-#else
- return __shfl(value, src_lane, width);
-#endif
-}
-
-// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
-// instead of float for lo and hi (which is incorrect with ftz, for example).
-// See b/69446944.
-__device__ inline double CudaShuffleSync(unsigned mask, double value,
- int src_lane, int width = warpSize) {
- unsigned lo, hi;
- asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
- hi = CudaShuffleSync(mask, hi, src_lane, width);
- lo = CudaShuffleSync(mask, lo, src_lane, width);
- asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
- return value;
-}
-
-// Wrapper for __shfl_up_sync. All threads in 'mask' must call this function in
-// convergence, see comment above for details.
-template <typename T>
-__device__ inline T CudaShuffleUpSync(unsigned mask, T value, unsigned delta,
- int width = warpSize) {
- assert(!(width & width - 1));
- assert(detail::CudaValidateShuffleSyncMask(
- mask, detail::CudaShuffleUpGetSrcLane(delta, width)));
-#if CUDA_VERSION >= 9000
- return __shfl_up_sync(mask, value, delta, width);
-#else
- return __shfl_up(value, delta, width);
-#endif
-}
-
-// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
-// instead of float for lo and hi (which is incorrect with ftz, for example).
-// See b/69446944.
-__device__ inline double CudaShuffleUpSync(unsigned mask, double value,
- unsigned delta,
- int width = warpSize) {
- unsigned lo, hi;
- asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
- hi = CudaShuffleUpSync(mask, hi, delta, width);
- lo = CudaShuffleUpSync(mask, lo, delta, width);
- asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
- return value;
-}
-
-// Wrapper for __shfl_down_sync. All threads in 'mask' must call this function
-// in convergence, see comment above for details.
-template <typename T>
-__device__ inline T CudaShuffleDownSync(unsigned mask, T value, unsigned delta,
- int width = warpSize) {
- assert(!(width & width - 1));
- assert(detail::CudaValidateShuffleSyncMask(
- mask, detail::CudaShuffleDownGetSrcLane(delta, width)));
-#if CUDA_VERSION >= 9000
- return __shfl_down_sync(mask, value, delta, width);
-#else
- return __shfl_down(value, delta, width);
-#endif
-}
-
-// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
-// instead of float for lo and hi (which is incorrect with ftz, for example).
-// See b/69446944.
-__device__ inline double CudaShuffleDownSync(unsigned mask, double value,
- unsigned delta,
- int width = warpSize) {
- unsigned lo, hi;
- asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
- hi = CudaShuffleDownSync(mask, hi, delta, width);
- lo = CudaShuffleDownSync(mask, lo, delta, width);
- asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
- return value;
-}
-
-// Wrapper for __shfl_xor_sync. All threads in 'mask' must call this function in
-// convergence, see comment above for details.
-template <typename T>
-__device__ T CudaShuffleXorSync(unsigned mask, T value, int lane_mask,
- int width = warpSize) {
- assert(!(width & width - 1));
- assert(detail::CudaValidateShuffleSyncMask(
- mask, detail::CudaShuffleXorGetSrcLane(lane_mask, width)));
-#if CUDA_VERSION >= 9000
- return __shfl_xor_sync(mask, value, lane_mask, width);
-#else
- return __shfl_xor(value, lane_mask, width);
-#endif
-}
-
-// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
-// instead of float for lo and hi (which is incorrect with ftz, for example).
-// See b/69446944.
-__device__ inline double CudaShuffleXorSync(unsigned mask, double value,
- int lane_mask,
- int width = warpSize) {
- unsigned lo, hi;
- asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
- hi = CudaShuffleXorSync(mask, hi, lane_mask, width);
- lo = CudaShuffleXorSync(mask, lo, lane_mask, width);
- asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
- return value;
-}
-
-// Wrapper for __ldg.
-template <typename T>
-__host__ __device__ T CudaLdg(const T* address) {
-#if __CUDA_ARCH__ >= 350
- return __ldg(address);
-#else
- return *address;
-#endif
-}
-
-__host__ __device__ inline bool CudaLdg(const bool* address) {
- return CudaLdg(reinterpret_cast<const char*>(address)) != 0;
-}
-
-__host__ __device__ inline std::complex<float> CudaLdg(
- const std::complex<float>* address) {
-#if __CUDA_ARCH__ >= 350
- float2 mem = __ldg(reinterpret_cast<const float2*>(address));
- return std::complex<float>(mem.x, mem.y);
-#else
- return *address;
-#endif
-}
-
-__host__ __device__ inline std::complex<double> CudaLdg(
- const std::complex<double>* address) {
-#if __CUDA_ARCH__ >= 350
- double2 mem = __ldg(reinterpret_cast<const double2*>(address));
- return std::complex<double>(mem.x, mem.y);
-#else
- return *address;
-#endif
-}
-
-// Zeroes count elements starting at ptr using all threads of a 1-D grid.
-// Note: this function does not synchronize, and therefore the memory range is
-// not guaranteed to be zero until the next kernel launch.
-template <typename T>
-__global__ void SetZero(const int count, T* ptr) {
- // Check that the grid is one dimensional and index doesn't overflow.
- assert(blockDim.y == 1 && blockDim.z == 1);
- assert(blockDim.x * gridDim.x / blockDim.x == gridDim.x);
- for (int i : CudaGridRangeX(count)) {
- ptr[i] = T(0);
- }
-}
-
-namespace detail {
-// Helper function for atomic accumulation implemented as CAS.
-template <typename T, typename F>
-__device__ T CudaAtomicCasHelper(T* ptr, F accumulate) {
- T old = *ptr;
- T assumed;
- do {
- assumed = old;
- old = atomicCAS(ptr, assumed, accumulate(assumed));
- } while (assumed != old);
- return old;
-}
-
-// Overload for floating point (using integer comparison to handle NaN
-// correctly).
-template <typename F>
-__device__ float CudaAtomicCasHelper(float* ptr, F accumulate) {
- return __float_as_int(
- CudaAtomicCasHelper(reinterpret_cast<int32*>(ptr), [accumulate](int32 a) {
- return __float_as_int(accumulate(__int_as_float(a)));
- }));
-}
-template <typename F>
-__device__ double CudaAtomicCasHelper(double* ptr, F accumulate) {
- return __longlong_as_double(CudaAtomicCasHelper(
- reinterpret_cast<tensorflow::uint64*>(ptr),
- [accumulate](tensorflow::uint64 a) {
- return __double_as_longlong(accumulate(__longlong_as_double(a)));
- }));
-}
-
-template <typename From, typename To>
-using ToTypeIfConvertible =
- typename std::enable_if<std::is_convertible<From, To>::value, To>::type;
-
-} // namespace detail
-
-// CUDA provides atomic ops, but not for all types. We provide wrappers
-// for some ops and provide implementation for all reasonable types.
-
-template <typename T, typename U>
-__device__ detail::ToTypeIfConvertible<U, T> CudaAtomicAdd(T* ptr, U value) {
- return atomicAdd(ptr, value);
-}
-#if __CUDA_ARCH__ < 600
-__device__ inline double CudaAtomicAdd(double* ptr, double value) {
- return detail::CudaAtomicCasHelper(ptr,
- [value](double a) { return a + value; });
-}
-#elif __clang__
-// Clang cannot compile __nvvm_atom_add_gen_d builtin yet, use inline PTX.
-// see https://reviews.llvm.org/D39638
-__device__ inline double CudaAtomicAdd(double* ptr, double value) {
- double result;
- asm volatile("atom.add.f64 %0, [%1], %2;"
- : "=d"(result)
- : "l"(ptr), "d"(value)
- : "memory");
- return result;
-}
-#endif
-
-template <typename T, typename U>
-__device__ detail::ToTypeIfConvertible<U, T> CudaAtomicSub(T* ptr, U value) {
- return atomicSub(ptr, value);
-}
-// Specializations of substraction which add the negative value.
-__device__ inline float CudaAtomicSub(float* ptr, float value) {
- return CudaAtomicAdd(ptr, -value);
-}
-__device__ inline double CudaAtomicSub(double* ptr, double value) {
- return CudaAtomicAdd(ptr, -value);
-}
-__device__ inline tensorflow::uint64 CudaAtomicSub(tensorflow::uint64* ptr,
- tensorflow::uint64 value) {
- return CudaAtomicAdd(ptr, -value);
-}
-
-template <typename T, typename U>
-__device__ detail::ToTypeIfConvertible<U, T> CudaAtomicMax(T* ptr, U value) {
- return atomicMax(ptr, value);
-}
-#if __CUDA_ARCH__ < 320
-__device__ inline tensorflow::uint64 CudaAtomicMax(tensorflow::uint64* ptr,
- tensorflow::uint64 value) {
- return detail::CudaAtomicCasHelper(
- ptr, [value](tensorflow::uint64 a) { return max(a, value); });
-}
-#endif
-
-template <typename T, typename U>
-__device__ detail::ToTypeIfConvertible<U, T> CudaAtomicMul(T* ptr, U value) {
- return detail::CudaAtomicCasHelper(ptr, [value](T a) { return a * value; });
-}
-template <typename T, typename U>
-__device__ detail::ToTypeIfConvertible<U, T> CudaAtomicDiv(T* ptr, U value) {
- return detail::CudaAtomicCasHelper(ptr, [value](T a) { return a / value; });
-}
-
-} // namespace tensorflow
-
-#endif // GOOGLE_CUDA
-#endif // TENSORFLOW_CORE_UTIL_CUDA_KERNEL_HELPER_H_
diff --git a/tensorflow/core/util/cuda_kernel_helper.h b/tensorflow/core/util/cuda_kernel_helper.h
index 31bcbe91e9..3e32ec7973 100644
--- a/tensorflow/core/util/cuda_kernel_helper.h
+++ b/tensorflow/core/util/cuda_kernel_helper.h
@@ -18,132 +18,299 @@ limitations under the License.
#if GOOGLE_CUDA
-#include "tensorflow/core/util/cuda_device_functions.h"
-#include "tensorflow/core/util/cuda_launch_config.h"
+#include <algorithm>
-// Deprecated, use 'for(int i : CudaGridRangeX(n))' instead.
-#define CUDA_1D_KERNEL_LOOP(i, n) \
- for (int i : ::tensorflow::CudaGridRangeX<int>(n))
-// Deprecated, use 'for(int i : CudaGridRange?(n))' instead.
-#define CUDA_AXIS_KERNEL_LOOP(i, n, axis) \
- for (int i : ::tensorflow::CudaGridRange##axis<int>(n))
+#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
+#include "cuda/include/cuda.h"
+#include "tensorflow/core/framework/op_kernel.h"
+#include "tensorflow/core/platform/logging.h"
+#include "tensorflow/core/platform/stream_executor.h"
+#include "tensorflow/core/platform/types.h"
-namespace tensorflow {
-__host__ __device__ inline tensorflow::bfloat16 CudaLdg(
- const tensorflow::bfloat16* address) {
- tensorflow::bfloat16 return_value;
- return_value.value = CudaLdg(reinterpret_cast<const uint16_t*>(address));
- return return_value;
-}
+// Mask for all 32 threads in a warp.
+#define CUDA_WARP_ALL 0xFFFFFFFF
-template <typename T>
-__host__ __device__ inline T ldg(const T* ptr) {
- return CudaLdg(ptr);
-}
+#if defined(CUDA_VERSION) && CUDA_VERSION < 9000
+// CUDA 9.0 introduces a new, light-weight barrier synchronization primitive
+// that operates at the warp-scope. This is required to ensure visibility of
+// reads/writes among threads that can make indepenent progress on Volta.
+// For previous CUDA versions these synchronizations not necessary, and we
+// define an empty function as a convenience for backward compatibility.
+__device__ inline void __syncwarp(unsigned mask = CUDA_WARP_ALL) {}
-template <typename T>
-__host__ __device__ inline const T& tf_min(const T& x, const T& y) {
- return x < y ? x : y;
-}
+// CUDA 9.0 deprecates the warp-intrinsic functions (shfl, ballot, etc.) in
+// favor of synchronizing versions. These ensure that all warp lanes specified
+// in mask execute the intrinsic in convergence. Here we provide legacy mappings
+// to the less-verbose routines provided in previous versions of CUDA.
+#define __ballot_sync(mask, predicate) __ballot(predicate)
+#define __shfl_sync(mask, val, srcLane, width) __shfl(val, srcLane, width)
+#define __shfl_down_sync(mask, val, delta, width) __shfl_down(val, delta, width)
+#define __shfl_up_sync(mask, val, delta, width) __shfl_up(val, delta, width)
+#define __shfl_xor_sync(mask, val, laneMask, width) \
+ __shfl_xor(val, laneMask, width)
+#endif
-template <typename T>
-__host__ __device__ inline const T& tf_max(const T& x, const T& y) {
- return x < y ? y : x;
-}
+// Usage of GetCudaLaunchConfig, GetCuda2DLaunchConfig, and
+// GetCuda3DLaunchConfig:
+//
+// There are two versions of GetCudaLaunchConfig and GetCuda2DLaunchConfig, one
+// version uses heuristics without any knowledge of the device kernel, the other
+// version uses cudaOccupancyMaxPotentialBlockSize to determine the theoretical
+// launch parameters that maximize occupancy. Currently, only the maximum
+// occupancy version of GetCuda3DLaunchConfig is available.
+//
+// For large number of work elements, the convention is that each kernel would
+// iterate through its assigned range. The return value of GetCudaLaunchConfig
+// is struct CudaLaunchConfig, which contains all the information needed for the
+// kernel launch, including: virtual number of threads, the number of threads
+// per block and number of threads per block used inside <<< >>> of a kernel
+// launch. GetCuda2DLaunchConfig and GetCuda3DLaunchConfig does the same thing
+// as CudaLaunchConfig. The only difference is the dimension. The macros
+// CUDA_1D_KERNEL_LOOP and CUDA_AXIS_KERNEL_LOOP might be used to do inner loop.
+//
+/* Sample code:
-// Overloads of the above functions for float and double.
-__host__ __device__ inline float tf_min(float x, float y) {
- return fminf(x, y);
-}
-__host__ __device__ inline double tf_min(double x, double y) {
- return fmin(x, y);
+__global__ void MyKernel1D(CudaLaunchConfig config, other_args...) {
+ CUDA_1D_KERNEL_LOOP(x, config.virtual_thread_count) {
+ do_your_job_here;
+ }
}
-__host__ __device__ inline float tf_max(float x, float y) {
- return fmaxf(x, y);
+
+__global__ void MyKernel2D(Cuda2DLaunchConfig config, other_args...) {
+ CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) {
+ CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) {
+ do_your_job_here;
+ }
+ }
}
-__host__ __device__ inline double tf_max(double x, double y) {
- return fmax(x, y);
+
+__global__ void MyKernel3D(Cuda3DLaunchConfig config, other_args...) {
+ CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) {
+ CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) {
+ CUDA_AXIS_KERNEL_LOOP(z, config.virtual_thread_count, z) {
+ do_your_job_here;
+ }
+ }
+ }
}
-__device__ inline Eigen::half CudaShuffleSync(unsigned mask, Eigen::half value,
- int src_lane,
- int width = warpSize) {
- return Eigen::half(
- CudaShuffleSync(mask, static_cast<uint16>(value), src_lane, width));
+void MyDriverFunc(const GPUDevice &d) {
+ // use heuristics
+ CudaLaunchConfig cfg1 = GetCudaLaunchConfig(10240, d);
+ MyKernel1D <<<config.block_count,
+ config.thread_per_block, 0, d.stream()>>> (cfg1, other_args...);
+ Cuda2DLaunchConfig cfg2 = GetCuda2DLaunchConfig(10240, 10240, d);
+ MyKernel2D <<<config.block_count,
+ config.thread_per_block, 0, d.stream()>>> (cfg2, other_args...);
+ Cuda3DLaunchConfig cfg3 = GetCuda3DLaunchConfig(4096, 4096, 100, d);
+ MyKernel3D <<<config.block_count,
+ config.thread_per_block, 0, d.stream()>>> (cfg3, other_args...);
+
+ // maximize occupancy
+ CudaLaunchConfig cfg4 = GetCudaLaunchConfig(10240, d, MyKernel1D, 0, 0 );
+ MyKernel1D <<<config.block_count,
+ config.thread_per_block, 0, d.stream()>>> (cfg4, other_args...);
+ Cuda2DLaunchConfig cfg5 = GetCuda2DLaunchConfig(10240, 10240, d,
+ MyKernel1D, 0, 0);
+ MyKernel2D <<<config.block_count,
+ config.thread_per_block, 0, d.stream()>>> (cfg5, other_args...);
+ Cuda3DLaunchConfig cfg6 = GetCuda3DLaunchConfig(4096, 4096, 100, d,
+ MyKernel1D, 0, 0);
+ MyKernel3D <<<config.block_count,
+ config.thread_per_block, 0, d.stream()>>> (cfg6, other_args...);
}
-__device__ EIGEN_ALWAYS_INLINE Eigen::half CudaShuffleUpSync(
- unsigned mask, Eigen::half value, int delta, int width = warpSize) {
- return Eigen::half(
- CudaShuffleUpSync(mask, static_cast<uint16>(value), delta, width));
+// See the test for this for more example:
+//
+https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/util/cuda_kernel_helper_test.cu.cc
+
+*/
+
+#define CUDA_1D_KERNEL_LOOP(i, n) \
+ for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; \
+ i += blockDim.x * gridDim.x)
+
+#define CUDA_AXIS_KERNEL_LOOP(i, n, axis) \
+ for (int i = blockIdx.axis * blockDim.axis + threadIdx.axis; i < n.axis; \
+ i += blockDim.axis * gridDim.axis)
+
+#define DIV_UP(a, b) (((a) + (b)-1) / (b))
+
+namespace tensorflow {
+
+typedef Eigen::GpuDevice GPUDevice;
+
+struct CudaLaunchConfig {
+ // Logical number of thread that works on the elements. If each logical
+ // thread works on exactly a single element, this is the same as the working
+ // element count.
+ int virtual_thread_count = -1;
+ // Number of threads per block.
+ int thread_per_block = -1;
+ // Number of blocks for Cuda kernel launch.
+ int block_count = -1;
+};
+
+// Calculate the Cuda launch config we should use for a kernel launch.
+// This is assuming the kernel is quite simple and will largely be
+// memory-limited.
+// REQUIRES: work_element_count > 0.
+inline CudaLaunchConfig GetCudaLaunchConfig(int work_element_count,
+ const GPUDevice& d) {
+ CHECK_GT(work_element_count, 0);
+ CudaLaunchConfig config;
+ const int virtual_thread_count = work_element_count;
+ const int physical_thread_count = std::min(
+ d.getNumCudaMultiProcessors() * d.maxCudaThreadsPerMultiProcessor(),
+ virtual_thread_count);
+ const int thread_per_block = std::min(1024, d.maxCudaThreadsPerBlock());
+ const int block_count =
+ std::min(DIV_UP(physical_thread_count, thread_per_block),
+ d.getNumCudaMultiProcessors());
+
+ config.virtual_thread_count = virtual_thread_count;
+ config.thread_per_block = thread_per_block;
+ config.block_count = block_count;
+ return config;
}
-__device__ EIGEN_ALWAYS_INLINE Eigen::half CudaShuffleDownSync(
- unsigned mask, Eigen::half value, int delta, int width = warpSize) {
- return Eigen::half(
- CudaShuffleDownSync(mask, static_cast<uint16>(value), delta, width));
+// Calculate the Cuda launch config we should use for a kernel launch. This
+// variant takes the resource limits of func into account to maximize occupancy.
+// REQUIRES: work_element_count > 0.
+template <typename DeviceFunc>
+inline CudaLaunchConfig GetCudaLaunchConfig(int work_element_count,
+ const GPUDevice& d, DeviceFunc func,
+ size_t dynamic_shared_memory_size,
+ int block_size_limit) {
+ CHECK_GT(work_element_count, 0);
+ CudaLaunchConfig config;
+ int block_count = 0;
+ int thread_per_block = 0;
+
+ cudaError_t err = cudaOccupancyMaxPotentialBlockSize(
+ &block_count, &thread_per_block, func, dynamic_shared_memory_size,
+ block_size_limit);
+ CHECK_EQ(err, cudaSuccess);
+
+ block_count =
+ std::min(block_count, DIV_UP(work_element_count, thread_per_block));
+
+ config.virtual_thread_count = work_element_count;
+ config.thread_per_block = thread_per_block;
+ config.block_count = block_count;
+ return config;
}
-__device__ EIGEN_ALWAYS_INLINE Eigen::half CudaShuffleXorSync(
- unsigned mask, Eigen::half value, int lane_mask, int width = warpSize) {
- return Eigen::half(
- CudaShuffleXorSync(mask, static_cast<uint16>(value), lane_mask, width));
+struct Cuda2DLaunchConfig {
+ dim3 virtual_thread_count = dim3(0, 0, 0);
+ dim3 thread_per_block = dim3(0, 0, 0);
+ dim3 block_count = dim3(0, 0, 0);
+};
+
+inline Cuda2DLaunchConfig GetCuda2DLaunchConfig(int xdim, int ydim,
+ const GPUDevice& d) {
+ Cuda2DLaunchConfig config;
+
+ if (xdim <= 0 || ydim <= 0) {
+ return config;
+ }
+
+ const int kThreadsPerBlock = 256;
+ int block_cols = std::min(xdim, kThreadsPerBlock);
+ // ok to round down here and just do more loops in the kernel
+ int block_rows = std::max(kThreadsPerBlock / block_cols, 1);
+
+ const int physical_thread_count =
+ d.getNumCudaMultiProcessors() * d.maxCudaThreadsPerMultiProcessor();
+
+ const int max_blocks = std::max(physical_thread_count / kThreadsPerBlock, 1);
+
+ config.virtual_thread_count = dim3(xdim, ydim, 1);
+ config.thread_per_block = dim3(block_cols, block_rows, 1);
+
+ int grid_x = std::min(DIV_UP(xdim, block_cols), max_blocks);
+
+ config.block_count = dim3(
+ grid_x, std::min(max_blocks / grid_x, std::max(ydim / block_rows, 1)), 1);
+ return config;
}
-namespace detail {
-// Overload of above function for half. Note that we don't have
-// atomicCAS() for anything less than 32 bits, so we need to include the
-// other 16 bits in the operation.
-//
-// This version is going to be very slow
-// under high concurrency, since most threads will be spinning on failing
-// their compare-and-swap tests. (The fact that we get false sharing on the
-// neighboring fp16 makes this even worse.) If you are doing a large reduction,
-// you are much better off with doing the intermediate steps in fp32 and then
-// switching to fp16 as late as you can in the calculations.
-//
-// Note: Assumes little endian.
-template <typename F>
-__device__ Eigen::half CudaAtomicCasHelper(Eigen::half* ptr, F accumulate) {
- namespace half_impl = Eigen::half_impl;
- intptr_t intptr = reinterpret_cast<intptr_t>(ptr);
- if (intptr & 0x3) {
- assert(!(intptr & 0x1));
- // The half is in the second part of the uint32 (upper 16 bits).
- uint32* address = reinterpret_cast<uint32*>(intptr - 2);
- uint32 result = CudaAtomicCasHelper(address, [accumulate](uint32 a) {
- Eigen::half acc = accumulate(
- half_impl::__half_raw{static_cast<unsigned short>(a >> 16)});
- uint32_t upper = static_cast<half_impl::__half_raw>(acc).x;
- return (upper << 16) | (a & 0xffff);
- });
- return half_impl::__half_raw{static_cast<uint16>(result >> 16)};
- } else {
- // The half is in the first part of the uint32 (lower 16 bits).
- uint32* address = reinterpret_cast<uint32*>(intptr);
- uint32 result = CudaAtomicCasHelper(address, [accumulate](uint32 a) {
- Eigen::half acc = accumulate(
- half_impl::__half_raw{static_cast<unsigned short>(a & 0xffff)});
- uint32_t lower = static_cast<half_impl::__half_raw>(acc).x;
- return (a & 0xffff0000) | lower;
- });
- return half_impl::__half_raw{static_cast<uint16>(result & 0xffff)};
+// Calculate the Cuda 2D and 3D launch config we should use for a kernel launch.
+// This variant takes the resource limits of func into account to maximize
+// occupancy.
+using Cuda3DLaunchConfig = Cuda2DLaunchConfig;
+
+template <typename DeviceFunc>
+inline Cuda3DLaunchConfig GetCuda3DLaunchConfig(
+ int xdim, int ydim, int zdim, const GPUDevice& d, DeviceFunc func,
+ size_t dynamic_shared_memory_size, int block_size_limit) {
+ Cuda3DLaunchConfig config;
+
+ if (xdim <= 0 || ydim <= 0 || zdim <= 0) {
+ return config;
}
+
+ int dev;
+ cudaGetDevice(&dev);
+ cudaDeviceProp deviceProp;
+ cudaGetDeviceProperties(&deviceProp, dev);
+ int xthreadlimit = deviceProp.maxThreadsDim[0];
+ int ythreadlimit = deviceProp.maxThreadsDim[1];
+ int zthreadlimit = deviceProp.maxThreadsDim[2];
+ int xgridlimit = deviceProp.maxGridSize[0];
+ int ygridlimit = deviceProp.maxGridSize[1];
+ int zgridlimit = deviceProp.maxGridSize[2];
+
+ int block_count = 0;
+ int thread_per_block = 0;
+ cudaError_t err = cudaOccupancyMaxPotentialBlockSize(
+ &block_count, &thread_per_block, func, dynamic_shared_memory_size,
+ block_size_limit);
+ CHECK_EQ(err, cudaSuccess);
+
+#define MIN3(a, b, c) std::min((a), std::min((b), (c)))
+ int threadsx = MIN3(xdim, thread_per_block, xthreadlimit);
+ int threadsy =
+ MIN3(ydim, std::max(thread_per_block / threadsx, 1), ythreadlimit);
+ int threadsz =
+ MIN3(zdim, std::max(thread_per_block / (threadsx * threadsy), 1),
+ zthreadlimit);
+
+ int blocksx = MIN3(block_count, DIV_UP(xdim, threadsx), xgridlimit);
+ int blocksy =
+ MIN3(DIV_UP(block_count, blocksx), DIV_UP(ydim, threadsy), ygridlimit);
+ int blocksz = MIN3(DIV_UP(block_count, (blocksx * blocksy)),
+ DIV_UP(zdim, threadsz), zgridlimit);
+#undef MIN3
+
+ config.virtual_thread_count = dim3(xdim, ydim, zdim);
+ config.thread_per_block = dim3(threadsx, threadsy, threadsz);
+ config.block_count = dim3(blocksx, blocksy, blocksz);
+ return config;
}
-} // namespace detail
-__device__ inline Eigen::half CudaAtomicAdd(Eigen::half* ptr,
- Eigen::half value) {
- return detail::CudaAtomicCasHelper(
- ptr, [value](Eigen::half a) { return a + value; });
+template <typename DeviceFunc>
+inline Cuda2DLaunchConfig GetCuda2DLaunchConfig(
+ int xdim, int ydim, const GPUDevice& d, DeviceFunc func,
+ size_t dynamic_shared_memory_size, int block_size_limit) {
+ return GetCuda3DLaunchConfig(xdim, ydim, 1, d, func,
+ dynamic_shared_memory_size, block_size_limit);
}
-__device__ inline Eigen::half CudaAtomicSub(Eigen::half* ptr,
- Eigen::half value) {
- return detail::CudaAtomicCasHelper(
- ptr, [value](Eigen::half a) { return a - value; });
+
+// Returns a raw reference to the current cuda stream. Required by a
+// number of kernel calls (for which StreamInterface* does not work), i.e.
+// CUB and certain cublas primitives.
+inline const cudaStream_t& GetCudaStream(OpKernelContext* context) {
+ const cudaStream_t* ptr = CHECK_NOTNULL(
+ reinterpret_cast<const cudaStream_t*>(context->op_device_context()
+ ->stream()
+ ->implementation()
+ ->CudaStreamMemberHack()));
+ return *ptr;
}
namespace cuda_helper {
+
template <typename IntType>
__device__ IntType upper_bound(IntType* first, IntType count, IntType val) {
IntType* orig = first;
@@ -163,8 +330,495 @@ __device__ IntType upper_bound(IntType* first, IntType count, IntType val) {
return first - orig;
}
+
} // namespace cuda_helper
+
+template <typename T>
+__device__ __host__ inline T ldg(const T* address) {
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+ return __ldg(address);
+#else
+ return *address;
+#endif
+}
+
+template <>
+__device__ __host__ inline std::complex<float> ldg(
+ const std::complex<float>* address) {
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+ float2 mem = __ldg(reinterpret_cast<const float2*>(address));
+ return std::complex<float>(mem.x, mem.y);
+#else
+ return *address;
+#endif
+}
+
+template <>
+__device__ __host__ inline std::complex<double> ldg(
+ const std::complex<double>* address) {
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+ double2 mem = __ldg(reinterpret_cast<const double2*>(address));
+ return std::complex<double>(mem.x, mem.y);
+#else
+ return *address;
+#endif
+}
+
+template <>
+__device__ __host__ inline Eigen::half ldg(const Eigen::half* address) {
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+ return Eigen::half_impl::raw_uint16_to_half(
+ __ldg(reinterpret_cast<const uint16_t*>(address)));
+#else
+ return *address;
+#endif
+}
+
+template <>
+__device__ __host__ inline tensorflow::bfloat16 ldg(
+ const tensorflow::bfloat16* address) {
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+ tensorflow::bfloat16 return_value;
+ asm volatile("ld.global.nc.u16 %0, [%1];"
+ : "=h"(return_value.value)
+ : "l"(address));
+ return return_value;
+#else
+ return *address;
+#endif
+}
+
+template <>
+__device__ __host__ inline bool ldg(const bool* address) {
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+ return *reinterpret_cast<const bool*>(
+ __ldg(reinterpret_cast<const char*>(address)));
+#else
+ return *address;
+#endif
+}
+
+// CUDA provides atomic ops, but not for all types. We provide wrappers
+// for some ops and provide implementation for all reasonable types.
+#define CUDA_ATOMIC_WRAPPER(op, T) \
+ __device__ __forceinline__ T CudaAtomic##op(T* address, T val)
+
+#define USE_CUDA_ATOMIC(op, T) \
+ CUDA_ATOMIC_WRAPPER(op, T) { return atomic##op(address, val); }
+
+// For atomicAdd.
+USE_CUDA_ATOMIC(Add, int32);
+USE_CUDA_ATOMIC(Add, uint32);
+USE_CUDA_ATOMIC(Add, uint64);
+USE_CUDA_ATOMIC(Add, float);
+
+// For atomicMax.
+USE_CUDA_ATOMIC(Max, int32);
+USE_CUDA_ATOMIC(Max, uint32);
+#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 350
+USE_CUDA_ATOMIC(Max, uint64);
+#else
+// The uint64 overload of atomicMax() is only available for __CUDA_ARCH__ >=
+// 350. If not satisfied, we provide a custom implementation using atomicCAS().
+CUDA_ATOMIC_WRAPPER(Max, uint64) {
+ uint64* address_as_ull = reinterpret_cast<uint64*>(address);
+ uint64 old = *address_as_ull, assumed;
+
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_ull, assumed, max(val, assumed));
+ } while (assumed != old);
+
+ return old;
+}
+#endif
+
+// Custom implementation of atomicAdd for double.
+// This implementation is copied from CUDA manual.
+CUDA_ATOMIC_WRAPPER(Add, double) {
+ uint64* address_as_ull = reinterpret_cast<uint64*>(address);
+ uint64 old = *address_as_ull, assumed;
+
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_ull, assumed,
+ __double_as_longlong(val + __longlong_as_double(assumed)));
+
+ // Note: uses integer comparison to avoid hang in case of NaN
+ } while (assumed != old);
+
+ return __longlong_as_double(old);
+}
+
+// Custom implementation of atomicAdd for std::complex<float>.
+// This implementation performs to atomic additions on the components.
+CUDA_ATOMIC_WRAPPER(Add, std::complex<float>) {
+#if defined(__CUDA_ARCH__)
+#if __CUDA_ARCH__ >= 350
+ float2* addr_as_float2 = reinterpret_cast<float2*>(address);
+ float2* val_as_float2 = reinterpret_cast<float2*>(&val);
+ CudaAtomicAdd(&(addr_as_float2->x), val_as_float2->x);
+ CudaAtomicAdd(&(addr_as_float2->y), val_as_float2->y);
+#else
+ static_assert(sizeof(std::complex<float>) == 2 * sizeof(float),
+ "Unable to compile CudaAtomicAdd for complex64 because "
+ "sizeof(complex64) != 2*sizeof(float32)");
+ float* addr_as_float = reinterpret_cast<float*>(address);
+ float* val_as_float = reinterpret_cast<float*>(&val);
+ CudaAtomicAdd(addr_as_float, *val_as_float);
+ CudaAtomicAdd(addr_as_float + 1, *(val_as_float + 1));
+#endif
+#endif
+ return *address;
+}
+
+// Custom implementation of atomicAdd for std::complex<double>.
+// This implementation performs to atomic additions on the components
+// using the double atomic wrapper above.
+CUDA_ATOMIC_WRAPPER(Add, complex128) {
+#if defined(__CUDA_ARCH__)
+#if __CUDA_ARCH__ >= 350
+ double2* addr_as_double2 = reinterpret_cast<double2*>(address);
+ double2* val_as_double2 = reinterpret_cast<double2*>(&val);
+ CudaAtomicAdd(&(addr_as_double2->x), val_as_double2->x);
+ CudaAtomicAdd(&(addr_as_double2->y), val_as_double2->y);
+#else
+ static_assert(sizeof(std::complex<double>) == 2 * sizeof(double),
+ "Unable to compile CudaAtomicAdd for complex128 because "
+ "sizeof(complex128) != 2*sizeof(float64)");
+ double* addr_as_double = reinterpret_cast<double*>(address);
+ double* val_as_double = reinterpret_cast<double*>(&val);
+ CudaAtomicAdd(addr_as_double, *val_as_double);
+ CudaAtomicAdd(addr_as_double + 1, *(val_as_double + 1));
+#endif
+#endif
+ return *address;
+}
+
+// Helper functions for CudaAtomicAdd(half*, half), below.
+//
+// Note that if __CUDA_ARCH__ >= 530, we could probably use __hadd2()
+// for a more efficient implementation, assuming that adding -0.0
+// will never harm the neighboring value. In this version, we take special
+// care to guarantee the bits of the untouched value are unchanged.
+inline __device__ uint32 add_to_low_half(uint32 val, float x) {
+ Eigen::half low_half;
+ low_half.x = static_cast<uint16>(val & 0xffffu);
+ low_half = static_cast<Eigen::half>(static_cast<float>(low_half) + x);
+ return (val & 0xffff0000u) | low_half.x;
+}
+
+inline __device__ uint32 add_to_high_half(uint32 val, float x) {
+ Eigen::half high_half;
+ high_half.x = static_cast<uint16>(val >> 16);
+ high_half = static_cast<Eigen::half>(static_cast<float>(high_half) + x);
+ return (val & 0xffffu) | (high_half.x << 16);
+}
+
+// Custom implementation of atomicAdd for half. Note that we don't have
+// atomicCAS() for anything less than 32 bits, so we need to include the
+// other 16 bits in the operation.
+//
+// Unlike the other atomic adds, this version is going to be very slow
+// under high concurrency, since most threads will be spinning on failing
+// their compare-and-swap tests. (The fact that we get false sharing on the
+// neighboring fp16 makes this even worse.) If you are doing a large reduction,
+// you are much better off with doing the intermediate steps in fp32 and then
+// switching to fp16 as late as you can in the calculations.
+//
+// Note: Assumes little endian.
+CUDA_ATOMIC_WRAPPER(Add, Eigen::half) {
+ float val_as_float(val);
+ intptr_t address_int = reinterpret_cast<intptr_t>(address);
+ if ((address_int & 0x2) == 0) {
+ // The half is in the first part of the uint32 (lower 16 bits).
+ uint32* address_as_uint32 = reinterpret_cast<uint32*>(address);
+ assert(((intptr_t)address_as_uint32 & 0x3) == 0);
+ uint32 old = *address_as_uint32, assumed;
+
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_uint32, assumed,
+ add_to_low_half(assumed, val_as_float));
+
+ // Note: uses integer comparison to avoid hang in case of NaN
+ } while (assumed != old);
+
+ Eigen::half ret;
+ ret.x = old & 0xffffu;
+ return ret;
+ } else {
+ // The half is in the second part of the uint32 (upper 16 bits).
+ uint32* address_as_uint32 = reinterpret_cast<uint32*>(address_int - 2);
+ assert(((intptr_t)address_as_uint32 & 0x3) == 0);
+ uint32 old = *address_as_uint32, assumed;
+
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_uint32, assumed,
+ add_to_high_half(assumed, val_as_float));
+
+ // Note: uses integer comparison to avoid hang in case of NaN
+ } while (assumed != old);
+
+ Eigen::half ret;
+ ret.x = old >> 16;
+ return ret;
+ }
+}
+
+template <typename T>
+__global__ void SetZero(const int nthreads, T* bottom_diff) {
+ CUDA_1D_KERNEL_LOOP(index, nthreads) { *(bottom_diff + index) = T(0); }
+}
+
+// For atomicSub.
+
+// Custom implementation for sub by just negating the value.
+#define WRAPPED_ATOMIC_SUB(T) \
+ CUDA_ATOMIC_WRAPPER(Sub, T) { return CudaAtomicAdd(address, -val); }
+
+WRAPPED_ATOMIC_SUB(uint64);
+WRAPPED_ATOMIC_SUB(int32);
+WRAPPED_ATOMIC_SUB(uint32);
+WRAPPED_ATOMIC_SUB(Eigen::half);
+WRAPPED_ATOMIC_SUB(float);
+WRAPPED_ATOMIC_SUB(double);
+
+CUDA_ATOMIC_WRAPPER(Sub, complex64) {
+ const std::complex<float> Tneg(-val.real(), -val.imag());
+ return CudaAtomicAdd(address, Tneg);
+}
+
+CUDA_ATOMIC_WRAPPER(Sub, complex128) {
+ const std::complex<double> Tneg(-val.real(), -val.imag());
+ return CudaAtomicAdd(address, Tneg);
+}
+
+#undef WRAPPED_ATOMIC_SUB
+
+// For atomicMul.
+CUDA_ATOMIC_WRAPPER(Mul, int32) {
+ int32 old = *address, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address, assumed, val * assumed);
+ } while (assumed != old);
+ return old;
+}
+
+CUDA_ATOMIC_WRAPPER(Mul, uint32) {
+ uint32 old = *address, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address, assumed, val * assumed);
+ } while (assumed != old);
+ return old;
+}
+
+CUDA_ATOMIC_WRAPPER(Mul, uint64) {
+ uint64 old = *address, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address, assumed, val * assumed);
+ } while (assumed != old);
+ return old;
+}
+
+CUDA_ATOMIC_WRAPPER(Mul, float) {
+ int32* address_as_int = reinterpret_cast<int32*>(address);
+ int32 old = *address_as_int, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_int, assumed,
+ __float_as_int(val * __int_as_float(assumed)));
+ } while (assumed != old);
+ return __int_as_float(old);
+}
+
+CUDA_ATOMIC_WRAPPER(Mul, double) {
+ uint64* address_as_ull = reinterpret_cast<uint64*>(address);
+ uint64 old = *address_as_ull, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_ull, assumed,
+ __double_as_longlong(val * __longlong_as_double(assumed)));
+ } while (assumed != old);
+ return __longlong_as_double(old);
+}
+
+// For atomicDiv.
+CUDA_ATOMIC_WRAPPER(Div, int32) {
+ int32 old = *address, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address, assumed, assumed / val);
+ } while (assumed != old);
+ return old;
+}
+
+CUDA_ATOMIC_WRAPPER(Div, uint32) {
+ uint32 old = *address, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address, assumed, assumed / val);
+ } while (assumed != old);
+ return old;
+}
+
+CUDA_ATOMIC_WRAPPER(Div, uint64) {
+ uint64 old = *address, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address, assumed, assumed / val);
+ } while (assumed != old);
+ return old;
+}
+
+CUDA_ATOMIC_WRAPPER(Div, float) {
+ int32* address_as_int = reinterpret_cast<int32*>(address);
+ int32 old = *address_as_int, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_int, assumed,
+ __float_as_int(__int_as_float(assumed) / val));
+ } while (assumed != old);
+ return __int_as_float(old);
+}
+
+CUDA_ATOMIC_WRAPPER(Div, double) {
+ uint64* address_as_ull = reinterpret_cast<uint64*>(address);
+ uint64 old = *address_as_ull, assumed;
+ do {
+ assumed = old;
+ old = atomicCAS(address_as_ull, assumed,
+ __double_as_longlong(__longlong_as_double(assumed) / val));
+ } while (assumed != old);
+ return __longlong_as_double(old);
+}
+
+#undef USE_CUDA_ATOMIC
+#undef CUDA_ATOMIC_WRAPPER
+
+template <typename T>
+EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE T tf_min(const T& x, const T& y) {
+ return x > y ? y : x;
+}
+
+template <typename T>
+EIGEN_DEVICE_FUNC EIGEN_ALWAYS_INLINE T tf_max(const T& x, const T& y) {
+ return x < y ? y : x;
+}
+
+__device__ EIGEN_ALWAYS_INLINE unsigned CudaBallot(unsigned mask,
+ int predicate) {
+ return __ballot_sync(mask, predicate);
+}
+
+template <typename T>
+__device__ EIGEN_ALWAYS_INLINE T CudaShuffle(unsigned mask, T value,
+ int srcLane,
+ int width = warpSize) {
+ return __shfl_sync(mask, value, srcLane, width);
+}
+
+// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
+// instead of float for lo and hi (which is incorrect with ftz, for example).
+// A bug has been filed with NVIDIA and will be fixed in the next CUDA release.
+// TODO(csigg): remove when the bug is fixed in the next CUDA release.
+__device__ EIGEN_ALWAYS_INLINE double CudaShuffle(unsigned mask, double value,
+ int srcLane,
+ int width = warpSize) {
+ unsigned lo, hi;
+ asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
+ hi = __shfl_sync(mask, hi, srcLane, width);
+ lo = __shfl_sync(mask, lo, srcLane, width);
+ asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
+ return value;
+}
+
+template <typename T>
+__device__ EIGEN_ALWAYS_INLINE T CudaShuffleUp(unsigned mask, T value,
+ int delta,
+ int width = warpSize) {
+ return __shfl_up_sync(mask, value, delta, width);
+}
+
+// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
+// instead of float for lo and hi (which is incorrect with ftz, for example).
+// A bug has been filed with NVIDIA and will be fixed in the next CUDA release.
+// TODO(csigg): remove when the bug is fixed in the next CUDA release.
+__device__ EIGEN_ALWAYS_INLINE double CudaShuffleUp(unsigned mask, double value,
+ int delta,
+ int width = warpSize) {
+ unsigned lo, hi;
+ asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
+ hi = __shfl_up_sync(mask, hi, delta, width);
+ lo = __shfl_up_sync(mask, lo, delta, width);
+ asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
+ return value;
+}
+
+template <typename T>
+__device__ EIGEN_ALWAYS_INLINE T CudaShuffleDown(unsigned mask, T value,
+ int delta,
+ int width = warpSize) {
+ return __shfl_down_sync(mask, value, delta, width);
+}
+
+__device__ EIGEN_ALWAYS_INLINE Eigen::half CudaShuffleDown(
+ unsigned mask, Eigen::half value, int delta, int width = warpSize) {
+ return Eigen::half(
+ __shfl_down_sync(mask, static_cast<uint16>(value), delta, width));
+}
+
+// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
+// instead of float for lo and hi (which is incorrect with ftz, for example).
+// A bug has been filed with NVIDIA and will be fixed in the next CUDA release.
+// TODO(csigg): remove when the bug is fixed in the next CUDA release.
+__device__ EIGEN_ALWAYS_INLINE double CudaShuffleDown(unsigned mask,
+ double value, int delta,
+ int width = warpSize) {
+ unsigned lo, hi;
+ asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
+ hi = __shfl_down_sync(mask, hi, delta, width);
+ lo = __shfl_down_sync(mask, lo, delta, width);
+ asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
+ return value;
+}
+
+template <typename T>
+__device__ EIGEN_ALWAYS_INLINE T CudaShuffleXor(unsigned mask, T value,
+ int laneMask,
+ int width = warpSize) {
+ return __shfl_xor_sync(mask, value, laneMask, width);
+}
+
+__device__ EIGEN_ALWAYS_INLINE Eigen::half CudaShuffleXor(
+ unsigned mask, Eigen::half value, int laneMask, int width = warpSize) {
+ return Eigen::half(
+ __shfl_xor_sync(mask, static_cast<uint16>(value), laneMask, width));
+}
+
+// Variant of the (undocumented) version from the CUDA SDK, but using unsigned
+// instead of float for lo and hi (which is incorrect with ftz, for example).
+// A bug has been filed with NVIDIA and will be fixed in the next CUDA release.
+// TODO(csigg): remove when the bug is fixed in the next CUDA release.
+__device__ EIGEN_ALWAYS_INLINE double CudaShuffleXor(unsigned mask,
+ double value, int laneMask,
+ int width = warpSize) {
+ unsigned lo, hi;
+ asm volatile("mov.b64 {%0,%1}, %2;" : "=r"(lo), "=r"(hi) : "d"(value));
+ hi = __shfl_xor_sync(mask, hi, laneMask, width);
+ lo = __shfl_xor_sync(mask, lo, laneMask, width);
+ asm volatile("mov.b64 %0, {%1,%2};" : "=d"(value) : "r"(lo), "r"(hi));
+ return value;
+}
+
} // namespace tensorflow
+#undef DIV_UP
+
#endif // GOOGLE_CUDA
+
#endif // TENSORFLOW_CORE_UTIL_CUDA_KERNEL_HELPER_H_
diff --git a/tensorflow/core/util/cuda_kernel_helper_test.cu.cc b/tensorflow/core/util/cuda_kernel_helper_test.cu.cc
index bd4c356ea0..6991554eff 100644
--- a/tensorflow/core/util/cuda_kernel_helper_test.cu.cc
+++ b/tensorflow/core/util/cuda_kernel_helper_test.cu.cc
@@ -52,11 +52,11 @@ __global__ void Count1D(CudaLaunchConfig config, int bufsize, int* outbuf) {
}
}
__global__ void Count2D(Cuda2DLaunchConfig config, int bufsize, int* outbuf) {
- CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count.x, X) {
+ CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) {
if (x < 0) { // x might overflow when testing extreme case
break;
}
- CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count.y, Y) {
+ CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) {
if (y < 0) { // y might overflow when testing extreme case
break;
}
@@ -66,15 +66,15 @@ __global__ void Count2D(Cuda2DLaunchConfig config, int bufsize, int* outbuf) {
}
}
__global__ void Count3D(Cuda3DLaunchConfig config, int bufsize, int* outbuf) {
- CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count.x, X) {
+ CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) {
if (x < 0) { // x might overflow when testing extreme case
break;
}
- CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count.y, Y) {
+ CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) {
if (y < 0) { // y might overflow when testing extreme case
break;
}
- CUDA_AXIS_KERNEL_LOOP(z, config.virtual_thread_count.z, Z) {
+ CUDA_AXIS_KERNEL_LOOP(z, config.virtual_thread_count, z) {
if (z < 0) { // z might overflow when testing extreme case
break;
}
@@ -87,44 +87,6 @@ __global__ void Count3D(Cuda3DLaunchConfig config, int bufsize, int* outbuf) {
}
}
-__global__ void CudaShuffleGetSrcLaneTest(unsigned* failure_count) {
- unsigned lane_id = CudaLaneId();
- for (int width = warpSize; width > 1; width /= 2) {
- auto check_result = [&](const char* op_name, int param, unsigned actual,
- unsigned expected) {
- if (actual != expected) {
- printf("Cuda%sGetSrcLane(%d, %d) for lane %d returned %d, not %d\n",
- op_name, param, width, lane_id, actual, expected);
- CudaAtomicAdd(failure_count, 1);
- }
- };
- for (int src_lane = -warpSize; src_lane <= warpSize; ++src_lane) {
- unsigned actual_lane = detail::CudaShuffleGetSrcLane(src_lane, width);
- unsigned expect_lane =
- CudaShuffleSync(kCudaWarpAll, lane_id, src_lane, width);
- check_result("Shuffle", src_lane, actual_lane, expect_lane);
- }
- for (unsigned delta = 0; delta <= warpSize; ++delta) {
- unsigned actual_lane = detail::CudaShuffleUpGetSrcLane(delta, width);
- unsigned expect_lane =
- CudaShuffleUpSync(kCudaWarpAll, lane_id, delta, width);
- check_result("ShuffleUp", delta, actual_lane, expect_lane);
- }
- for (unsigned delta = 0; delta <= warpSize; ++delta) {
- unsigned actual_lane = detail::CudaShuffleDownGetSrcLane(delta, width);
- unsigned expect_lane =
- CudaShuffleDownSync(kCudaWarpAll, lane_id, delta, width);
- check_result("ShuffleDown", delta, actual_lane, expect_lane);
- }
- for (int lane_lane = warpSize; lane_lane > 0; lane_lane /= 2) {
- unsigned actual_lane = detail::CudaShuffleXorGetSrcLane(lane_lane, width);
- unsigned expect_lane =
- CudaShuffleXorSync(kCudaWarpAll, lane_id, lane_lane, width);
- check_result("ShuffleXor", lane_lane, actual_lane, expect_lane);
- }
- }
-}
-
} // namespace
class CudaLaunchConfigTest : public ::testing::Test {
@@ -132,7 +94,7 @@ class CudaLaunchConfigTest : public ::testing::Test {
const int bufsize = 1024;
int* outbuf = nullptr;
Eigen::CudaStreamDevice stream;
- Eigen::GpuDevice d = Eigen::GpuDevice(&stream);
+ GPUDevice d = GPUDevice(&stream);
virtual void SetUp() {
cudaError_t err = cudaMallocManaged(&outbuf, sizeof(int) * bufsize);
@@ -267,16 +229,6 @@ TEST_F(CudaLaunchConfigTest, GetCuda3DLaunchConfig) {
#undef TEST_LAUNCH_PARAMETER
}
-TEST(CudaDeviceFunctionsTest, ShuffleGetSrcLane) {
- unsigned* failure_count;
- ASSERT_EQ(cudaMallocManaged(&failure_count, sizeof(unsigned)), cudaSuccess);
- *failure_count = 0;
- CudaShuffleGetSrcLaneTest<<<1, 32>>>(failure_count);
- ASSERT_EQ(cudaDeviceSynchronize(), cudaSuccess);
- ASSERT_EQ(*failure_count, 0);
- cudaFree(failure_count);
-}
-
} // namespace tensorflow
#endif // GOOGLE_CUDA
diff --git a/tensorflow/core/util/cuda_launch_config.h b/tensorflow/core/util/cuda_launch_config.h
deleted file mode 100644
index 3ea33ee6cf..0000000000
--- a/tensorflow/core/util/cuda_launch_config.h
+++ /dev/null
@@ -1,284 +0,0 @@
-/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#ifndef TENSORFLOW_CORE_UTIL_CUDA_LAUNCH_CONFIG_H_
-#define TENSORFLOW_CORE_UTIL_CUDA_LAUNCH_CONFIG_H_
-
-#if GOOGLE_CUDA
-
-#include <algorithm>
-
-#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
-#include "cuda/include/cuda.h"
-#include "tensorflow/core/framework/op_kernel.h"
-#include "tensorflow/core/platform/logging.h"
-#include "tensorflow/core/platform/stream_executor.h"
-#include "tensorflow/core/platform/types.h"
-
-// Usage of GetCudaLaunchConfig, GetCuda2DLaunchConfig, and
-// GetCuda3DLaunchConfig:
-//
-// There are two versions of GetCudaLaunchConfig and GetCuda2DLaunchConfig, one
-// version uses heuristics without any knowledge of the device kernel, the other
-// version uses cudaOccupancyMaxPotentialBlockSize to determine the theoretical
-// launch parameters that maximize occupancy. Currently, only the maximum
-// occupancy version of GetCuda3DLaunchConfig is available.
-//
-// For large number of work elements, the convention is that each kernel would
-// iterate through its assigned range. The return value of GetCudaLaunchConfig
-// is struct CudaLaunchConfig, which contains all the information needed for the
-// kernel launch, including: virtual number of threads, the number of threads
-// per block and number of threads per block used inside <<< >>> of a kernel
-// launch. GetCuda2DLaunchConfig and GetCuda3DLaunchConfig does the same thing
-// as CudaLaunchConfig. The only difference is the dimension. The macros
-// CUDA_1D_KERNEL_LOOP and CUDA_AXIS_KERNEL_LOOP might be used to do inner loop.
-//
-/* Sample code:
-
-__global__ void MyKernel1D(CudaLaunchConfig config, other_args...) {
- CUDA_1D_KERNEL_LOOP(x, config.virtual_thread_count) {
- do_your_job_here;
- }
-}
-
-__global__ void MyKernel2D(Cuda2DLaunchConfig config, other_args...) {
- CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) {
- CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) {
- do_your_job_here;
- }
- }
-}
-
-__global__ void MyKernel3D(Cuda3DLaunchConfig config, other_args...) {
- CUDA_AXIS_KERNEL_LOOP(x, config.virtual_thread_count, x) {
- CUDA_AXIS_KERNEL_LOOP(y, config.virtual_thread_count, y) {
- CUDA_AXIS_KERNEL_LOOP(z, config.virtual_thread_count, z) {
- do_your_job_here;
- }
- }
- }
-}
-
-void MyDriverFunc(const Eigen::GpuDevice &d) {
- // use heuristics
- CudaLaunchConfig cfg1 = GetCudaLaunchConfig(10240, d);
- MyKernel1D <<<config.block_count,
- config.thread_per_block, 0, d.stream()>>> (cfg1, other_args...);
- Cuda2DLaunchConfig cfg2 = GetCuda2DLaunchConfig(10240, 10240, d);
- MyKernel2D <<<config.block_count,
- config.thread_per_block, 0, d.stream()>>> (cfg2, other_args...);
- Cuda3DLaunchConfig cfg3 = GetCuda3DLaunchConfig(4096, 4096, 100, d);
- MyKernel3D <<<config.block_count,
- config.thread_per_block, 0, d.stream()>>> (cfg3, other_args...);
-
- // maximize occupancy
- CudaLaunchConfig cfg4 = GetCudaLaunchConfig(10240, d, MyKernel1D, 0, 0 );
- MyKernel1D <<<config.block_count,
- config.thread_per_block, 0, d.stream()>>> (cfg4, other_args...);
- Cuda2DLaunchConfig cfg5 = GetCuda2DLaunchConfig(10240, 10240, d,
- MyKernel1D, 0, 0);
- MyKernel2D <<<config.block_count,
- config.thread_per_block, 0, d.stream()>>> (cfg5, other_args...);
- Cuda3DLaunchConfig cfg6 = GetCuda3DLaunchConfig(4096, 4096, 100, d,
- MyKernel1D, 0, 0);
- MyKernel3D <<<config.block_count,
- config.thread_per_block, 0, d.stream()>>> (cfg6, other_args...);
-}
-
-// See the test for this for more example:
-//
-https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/util/cuda_kernel_helper_test.cu.cc
-
-*/
-
-namespace tensorflow {
-
-inline int DivUp(int a, int b) { return (a + b - 1) / b; }
-
-struct CudaLaunchConfig {
- // Logical number of thread that works on the elements. If each logical
- // thread works on exactly a single element, this is the same as the working
- // element count.
- int virtual_thread_count = -1;
- // Number of threads per block.
- int thread_per_block = -1;
- // Number of blocks for Cuda kernel launch.
- int block_count = -1;
-};
-
-// Calculate the Cuda launch config we should use for a kernel launch.
-// This is assuming the kernel is quite simple and will largely be
-// memory-limited.
-// REQUIRES: work_element_count > 0.
-inline CudaLaunchConfig GetCudaLaunchConfig(int work_element_count,
- const Eigen::GpuDevice& d) {
- CHECK_GT(work_element_count, 0);
- CudaLaunchConfig config;
- const int virtual_thread_count = work_element_count;
- const int physical_thread_count = std::min(
- d.getNumCudaMultiProcessors() * d.maxCudaThreadsPerMultiProcessor(),
- virtual_thread_count);
- const int thread_per_block = std::min(1024, d.maxCudaThreadsPerBlock());
- const int block_count =
- std::min(DivUp(physical_thread_count, thread_per_block),
- d.getNumCudaMultiProcessors());
-
- config.virtual_thread_count = virtual_thread_count;
- config.thread_per_block = thread_per_block;
- config.block_count = block_count;
- return config;
-}
-
-// Calculate the Cuda launch config we should use for a kernel launch. This
-// variant takes the resource limits of func into account to maximize occupancy.
-// REQUIRES: work_element_count > 0.
-template <typename DeviceFunc>
-inline CudaLaunchConfig GetCudaLaunchConfig(int work_element_count,
- const Eigen::GpuDevice& d,
- DeviceFunc func,
- size_t dynamic_shared_memory_size,
- int block_size_limit) {
- CHECK_GT(work_element_count, 0);
- CudaLaunchConfig config;
- int block_count = 0;
- int thread_per_block = 0;
-
- cudaError_t err = cudaOccupancyMaxPotentialBlockSize(
- &block_count, &thread_per_block, func, dynamic_shared_memory_size,
- block_size_limit);
- CHECK_EQ(err, cudaSuccess);
-
- block_count =
- std::min(block_count, DivUp(work_element_count, thread_per_block));
-
- config.virtual_thread_count = work_element_count;
- config.thread_per_block = thread_per_block;
- config.block_count = block_count;
- return config;
-}
-
-struct Cuda2DLaunchConfig {
- dim3 virtual_thread_count = dim3(0, 0, 0);
- dim3 thread_per_block = dim3(0, 0, 0);
- dim3 block_count = dim3(0, 0, 0);
-};
-
-inline Cuda2DLaunchConfig GetCuda2DLaunchConfig(int xdim, int ydim,
- const Eigen::GpuDevice& d) {
- Cuda2DLaunchConfig config;
-
- if (xdim <= 0 || ydim <= 0) {
- return config;
- }
-
- const int kThreadsPerBlock = 256;
- int block_cols = std::min(xdim, kThreadsPerBlock);
- // ok to round down here and just do more loops in the kernel
- int block_rows = std::max(kThreadsPerBlock / block_cols, 1);
-
- const int physical_thread_count =
- d.getNumCudaMultiProcessors() * d.maxCudaThreadsPerMultiProcessor();
-
- const int max_blocks = std::max(physical_thread_count / kThreadsPerBlock, 1);
-
- config.virtual_thread_count = dim3(xdim, ydim, 1);
- config.thread_per_block = dim3(block_cols, block_rows, 1);
-
- int grid_x = std::min(DivUp(xdim, block_cols), max_blocks);
-
- config.block_count = dim3(
- grid_x, std::min(max_blocks / grid_x, std::max(ydim / block_rows, 1)), 1);
- return config;
-}
-
-// Calculate the Cuda 2D and 3D launch config we should use for a kernel launch.
-// This variant takes the resource limits of func into account to maximize
-// occupancy.
-using Cuda3DLaunchConfig = Cuda2DLaunchConfig;
-
-template <typename DeviceFunc>
-inline Cuda3DLaunchConfig GetCuda3DLaunchConfig(
- int xdim, int ydim, int zdim, const Eigen::GpuDevice& d, DeviceFunc func,
- size_t dynamic_shared_memory_size, int block_size_limit) {
- Cuda3DLaunchConfig config;
-
- if (xdim <= 0 || ydim <= 0 || zdim <= 0) {
- return config;
- }
-
- int dev;
- cudaGetDevice(&dev);
- cudaDeviceProp deviceProp;
- cudaGetDeviceProperties(&deviceProp, dev);
- int xthreadlimit = deviceProp.maxThreadsDim[0];
- int ythreadlimit = deviceProp.maxThreadsDim[1];
- int zthreadlimit = deviceProp.maxThreadsDim[2];
- int xgridlimit = deviceProp.maxGridSize[0];
- int ygridlimit = deviceProp.maxGridSize[1];
- int zgridlimit = deviceProp.maxGridSize[2];
-
- int block_count = 0;
- int thread_per_block = 0;
- cudaError_t err = cudaOccupancyMaxPotentialBlockSize(
- &block_count, &thread_per_block, func, dynamic_shared_memory_size,
- block_size_limit);
- CHECK_EQ(err, cudaSuccess);
-
- auto min3 = [](int a, int b, int c) { return std::min(a, std::min(b, c)); };
-
- int threadsx = min3(xdim, thread_per_block, xthreadlimit);
- int threadsy =
- min3(ydim, std::max(thread_per_block / threadsx, 1), ythreadlimit);
- int threadsz =
- min3(zdim, std::max(thread_per_block / (threadsx * threadsy), 1),
- zthreadlimit);
-
- int blocksx = min3(block_count, DivUp(xdim, threadsx), xgridlimit);
- int blocksy =
- min3(DivUp(block_count, blocksx), DivUp(ydim, threadsy), ygridlimit);
- int blocksz = min3(DivUp(block_count, (blocksx * blocksy)),
- DivUp(zdim, threadsz), zgridlimit);
-
- config.virtual_thread_count = dim3(xdim, ydim, zdim);
- config.thread_per_block = dim3(threadsx, threadsy, threadsz);
- config.block_count = dim3(blocksx, blocksy, blocksz);
- return config;
-}
-
-template <typename DeviceFunc>
-inline Cuda2DLaunchConfig GetCuda2DLaunchConfig(
- int xdim, int ydim, const Eigen::GpuDevice& d, DeviceFunc func,
- size_t dynamic_shared_memory_size, int block_size_limit) {
- return GetCuda3DLaunchConfig(xdim, ydim, 1, d, func,
- dynamic_shared_memory_size, block_size_limit);
-}
-
-// Returns a raw reference to the current cuda stream. Required by a
-// number of kernel calls (for which StreamInterface* does not work), i.e.
-// CUB and certain cublas primitives.
-inline const cudaStream_t& GetCudaStream(OpKernelContext* context) {
- const cudaStream_t* ptr = CHECK_NOTNULL(
- reinterpret_cast<const cudaStream_t*>(context->op_device_context()
- ->stream()
- ->implementation()
- ->CudaStreamMemberHack()));
- return *ptr;
-}
-
-} // namespace tensorflow
-
-#endif // GOOGLE_CUDA
-
-#endif // TENSORFLOW_CORE_UTIL_CUDA_KERNEL_HELPER_H_