Tensor: Use native atomic addition for double reductions
diff --git a/benchmarks/GPU/bench_gpu_reduction.cu b/benchmarks/GPU/bench_gpu_reduction.cu
index a59535c..2ea8b26 100644
--- a/benchmarks/GPU/bench_gpu_reduction.cu
+++ b/benchmarks/GPU/bench_gpu_reduction.cu
@@ -223,8 +223,8 @@
 
 // Explicit shapes rather than a product: the product of the two ranges asks for tensors of up to 2^40 elements.
 // Args is (rows, columns), so for the outer reduction it reads as (outputs, reduced extent) and the last three
-// shapes straddle the band OuterReducer::run admits doubles to OuterReductionKernel in: a reduced extent below
-// its floor, a shape inside the band, and an output count above its ceiling on a 36-multiprocessor device. For
+// shapes straddle the former CUDA double-sum band: a reduced extent below its floor, a shape inside the band,
+// and an output count above its ceiling on a 36-multiprocessor device. HIP retains that band. For
 // the inner reduction and the CUB baseline the two extents swap roles.
 #define EIGEN_GPU_PARTIAL_REDUCTION_SHAPES(NAME, TYPE) \
   BENCHMARK_TEMPLATE(NAME, TYPE)                       \
diff --git a/contrib/Eigen/src/Tensor/TensorReductionGpu.h b/contrib/Eigen/src/Tensor/TensorReductionGpu.h
index 1dccb6c..ef17207 100644
--- a/contrib/Eigen/src/Tensor/TensorReductionGpu.h
+++ b/contrib/Eigen/src/Tensor/TensorReductionGpu.h
@@ -156,6 +156,11 @@
   atomicAdd(output, accum);
 }
 
+template <>
+__device__ inline void atomicReduce(double* output, double accum, SumReducer<double>&) {
+  atomicAdd(output, accum);
+}
+
 template <typename CoeffType, typename Index>
 __global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void ReductionInitKernel(const CoeffType val, Index num_preserved_coeffs,
                                                                  CoeffType* output) {
@@ -889,9 +894,14 @@
       return true;
     }
 
-    // Double reductions use compare-and-swap atomics. Restrict their parallel reduction to the shape band
-    // measured to amortize that contention on sm_89; outside it, use one thread per output.
-    if (std::is_same<OutputType, double>::value) {
+    // CUDA has native FP64 atomicAdd on every supported architecture (sm_60+). Keep the measured shape band
+    // for the other double reducers and for HIP, where atomicAdd may still use compare-and-swap.
+#if defined(EIGEN_CUDACC)
+    constexpr bool has_native_double_sum = std::is_same<Op, SumReducer<double>>::value;
+#else
+    constexpr bool has_native_double_sum = false;
+#endif
+    if (std::is_same<OutputType, double>::value && !has_native_double_sum) {
       const Index multi_processors = device.getNumGpuMultiProcessors();
       if (num_coeffs_to_reduce < 64 || num_preserved_vals < 8 * multi_processors ||
           num_preserved_vals > 64 * multi_processors) {
diff --git a/contrib/test/tensor_reduction_gpu.cu b/contrib/test/tensor_reduction_gpu.cu
index 0e91784..e4f9ddf 100644
--- a/contrib/test/tensor_reduction_gpu.cu
+++ b/contrib/test/tensor_reduction_gpu.cu
@@ -53,11 +53,7 @@
 }
 
 template <typename Type, int DataLayout>
-static void test_first_dim_reductions() {
-  int dim_x = 33;
-  int dim_y = 1;
-  int dim_z = 128;
-
+static void test_first_dim_reductions(int dim_x = 33, int dim_y = 1, int dim_z = 128) {
   Tensor<Type, 3, DataLayout> in(dim_x, dim_y, dim_z);
   in.setRandom();
 
@@ -144,13 +140,16 @@
   CALL_SUBTEST_6((test_last_dim_reductions<float, RowMajor>()));
   CALL_SUBTEST_6((test_last_dim_reductions<double, RowMajor>()));
 
-  // A double outer reduction reaches OuterReductionKernel only inside the band of output counts OuterReducer::run
-  // gates on, and that band scales with the device's multiprocessor count; every shape above lies outside it and
-  // takes the generic path. The float cases are not gated and reach the kernel already.
+  // CUDA double sums also reach OuterReductionKernel below and above the former shape band. HIP keeps that band.
   {
     Eigen::GpuStreamDevice stream;
     Eigen::GpuDevice device(&stream);
-    const int outputs_in_band = 16 * device.getNumGpuMultiProcessors();
-    CALL_SUBTEST_7((test_last_dim_reductions<double, ColMajor>(outputs_in_band, 1, 128)));
+    const int multi_processors = device.getNumGpuMultiProcessors();
+    for (int outputs : {4 * multi_processors, 16 * multi_processors, 65 * multi_processors}) {
+      CALL_SUBTEST_7((test_last_dim_reductions<double, ColMajor>(outputs, 1, 128)));
+      CALL_SUBTEST_7((test_first_dim_reductions<double, RowMajor>(128, 1, outputs)));
+    }
+    CALL_SUBTEST_7((test_last_dim_reductions<double, ColMajor>(16 * multi_processors, 1, 33)));
+    CALL_SUBTEST_7((test_first_dim_reductions<double, RowMajor>(33, 1, 16 * multi_processors)));
   }
 }