Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
TensorReductionGpu.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2014 Benoit Steiner <benoit.steiner.goog@gmail.com>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11#ifndef EIGEN_TENSOR_TENSOR_REDUCTION_GPU_H
12#define EIGEN_TENSOR_TENSOR_REDUCTION_GPU_H
13
14// IWYU pragma: private
15#include "./InternalHeaderCheck.h"
16
17namespace Eigen {
18namespace internal {
19
20#if defined(EIGEN_USE_GPU) && defined(EIGEN_GPUCC)
21// Full reducers for GPU, don't vectorize for now
22
23// Reducer function that enables multiple gpu threads to safely accumulate at the same
24// output address. It basically reads the current value of the output variable, and
25// attempts to update it with the new value. If in the meantime another gpu thread
26// updated the content of the output address it will try again.
27template <typename T, typename R>
28__device__ EIGEN_ALWAYS_INLINE void atomicReduce(T* output, T accum, R& reducer) {
29 EIGEN_IF_CONSTEXPR (sizeof(T) == 4) {
30 unsigned int oldval = *reinterpret_cast<unsigned int*>(output);
31 unsigned int newval = oldval;
32 reducer.reduce(accum, reinterpret_cast<T*>(&newval));
33 if (newval == oldval) {
34 return;
35 }
36 unsigned int readback;
37 while ((readback = atomicCAS((unsigned int*)output, oldval, newval)) != oldval) {
38 oldval = readback;
39 newval = oldval;
40 reducer.reduce(accum, reinterpret_cast<T*>(&newval));
41 if (newval == oldval) {
42 return;
43 }
44 }
45 } else EIGEN_IF_CONSTEXPR (sizeof(T) == 8) {
46 unsigned long long oldval = *reinterpret_cast<unsigned long long*>(output);
47 unsigned long long newval = oldval;
48 reducer.reduce(accum, reinterpret_cast<T*>(&newval));
49 if (newval == oldval) {
50 return;
51 }
52 unsigned long long readback;
53 while ((readback = atomicCAS(reinterpret_cast<unsigned long long*>(output), oldval, newval)) != oldval) {
54 oldval = readback;
55 newval = oldval;
56 reducer.reduce(accum, reinterpret_cast<T*>(&newval));
57 if (newval == oldval) {
58 return;
59 }
60 }
61 } else {
62 gpu_assert(0 && "Wordsize not supported");
63 }
64}
65
66// We extend atomicExch to support extra data types
67template <typename Type>
68__device__ inline Type atomicExchCustom(Type* address, Type val) {
69 return atomicExch(address, val);
70}
71
72template <typename T>
73EIGEN_DEVICE_FUNC EIGEN_CONSTEXPR auto reduction_shuffle_mask() {
74#if defined(EIGEN_HIP_DEVICE_COMPILE)
75 return 0xFFFFFFFFFFFFFFFFull;
76#else
77 return 0xFFFFFFFFu;
78#endif
79}
80
81template <typename T>
82__device__ EIGEN_ALWAYS_INLINE T reduction_shuffle_down(T value, int offset) {
83#if defined(EIGEN_HIPCC)
84 return __shfl_down(value, offset, warpSize);
85#else
86 return __shfl_down_sync(reduction_shuffle_mask<T>(), value, offset, warpSize);
87#endif
88}
89
90template <>
91__device__ EIGEN_ALWAYS_INLINE int reduction_shuffle_down<int>(int value, int offset) {
92#if defined(EIGEN_HIPCC)
93 return __shfl_down(value, offset, warpSize);
94#else
95 return __shfl_down_sync(reduction_shuffle_mask<int>(), value, offset, warpSize);
96#endif
97}
98
99template <>
100__device__ EIGEN_ALWAYS_INLINE float reduction_shuffle_down<float>(float value, int offset) {
101#if defined(EIGEN_HIPCC)
102 return __shfl_down(value, offset, warpSize);
103#else
104 return __shfl_down_sync(reduction_shuffle_mask<float>(), value, offset, warpSize);
105#endif
106}
107
108template <>
109__device__ EIGEN_ALWAYS_INLINE double reduction_shuffle_down<double>(double value, int offset) {
110#if defined(EIGEN_HIPCC)
111 return __shfl_down(value, offset, warpSize);
112#else
113 return __shfl_down_sync(reduction_shuffle_mask<double>(), value, offset, warpSize);
114#endif
115}
116
117template <>
118__device__ inline double atomicExchCustom(double* address, double val) {
119 unsigned long long int* address_as_ull = reinterpret_cast<unsigned long long int*>(address);
120 return __longlong_as_double(atomicExch(address_as_ull, __double_as_longlong(val)));
121}
122
123// Half-float reduction specializations.
124template <typename R>
125__device__ inline void atomicReduce(half2* output, half2 accum, R& reducer) {
126 unsigned int oldval = *reinterpret_cast<unsigned int*>(output);
127 unsigned int newval = oldval;
128 reducer.reducePacket(accum, reinterpret_cast<half2*>(&newval));
129 if (newval == oldval) {
130 return;
131 }
132 unsigned int readback;
133 while ((readback = atomicCAS((unsigned int*)output, oldval, newval)) != oldval) {
134 oldval = readback;
135 newval = oldval;
136 reducer.reducePacket(accum, reinterpret_cast<half2*>(&newval));
137 if (newval == oldval) {
138 return;
139 }
140 }
141}
142#ifdef EIGEN_GPU_COMPILE_PHASE
143// reduction should be associative since reduction is not atomic in wide vector but atomic in half2 operations
144template <typename R>
145__device__ inline void atomicReduce(Packet4h2* output, Packet4h2 accum, R& reducer) {
146 half2* houtput = reinterpret_cast<half2*>(output);
147 half2* haccum = reinterpret_cast<half2*>(&accum);
148 for (int i = 0; i < 4; ++i) {
149 atomicReduce(houtput + i, *(haccum + i), reducer);
150 }
151}
152#endif // EIGEN_GPU_COMPILE_PHASE
153
154template <>
155__device__ inline void atomicReduce(float* output, float accum, SumReducer<float>&) {
156 atomicAdd(output, accum);
157}
158
159template <>
160__device__ inline void atomicReduce(double* output, double accum, SumReducer<double>&) {
161 atomicAdd(output, accum);
162}
163
164template <typename CoeffType, typename Index>
165__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void ReductionInitKernel(const CoeffType val, Index num_preserved_coeffs,
166 CoeffType* output) {
167 const Index thread_id = blockIdx.x * blockDim.x + threadIdx.x;
168 const Index num_threads = blockDim.x * gridDim.x;
169 for (Index i = thread_id; i < num_preserved_coeffs; i += num_threads) {
170 output[i] = val;
171 }
172}
173
174template <int BlockSize, int NumPerThread, typename Self, typename Reducer, typename Index>
175__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void FullReductionKernel(Reducer reducer, const Self input, Index num_coeffs,
176 typename Self::CoeffReturnType* output,
177 unsigned int* semaphore) {
178 // Initialize the output value
179 const Index first_index = blockIdx.x * BlockSize * NumPerThread + threadIdx.x;
180 if (gridDim.x == 1) {
181 if (first_index == 0) {
182 *output = reducer.initialize();
183 }
184 } else {
185 if (threadIdx.x == 0) {
186 unsigned int block = atomicCAS(semaphore, 0u, 1u);
187 if (block == 0) {
188 // We're the first block to run, initialize the output value
189 atomicExchCustom(output, reducer.initialize());
190 __threadfence();
191 atomicExch(semaphore, 2u);
192 } else {
193 // Wait for the first block to initialize the output value.
194 // Use atomicCAS here to ensure that the reads aren't cached
195 unsigned int val;
196 do {
197 val = atomicCAS(semaphore, 2u, 2u);
198 } while (val < 2u);
199 }
200 }
201 }
202
203 __syncthreads();
204
205 eigen_assert(gridDim.x == 1 || *semaphore >= 2u);
206
207 typename Self::CoeffReturnType accum = reducer.initialize();
208 Index max_iter = numext::mini<Index>(num_coeffs - first_index, NumPerThread * BlockSize);
209 for (Index i = 0; i < max_iter; i += BlockSize) {
210 const Index index = first_index + i;
211 eigen_assert(index < num_coeffs);
212 typename Self::CoeffReturnType val = input.m_impl.coeff(index);
213 reducer.reduce(val, &accum);
214 }
215
216#pragma unroll
217 for (int offset = warpSize / 2; offset > 0; offset /= 2) {
218 reducer.reduce(reduction_shuffle_down(accum, offset), &accum);
219 }
220
221 if ((threadIdx.x & (warpSize - 1)) == 0) {
222 atomicReduce(output, accum, reducer);
223 }
224
225 if (gridDim.x > 1 && threadIdx.x == 0) {
226 // Let the last block reset the semaphore
227 atomicInc(semaphore, gridDim.x + 1);
228#if defined(EIGEN_HIPCC)
229 __threadfence_system();
230#endif
231 }
232}
233
234// Half-float reduction specializations.
235template <typename Self, typename Reducer, typename Index>
236__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void ReductionInitFullReduxKernelHalfFloat(Reducer reducer, const Self input,
237 Index num_coeffs, half* scratch) {
238 eigen_assert(blockDim.x == 1);
239 eigen_assert(gridDim.x == 1);
240 typedef packet_traits<Eigen::half>::type packet_type;
241 Index packet_remainder = num_coeffs % Index(unpacket_traits<packet_type>::size);
242 if (packet_remainder != 0) {
243 half2* h2scratch = reinterpret_cast<half2*>(scratch);
244 for (Index i = num_coeffs - packet_remainder; i + 2 <= num_coeffs; i += 2) {
245 *h2scratch = __halves2half2(input.coeff(i), input.coeff(i + 1));
246 h2scratch++;
247 }
248 if ((num_coeffs & 1) != 0) {
249 half lastCoeff = input.coeff(num_coeffs - 1);
250 *h2scratch = __halves2half2(lastCoeff, reducer.initialize());
251 }
252 } else {
253 packet_type reduce = reducer.template initializePacket<packet_type>();
254 internal::pstoreu(scratch, reduce);
255 }
256}
257
258template <typename Self, typename Reducer, typename Index>
259__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void ReductionInitKernelHalfFloat(Reducer reducer, const Self /*input*/,
260 Index num_coeffs, half* output) {
261 const Index thread_id = blockIdx.x * blockDim.x + threadIdx.x;
262 const Index num_threads = blockDim.x * gridDim.x;
263 typedef typename packet_traits<Eigen::half>::type PacketType;
264
265 const Index num_packets = num_coeffs / Index(unpacket_traits<PacketType>::size);
266 PacketType* p_output = reinterpret_cast<PacketType*>(output);
267 for (Index i = thread_id; i < num_packets; i += num_threads) {
268 p_output[i] = reducer.template initializePacket<PacketType>();
269 }
270 Index packet_remainder = num_coeffs % Index(unpacket_traits<PacketType>::size);
271 if (thread_id < packet_remainder) {
272 output[num_coeffs - packet_remainder + thread_id] = reducer.initialize();
273 }
274}
275
276template <int BlockSize, int NumPerThread, typename Self, typename Reducer, typename Index>
277__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void FullReductionKernelHalfFloat(Reducer reducer, const Self input,
278 Index num_coeffs, half* output,
279 half* scratch) {
280 typedef typename packet_traits<Eigen::half>::type PacketType;
281 const int packet_width = unpacket_traits<PacketType>::size;
282 eigen_assert(NumPerThread % packet_width == 0);
283 const Index first_index = blockIdx.x * BlockSize * NumPerThread + packet_width * threadIdx.x;
284
285 // Initialize the output value if it wasn't initialized by the ReductionInitKernel
286
287 if (gridDim.x == 1) {
288 if (first_index == 0) {
289 int rem = num_coeffs % packet_width;
290 if (rem != 0) {
291 half2* p_scratch = reinterpret_cast<half2*>(scratch);
292 pstoreu(scratch, reducer.template initializePacket<PacketType>());
293 for (int i = 0; i < rem / 2; i++) {
294 *p_scratch = __halves2half2(input.coeff(num_coeffs - packet_width + 2 * i),
295 input.coeff(num_coeffs - packet_width + 2 * i + 1));
296 p_scratch++;
297 }
298 if ((num_coeffs & 1) != 0) {
299 half last = input.coeff(num_coeffs - 1);
300 *p_scratch = __halves2half2(last, reducer.initialize());
301 }
302 } else {
303 PacketType reduce = reducer.template initializePacket<PacketType>();
304 pstoreu(scratch, reduce);
305 }
306 }
307 __syncthreads();
308 }
309
310 PacketType accum = reducer.template initializePacket<PacketType>();
311 const Index max_iter =
312 numext::mini<Index>((num_coeffs - first_index) / packet_width, NumPerThread * BlockSize / packet_width);
313 for (Index i = 0; i < max_iter; i += BlockSize) {
314 const Index index = first_index + packet_width * i;
315 eigen_assert(index + packet_width < num_coeffs);
316 PacketType val = input.template packet<Unaligned>(index);
317 reducer.reducePacket(val, &accum);
318 }
319
320#pragma unroll
321 for (int offset = warpSize / 2; offset > 0; offset /= 2) {
322#if defined(EIGEN_HIPCC)
323 PacketType r1;
324 half2* hr = reinterpret_cast<half2*>(&r1);
325 half2* hacc = reinterpret_cast<half2*>(&accum);
326 for (int i = 0; i < packet_width / 2; i++) {
327 // FIXME : remove this workaround once we have native half/half2 support for __shfl_down
328 union {
329 int i;
330 half2 h;
331 } wka_in, wka_out;
332 wka_in.h = hacc[i];
333 wka_out.i = __shfl_down(wka_in.i, offset, warpSize);
334 hr[i] = wka_out.h;
335 }
336 reducer.reducePacket(r1, &accum);
337#else
338 PacketType r1;
339 half2* hr = reinterpret_cast<half2*>(&r1);
340 half2* hacc = reinterpret_cast<half2*>(&accum);
341 for (int i = 0; i < packet_width / 2; i++) {
342 hr[i] = __shfl_down_sync(0xFFFFFFFF, hacc[i], (unsigned)offset, warpSize);
343 }
344 reducer.reducePacket(r1, &accum);
345
346#endif
347 }
348
349 if ((threadIdx.x & (warpSize - 1)) == 0) {
350 atomicReduce(reinterpret_cast<PacketType*>(scratch), accum, reducer);
351 }
352
353 __syncthreads();
354 half2* rv1 = reinterpret_cast<half2*>(scratch);
355 if (packet_width > 2) {
356 reducer.reducePacket(rv1[2], rv1);
357 reducer.reducePacket(rv1[3], rv1 + 1);
358 reducer.reducePacket(rv1[1], rv1);
359 }
360 if (gridDim.x == 1) {
361 if (first_index == 0) {
362 half tmp = __low2half(*rv1);
363 reducer.reduce(__high2half(*rv1), &tmp);
364 *output = tmp;
365 }
366 }
367}
368
369template <typename Op>
370__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void ReductionCleanupKernelHalfFloat(Op reducer, half* output, half* scratch) {
371 eigen_assert(threadIdx.x == 1);
372 typedef packet_traits<Eigen::half>::type packet_type;
373 EIGEN_IF_CONSTEXPR (unpacket_traits<packet_type>::size == 1) {
374 *output = *scratch;
375 } else {
376 half2* pscratch = reinterpret_cast<half2*>(scratch);
377 half tmp = __float2half(0.f);
378 for (int i = 0; i < unpacket_traits<packet_type>::size; i += 2) {
379 reducer.reduce(__low2half(*pscratch), &tmp);
380 reducer.reduce(__high2half(*pscratch), &tmp);
381 pscratch++;
382 }
383 *output = tmp;
384 }
385}
386
387template <typename Self, typename Op, typename OutputType, bool PacketAccess, typename Enabled = void>
388struct FullReductionLauncher {
389 static void run(const Self&, Op&, const GpuDevice&, OutputType*, typename Self::Index) {
390 gpu_assert(false && "Should only be called on doubles, floats and half floats");
391 }
392};
393
394// Specialization for float and double
395template <typename Self, typename Op, typename OutputType, bool PacketAccess>
396struct FullReductionLauncher<
397 Self, Op, OutputType, PacketAccess,
398 std::enable_if_t<std::is_same<float, OutputType>::value || std::is_same<double, OutputType>::value, void>> {
399 static void run(const Self& self, Op& reducer, const GpuDevice& device, OutputType* output,
400 typename Self::Index num_coeffs) {
401 typedef typename Self::Index Index;
402 const int block_size = 256;
403 const int num_per_thread = 128;
404 const int num_blocks = numext::div_ceil<int>(num_coeffs, block_size * num_per_thread);
405
406 unsigned int* semaphore = nullptr;
407 if (num_blocks > 1) {
408 semaphore = device.semaphore();
409 }
410
411 LAUNCH_GPU_KERNEL((FullReductionKernel<block_size, num_per_thread, Self, Op, Index>), num_blocks, block_size, 0,
412 device, reducer, self, num_coeffs, output, semaphore);
413 }
414};
415
416// Half-float reduction specializations.
417template <typename Self, typename Op>
418struct FullReductionLauncher<Self, Op, Eigen::half, false> {
419 static void run(const Self&, Op&, const GpuDevice&, half*, typename Self::Index) {
420 gpu_assert(false && "Should not be called since there is no packet accessor");
421 }
422};
423
424template <typename Self, typename Op>
425struct FullReductionLauncher<Self, Op, Eigen::half, true> {
426 static void run(const Self& self, Op& reducer, const GpuDevice& device, half* output,
427 typename Self::Index num_coeffs) {
428 typedef typename Self::Index Index;
429
430 const int block_size = 256;
431 const int num_per_thread = 128;
432 const int num_blocks = numext::div_ceil<int>(num_coeffs, block_size * num_per_thread);
433 half* scratch = static_cast<half*>(device.scratchpad());
434
435 if (num_blocks > 1) {
436 // We initialize the output and the scratchpad outside the reduction kernel when we can't be sure that there
437 // won't be race conditions between multiple thread blocks.
438 LAUNCH_GPU_KERNEL((ReductionInitFullReduxKernelHalfFloat<Self, Op, Index>), 1, 1, 0, device, reducer, self,
439 num_coeffs, scratch);
440 }
441
442 LAUNCH_GPU_KERNEL((FullReductionKernelHalfFloat<block_size, num_per_thread, Self, Op, Index>), num_blocks,
443 block_size, 0, device, reducer, self, num_coeffs, output, scratch);
444
445 if (num_blocks > 1) {
446 LAUNCH_GPU_KERNEL((ReductionCleanupKernelHalfFloat<Op>), 1, 1, 0, device, reducer, output, scratch);
447 }
448 }
449};
450
451template <typename Self, typename Op, bool Vectorizable>
452struct FullReducer<Self, Op, GpuDevice, Vectorizable> {
453 // Unfortunately nvidia doesn't support well exotic types such as complex,
454 // so reduce the scope of the optimized version of the code to the simple cases
455 // of doubles, floats and half floats
456 // Half-float reduction specializations.
457 static constexpr bool HasOptimizedImplementation =
458 !Self::ReducerTraits::IsStateful && (std::is_same<typename Self::CoeffReturnType, float>::value ||
459 std::is_same<typename Self::CoeffReturnType, double>::value ||
460 (std::is_same<typename Self::CoeffReturnType, Eigen::half>::value &&
461 reducer_traits<Op, GpuDevice>::PacketAccess));
462
463 template <typename OutputType>
464 static void run(const Self& self, Op& reducer, const GpuDevice& device, OutputType* output) {
465 gpu_assert(HasOptimizedImplementation && "Should only be called on doubles, floats or half floats");
466 const Index num_coeffs = array_prod(self.m_impl.dimensions());
467 // Don't crash when we're called with an input tensor of size 0.
468 if (num_coeffs == 0) {
469 return;
470 }
471
472 FullReductionLauncher<Self, Op, OutputType, reducer_traits<Op, GpuDevice>::PacketAccess>::run(self, reducer, device,
473 output, num_coeffs);
474 }
475};
476
477template <int NumPerThread, typename Self, typename Reducer, typename Index>
478__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void InnerReductionKernel(Reducer reducer, const Self input,
479 Index num_coeffs_to_reduce,
480 Index num_preserved_coeffs,
481 typename Self::CoeffReturnType* output) {
482 typedef typename Self::CoeffReturnType Type;
483 eigen_assert(blockDim.y == 1);
484 eigen_assert(blockDim.z == 1);
485 eigen_assert(gridDim.y == 1);
486 eigen_assert(gridDim.z == 1);
487
488 const int unroll_times = 16;
489 eigen_assert(NumPerThread % unroll_times == 0);
490
491 const Index input_col_blocks = numext::div_ceil<Index>(num_coeffs_to_reduce, blockDim.x * NumPerThread);
492 const Index num_input_blocks = input_col_blocks * num_preserved_coeffs;
493
494 const Index num_threads = blockDim.x * gridDim.x;
495 const Index thread_id = blockIdx.x * blockDim.x + threadIdx.x;
496
497 // Initialize the output values if they weren't initialized by the ReductionInitKernel
498 if (gridDim.x == 1) {
499 for (Index i = thread_id; i < num_preserved_coeffs; i += num_threads) {
500 output[i] = reducer.initialize();
501 }
502 __syncthreads();
503 }
504
505 for (Index i = blockIdx.x; i < num_input_blocks; i += gridDim.x) {
506 const Index row = i / input_col_blocks;
507
508 // `i`, and so `row`, is uniform across the block: a warp enters this branch as a whole, which the full-mask
509 // shuffle tree below relies on.
510 if (row < num_preserved_coeffs) {
511 const Index col_block = i % input_col_blocks;
512 const Index col_begin = col_block * blockDim.x * NumPerThread + threadIdx.x;
513
514 Type reduced_val = reducer.initialize();
515
516 for (Index j = 0; j < NumPerThread; j += unroll_times) {
517 const Index last_col = col_begin + blockDim.x * (j + unroll_times - 1);
518 if (last_col >= num_coeffs_to_reduce) {
519 for (Index col = col_begin + blockDim.x * j; col < num_coeffs_to_reduce; col += blockDim.x) {
520 const Type val = input.m_impl.coeff(row * num_coeffs_to_reduce + col);
521 reducer.reduce(val, &reduced_val);
522 }
523 break;
524 } else {
525 // Faster version of the loop with no branches after unrolling.
526#pragma unroll
527 for (int k = 0; k < unroll_times; ++k) {
528 const Index col = col_begin + blockDim.x * (j + k);
529 reducer.reduce(input.m_impl.coeff(row * num_coeffs_to_reduce + col), &reduced_val);
530 }
531 }
532 }
533
534#pragma unroll
535 for (int offset = warpSize / 2; offset > 0; offset /= 2) {
536 reducer.reduce(reduction_shuffle_down(reduced_val, offset), &reduced_val);
537 }
538
539 if ((threadIdx.x & (warpSize - 1)) == 0) {
540 atomicReduce(&(output[row]), reduced_val, reducer);
541 }
542 }
543 }
544}
545
546// Half-float reduction specializations.
547
548template <int NumPerThread, typename Self, typename Reducer, typename Index>
549__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void InnerReductionKernelHalfFloat(Reducer reducer, const Self input,
550 Index num_coeffs_to_reduce,
551 Index num_preserved_coeffs, half* output) {
552 eigen_assert(blockDim.y == 1);
553 eigen_assert(blockDim.z == 1);
554 eigen_assert(gridDim.y == 1);
555 eigen_assert(gridDim.z == 1);
556
557 typedef typename packet_traits<Eigen::half>::type PacketType;
558 const int packet_width = unpacket_traits<PacketType>::size;
559 const int unroll_times = 16 / packet_width;
560 eigen_assert(NumPerThread % unroll_times == 0);
561 eigen_assert(unroll_times % 2 == 0);
562
563 const Index input_col_blocks = numext::div_ceil<Index>(num_coeffs_to_reduce, blockDim.x * NumPerThread * 2);
564 const Index num_input_blocks = numext::div_ceil<Index>(input_col_blocks * num_preserved_coeffs, 2);
565
566 const Index num_threads = blockDim.x * gridDim.x;
567 const Index thread_id = blockIdx.x * blockDim.x + threadIdx.x;
568
569 // Initialize the output values if they weren't initialized by the ReductionInitKernel
570 if (gridDim.x == 1) {
571 Index i = packet_width * thread_id;
572 for (; i + packet_width <= num_preserved_coeffs; i += packet_width * num_threads) {
573 PacketType* poutput = reinterpret_cast<PacketType*>(output + i);
574 *poutput = reducer.template initializePacket<PacketType>();
575 }
576 if (i < num_preserved_coeffs) {
577 output[i] = reducer.initialize();
578 }
579 __syncthreads();
580 }
581
582 for (Index i = blockIdx.x; i < num_input_blocks; i += gridDim.x) {
583 const Index row = 2 * (i / input_col_blocks); // everybody takes 2 rows
584
585 // Block-uniform, as in InnerReductionKernel: the full-mask shuffles below are safe.
586 if (row + 1 < num_preserved_coeffs) {
587 const Index col_block = i % input_col_blocks;
588 const Index col_begin = packet_width * (col_block * blockDim.x * NumPerThread + threadIdx.x);
589
590 PacketType reduced_val1 = reducer.template initializePacket<PacketType>();
591 PacketType reduced_val2 = reducer.template initializePacket<PacketType>();
592
593 for (Index j = 0; j < NumPerThread; j += unroll_times) {
594 const Index last_col = col_begin + blockDim.x * (j + unroll_times - 1) * packet_width;
595 if (last_col >= num_coeffs_to_reduce) {
596 Index col = col_begin + blockDim.x * j;
597 for (; col + packet_width <= num_coeffs_to_reduce; col += blockDim.x) {
598 const PacketType val1 = input.m_impl.template packet<Unaligned>(row * num_coeffs_to_reduce + col);
599 reducer.reducePacket(val1, &reduced_val1);
600 const PacketType val2 = input.m_impl.template packet<Unaligned>((row + 1) * num_coeffs_to_reduce + col);
601 reducer.reducePacket(val2, &reduced_val2);
602 }
603 if (col < num_coeffs_to_reduce) {
604 PacketType r1 = reducer.template initializePacket<PacketType>();
605 PacketType r2 = reducer.template initializePacket<PacketType>();
606 half2* hr1 = reinterpret_cast<half2*>(&r1);
607 half2* hr2 = reinterpret_cast<half2*>(&r2);
608 while (col + 1 < num_coeffs_to_reduce) {
609 *hr1 = __halves2half2(input.m_impl.coeff(row * num_coeffs_to_reduce + col),
610 input.m_impl.coeff(row * num_coeffs_to_reduce + col + 1));
611 *hr2 = __halves2half2(input.m_impl.coeff((row + 1) * num_coeffs_to_reduce + col),
612 input.m_impl.coeff((row + 1) * num_coeffs_to_reduce + col + 1));
613 hr1++;
614 hr2++;
615 col += 2;
616 }
617 if (col < num_coeffs_to_reduce) {
618 // Peel;
619 const half last1 = input.m_impl.coeff(row * num_coeffs_to_reduce + col);
620 *hr1 = __halves2half2(last1, reducer.initialize());
621 const half last2 = input.m_impl.coeff((row + 1) * num_coeffs_to_reduce + col);
622 *hr2 = __halves2half2(last2, reducer.initialize());
623 }
624 reducer.reducePacket(r1, &reduced_val1);
625 reducer.reducePacket(r2, &reduced_val2);
626 }
627 break;
628 } else {
629 // Faster version of the loop with no branches after unrolling.
630#pragma unroll
631 for (int k = 0; k < unroll_times; ++k) {
632 const Index col = col_begin + blockDim.x * (j + k) * packet_width;
633 reducer.reducePacket(input.m_impl.template packet<Unaligned>(row * num_coeffs_to_reduce + col),
634 &reduced_val1);
635 reducer.reducePacket(input.m_impl.template packet<Unaligned>((row + 1) * num_coeffs_to_reduce + col),
636 &reduced_val2);
637 }
638 }
639 }
640
641#pragma unroll
642 for (int offset = warpSize / 2; offset > 0; offset /= 2) {
643#if defined(EIGEN_HIPCC)
644 PacketType r1;
645 PacketType r2;
646 half2* hr1 = reinterpret_cast<half2*>(&r1);
647 half2* hr2 = reinterpret_cast<half2*>(&r2);
648 half2* rv1 = reinterpret_cast<half2*>(&reduced_val1);
649 half2* rv2 = reinterpret_cast<half2*>(&reduced_val2);
650 for (int i = 0; i < packet_width / 2; i++) {
651 // FIXME : remove this workaround once we have native half/half2 support for __shfl_down
652 union {
653 int i;
654 half2 h;
655 } wka_in1, wka_out1;
656 wka_in1.h = rv1[i];
657 wka_out1.i = __shfl_down(wka_in1.i, offset, warpSize);
658 hr1[i] = wka_out1.h;
659
660 union {
661 int i;
662 half2 h;
663 } wka_in2, wka_out2;
664 wka_in2.h = rv2[i];
665 wka_out2.i = __shfl_down(wka_in2.i, offset, warpSize);
666 hr2[i] = wka_out2.h;
667 }
668 reducer.reducePacket(r1, &reduced_val1);
669 reducer.reducePacket(r2, &reduced_val2);
670#else
671 PacketType r1;
672 PacketType r2;
673 half2* hr1 = reinterpret_cast<half2*>(&r1);
674 half2* hr2 = reinterpret_cast<half2*>(&r2);
675 half2* rr1 = reinterpret_cast<half2*>(&reduced_val1);
676 half2* rr2 = reinterpret_cast<half2*>(&reduced_val2);
677 for (int j = 0; j < packet_width / 2; j++) {
678 hr1[j] = __shfl_down_sync(0xFFFFFFFF, rr1[j], (unsigned)offset, warpSize);
679 hr2[j] = __shfl_down_sync(0xFFFFFFFF, rr2[j], (unsigned)offset, warpSize);
680 }
681 reducer.reducePacket(r1, &reduced_val1);
682 reducer.reducePacket(r2, &reduced_val2);
683
684#endif
685 }
686 half2* rv1 = reinterpret_cast<half2*>(&reduced_val1);
687 half2* rv2 = reinterpret_cast<half2*>(&reduced_val2);
688 half2 val;
689 if (packet_width > 2) {
690 reducer.reducePacket(rv1[2], rv1);
691 reducer.reducePacket(rv1[3], rv1 + 1);
692 reducer.reducePacket(rv1[1], rv1);
693 reducer.reducePacket(rv2[2], rv2);
694 reducer.reducePacket(rv2[3], rv2 + 1);
695 reducer.reducePacket(rv2[1], rv2);
696 }
697 half val1 = __low2half(*rv1);
698 reducer.reduce(__high2half(*rv1), &val1);
699 half val2 = __low2half(*rv2);
700 reducer.reduce(__high2half(*rv2), &val2);
701 val = __halves2half2(val1, val2);
702 if ((threadIdx.x & (warpSize - 1)) == 0) {
703 half* loc = output + row;
704 atomicReduce(reinterpret_cast<half2*>(loc), val, reducer);
705 }
706 }
707 }
708}
709
710template <typename Self, typename Op, typename OutputType, bool PacketAccess, typename Enabled = void>
711struct InnerReductionLauncher {
712 static EIGEN_DEVICE_FUNC bool run(const Self&, Op&, const GpuDevice&, OutputType*, typename Self::Index,
713 typename Self::Index) {
714 gpu_assert(false && "Should only be called to reduce doubles, floats and half floats on a gpu device");
715 return true;
716 }
717};
718
719// Specialization for float and double
720template <typename Self, typename Op, typename OutputType, bool PacketAccess>
721struct InnerReductionLauncher<
722 Self, Op, OutputType, PacketAccess,
723 std::enable_if_t<std::is_same<float, OutputType>::value || std::is_same<double, OutputType>::value, void>> {
724 static bool run(const Self& self, Op& reducer, const GpuDevice& device, OutputType* output,
725 typename Self::Index num_coeffs_to_reduce, typename Self::Index num_preserved_vals) {
726 typedef typename Self::Index Index;
727
728 const Index num_coeffs = num_coeffs_to_reduce * num_preserved_vals;
729 const int block_size = 256;
730 const int num_per_thread = 128;
731 const int dyn_blocks = numext::div_ceil<int>(num_coeffs, block_size * num_per_thread);
732 const int max_blocks = device.getNumGpuMultiProcessors() * device.maxGpuThreadsPerMultiProcessor() / block_size;
733 const int num_blocks = numext::mini<int>(max_blocks, dyn_blocks);
734
735 if (num_blocks > 1) {
736 // We initialize the outputs outside the reduction kernel when we can't be sure that there
737 // won't be race conditions between multiple thread blocks.
738 const int dyn_blocks2 = numext::div_ceil<int>(num_preserved_vals, 1024);
739 const int max_blocks2 = device.getNumGpuMultiProcessors() * device.maxGpuThreadsPerMultiProcessor() / 1024;
740 const int num_blocks2 = numext::mini<int>(max_blocks2, dyn_blocks2);
741 LAUNCH_GPU_KERNEL((ReductionInitKernel<OutputType, Index>), num_blocks2, 1024, 0, device, reducer.initialize(),
742 num_preserved_vals, output);
743 }
744
745 LAUNCH_GPU_KERNEL((InnerReductionKernel<num_per_thread, Self, Op, Index>), num_blocks, block_size, 0, device,
746 reducer, self, num_coeffs_to_reduce, num_preserved_vals, output);
747
748 return false;
749 }
750};
751
752// Half-float reduction specializations.
753template <typename Self, typename Op>
754struct InnerReductionLauncher<Self, Op, Eigen::half, false> {
755 static bool run(const Self&, Op&, const GpuDevice&, half*, typename Self::Index, typename Self::Index) {
756 gpu_assert(false && "Should not be called since there is no packet accessor");
757 return true;
758 }
759};
760
761template <typename Self, typename Op>
762struct InnerReductionLauncher<Self, Op, Eigen::half, true> {
763 static bool run(const Self& self, Op& reducer, const GpuDevice& device, half* output,
764 typename Self::Index num_coeffs_to_reduce, typename Self::Index num_preserved_vals) {
765 typedef typename Self::Index Index;
766
767 if (num_preserved_vals % 2 != 0) {
768 // Not supported yet, revert to the slower code path
769 return true;
770 }
771
772 const Index num_coeffs = num_coeffs_to_reduce * num_preserved_vals;
773 const int block_size = 128;
774 const int num_per_thread = 64;
775 const int dyn_blocks = numext::div_ceil<int>(num_coeffs, block_size * num_per_thread);
776 const int max_blocks = device.getNumGpuMultiProcessors() * device.maxGpuThreadsPerMultiProcessor() / block_size;
777 const int num_blocks = numext::mini<int>(max_blocks, dyn_blocks);
778
779 if (num_blocks > 1) {
780 // We initialize the outputs outside the reduction kernel when we can't be sure that there
781 // won't be race conditions between multiple thread blocks.
782 LAUNCH_GPU_KERNEL((ReductionInitKernelHalfFloat<Self, Op, Index>), 1, 1, 0, device, reducer, self,
783 num_preserved_vals, output);
784 }
785
786 LAUNCH_GPU_KERNEL((InnerReductionKernelHalfFloat<num_per_thread, Self, Op, Index>), num_blocks, block_size, 0,
787 device, reducer, self, num_coeffs_to_reduce, num_preserved_vals, output);
788
789 return false;
790 }
791};
792
793template <typename Self, typename Op>
794struct InnerReducer<Self, Op, GpuDevice> {
795 // Unfortunately nvidia doesn't support well exotic types such as complex,
796 // so reduce the scope of the optimized version of the code to the simple case
797 // of floats and half floats.
798 // Half-float reduction specializations.
799 static constexpr bool HasOptimizedImplementation =
800 !Self::ReducerTraits::IsStateful && (std::is_same<typename Self::CoeffReturnType, float>::value ||
801 std::is_same<typename Self::CoeffReturnType, double>::value ||
802 (std::is_same<typename Self::CoeffReturnType, Eigen::half>::value &&
803 reducer_traits<Op, GpuDevice>::PacketAccess));
804
805 template <typename OutputType>
806 static bool run(const Self& self, Op& reducer, const GpuDevice& device, OutputType* output,
807 typename Self::Index num_coeffs_to_reduce, typename Self::Index num_preserved_vals) {
808 gpu_assert(HasOptimizedImplementation && "Should only be called on doubles, floats or half floats");
809 const Index num_coeffs = array_prod(self.m_impl.dimensions());
810 // Don't crash when we're called with an input tensor of size 0.
811 if (num_coeffs == 0) {
812 return true;
813 }
814 // It's faster to use the usual code.
815 if (num_coeffs_to_reduce <= 128) {
816 return true;
817 }
818
819 return InnerReductionLauncher<Self, Op, OutputType, reducer_traits<Op, GpuDevice>::PacketAccess>::run(
820 self, reducer, device, output, num_coeffs_to_reduce, num_preserved_vals);
821 }
822};
823
824template <int NumPerThread, typename Self, typename Reducer, typename Index>
825__global__ EIGEN_HIP_LAUNCH_BOUNDS_1024 void OuterReductionKernel(Reducer reducer, const Self input,
826 Index num_coeffs_to_reduce,
827 Index num_preserved_coeffs,
828 typename Self::CoeffReturnType* output) {
829 const Index num_threads = blockDim.x * gridDim.x;
830 const Index thread_id = blockIdx.x * blockDim.x + threadIdx.x;
831 // Initialize the output values if they weren't initialized by the ReductionInitKernel
832 if (gridDim.x == 1) {
833 for (Index i = thread_id; i < num_preserved_coeffs; i += num_threads) {
834 output[i] = reducer.initialize();
835 }
836 __syncthreads();
837 }
838
839 // Do the reduction.
840 const Index max_iter = num_preserved_coeffs * numext::div_ceil<Index>(num_coeffs_to_reduce, NumPerThread);
841 for (Index i = thread_id; i < max_iter; i += num_threads) {
842 const Index input_col = i % num_preserved_coeffs;
843 const Index input_row = (i / num_preserved_coeffs) * NumPerThread;
844 typename Self::CoeffReturnType reduced_val = reducer.initialize();
845 const Index max_row = numext::mini(input_row + NumPerThread, num_coeffs_to_reduce);
846 for (Index j = input_row; j < max_row; j++) {
847 typename Self::CoeffReturnType val = input.m_impl.coeff(j * num_preserved_coeffs + input_col);
848 reducer.reduce(val, &reduced_val);
849 }
850 atomicReduce(&(output[input_col]), reduced_val, reducer);
851 }
852}
853
854template <typename Self, typename Op>
855struct OuterReducer<Self, Op, GpuDevice> {
856 // Unfortunately nvidia doesn't support well exotic types such as complex,
857 // so reduce the scope of the optimized version of the code to the simple case
858 // of floats and doubles.
859 template <typename T>
860 using IsFloatOrDouble = bool_constant<std::is_same<T, float>::value || std::is_same<T, double>::value>;
861
862 static constexpr bool HasOptimizedImplementation =
863 !Self::ReducerTraits::IsStateful && IsFloatOrDouble<typename Self::CoeffReturnType>::value;
864 template <typename Device, typename OutputType>
865 static
866#if !defined(EIGEN_HIPCC)
867 // FIXME : leaving this EIGEN_DEVICE_FUNC in, results in the following runtime error
868 // (in the tensor_reduction_gpu test)
869 //
870 // terminate called after throwing an instance of 'std::runtime_error'
871 // what(): No device code available for function: _ZN5Eigen8internal20OuterReductionKernelIL...
872 //
873 // don't know why this happens (and why is it a runtime error instead of a compile time error)
874 //
875 // this will be fixed by HIP PR#457
876 EIGEN_DEVICE_FUNC
877#endif
878 bool
879 run(const Self&, Op&, const Device&, OutputType*, typename Self::Index, typename Self::Index) {
880 gpu_assert(false && "Should only be called to reduce doubles or floats on a gpu device");
881 return true;
882 }
883
884 // OuterReductionKernel is generic in CoeffReturnType, so one launch serves both types that
885 // HasOptimizedImplementation admits. The enable_if keeps every other OutputType on the overload above: the
886 // caller's EIGEN_IF_CONSTEXPR is a plain `if` in C++14, so the call is instantiated for those types as well.
887 template <typename OutputType, EIGEN_SFINAE_ENABLE_IF(IsFloatOrDouble<OutputType>::value)>
888 static bool run(const Self& self, Op& reducer, const GpuDevice& device, OutputType* output,
889 typename Self::Index num_coeffs_to_reduce, typename Self::Index num_preserved_vals) {
890 typedef typename Self::Index Index;
891
892 // It's faster to use the usual code.
893 if (num_coeffs_to_reduce <= 32) {
894 return true;
895 }
896
897 // CUDA has native FP64 atomicAdd on every supported architecture (sm_60+). Keep the measured shape band
898 // for the other double reducers and for HIP, where atomicAdd may still use compare-and-swap.
899#if defined(EIGEN_CUDACC)
900 constexpr bool has_native_double_sum = std::is_same<Op, SumReducer<double>>::value;
901#else
902 constexpr bool has_native_double_sum = false;
903#endif
904 if (std::is_same<OutputType, double>::value && !has_native_double_sum) {
905 const Index multi_processors = device.getNumGpuMultiProcessors();
906 if (num_coeffs_to_reduce < 64 || num_preserved_vals < 8 * multi_processors ||
907 num_preserved_vals > 64 * multi_processors) {
908 return true;
909 }
910 }
911
912 const Index num_coeffs = num_coeffs_to_reduce * num_preserved_vals;
913 const int block_size = 256;
914 const int num_per_thread = 16;
915 const int dyn_blocks = numext::div_ceil<int>(num_coeffs, block_size * num_per_thread);
916 const int max_blocks = device.getNumGpuMultiProcessors() * device.maxGpuThreadsPerMultiProcessor() / block_size;
917 const int num_blocks = numext::mini<int>(max_blocks, dyn_blocks);
918
919 if (num_blocks > 1) {
920 // We initialize the outputs outside the reduction kernel when we can't be sure that there
921 // won't be race conditions between multiple thread blocks.
922 const int dyn_blocks2 = numext::div_ceil<int>(num_preserved_vals, 1024);
923 const int max_blocks2 = device.getNumGpuMultiProcessors() * device.maxGpuThreadsPerMultiProcessor() / 1024;
924 const int num_blocks2 = numext::mini<int>(max_blocks2, dyn_blocks2);
925 LAUNCH_GPU_KERNEL((ReductionInitKernel<OutputType, Index>), num_blocks2, 1024, 0, device, reducer.initialize(),
926 num_preserved_vals, output);
927 }
928
929 LAUNCH_GPU_KERNEL((OuterReductionKernel<num_per_thread, Self, Op, Index>), num_blocks, block_size, 0, device,
930 reducer, self, num_coeffs_to_reduce, num_preserved_vals, output);
931
932 return false;
933 }
934};
935
936#endif // defined(EIGEN_USE_GPU) && defined(EIGEN_GPUCC)
937
938} // end namespace internal
939} // end namespace Eigen
940
941#endif // EIGEN_TENSOR_TENSOR_REDUCTION_GPU_H
Namespace containing all symbols from the Eigen library.