blob: a70d7ff40cbf0a38b9be67524f5da84b204f3f86 [file] [edit]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
#define EIGEN_USE_GPU
#define EIGEN_TEST_NO_LONGDOUBLE
#define EIGEN_TEST_NO_COMPLEX
#include "main.h"
#include <contrib/Eigen/Tensor>
template <int Layout, int NumDims, typename TensorIndex>
void verify_gpu_convolution(const Tensor<float, NumDims, Layout, TensorIndex>& input,
const Tensor<float, 3, Layout, TensorIndex>& kernel, const array<TensorIndex, 3>& axes,
const Tensor<float, NumDims, Layout, TensorIndex>& expected) {
GpuStreamDevice stream;
GpuDevice device(&stream);
bool nonzero = false, varying = false;
for (Index i = 0; i < expected.size(); ++i) {
nonzero |= expected.data()[i] != 0;
varying |= expected.data()[i] != expected.data()[0];
}
VERIFY(nonzero);
VERIFY(varying);
Tensor<float, NumDims, Layout, TensorIndex> result;
result.resize(expected.dimensions());
float* a = static_cast<float*>(device.allocate(input.size() * sizeof(float)));
float* k = static_cast<float*>(device.allocate(kernel.size() * sizeof(float)));
float* out = static_cast<float*>(device.allocate(result.size() * sizeof(float)));
device.memcpyHostToDevice(a, input.data(), input.size() * sizeof(float));
device.memcpyHostToDevice(k, kernel.data(), kernel.size() * sizeof(float));
// NaN sentinels make missing output writes fail even when the reference is zero.
device.memset(out, 0xff, result.size() * sizeof(float));
TensorMap<Tensor<float, NumDims, Layout, TensorIndex>> gpu_input(a, input.dimensions());
TensorMap<Tensor<float, 3, Layout, TensorIndex>> gpu_kernel(k, kernel.dimensions());
TensorMap<Tensor<float, NumDims, Layout, TensorIndex>> gpu_result(out, result.dimensions());
gpu_result.device(device) = gpu_input.convolve(gpu_kernel, axes);
device.memcpyDeviceToHost(result.data(), out, result.size() * sizeof(float));
device.synchronize();
for (Index i = 0; i < result.size(); ++i) VERIFY_IS_EQUAL(result.data()[i], expected.data()[i]);
device.deallocate(a);
device.deallocate(k);
device.deallocate(out);
}
template <int Layout>
void check_large_convolution(const array<Index, 3>& kernel_dims, const array<Index, 3>& axes) {
array<Index, 5> input_dims{{2, 3, 2, 3, 2}};
for (int i = 0; i < 3; ++i) input_dims[axes[i]] = kernel_dims[i] + i + 1;
Tensor<float, 5, Layout> input(input_dims);
Tensor<float, 3, Layout> kernel(kernel_dims);
// Small integers keep every product and partial sum exactly representable in float.
for (Index i = 0; i < input.size(); ++i) input.data()[i] = float((i * 17 + i / 7) % 19 - 9);
for (Index i = 0; i < kernel.size(); ++i) kernel.data()[i] = float((i * 7 + i / 5) % 7 - 2);
Tensor<float, 5, Layout> expected = input.convolve(kernel, axes);
verify_gpu_convolution(input, kernel, axes, expected);
}
template <int Layout, typename TensorIndex = Index>
void check_convolution_grid_stride(Index output_size = 0) {
GpuStreamDevice stream;
GpuDevice device(&stream);
const Index kernel_size = device.sharedMemPerBlock() / sizeof(float) + 1;
const Index threads = numext::mini(256, device.maxGpuThreadsPerBlock());
const Index grid_stride = Index(device.getNumGpuMultiProcessors()) * 32 * threads;
if (output_size == 0) output_size = 2 * grid_stride + 17;
const Index max_input =
static_cast<Index>(numext::mini<int64_t>(NumTraits<Index>::highest(), NumTraits<TensorIndex>::highest()));
const Index max_output = max_input - kernel_size + 1;
// Some devices cannot fit an oversized convolution's input in this narrow index type.
if (max_output < 2) return;
output_size = numext::mini(output_size, max_output);
const array<TensorIndex, 3> axes{{0, 1, 2}};
Tensor<float, 3, Layout, TensorIndex> input(1, 1, TensorIndex(kernel_size + output_size - 1));
Tensor<float, 3, Layout, TensorIndex> kernel(1, 1, TensorIndex(kernel_size));
Tensor<float, 3, Layout, TensorIndex> expected(1, 1, TensorIndex(output_size));
input.setConstant(1);
input.data()[kernel_size + output_size / 2 - 1] = 2;
kernel.setConstant(1);
// An independent sliding sum avoids a CPU convolution of O(kernel_size * output_size).
Index sum = kernel_size;
for (Index i = 0; i < output_size; ++i) {
expected.data()[i] = float(sum);
if (i + 1 < output_size) sum += Index(input.data()[i + kernel_size]) - Index(input.data()[i]);
}
if (std::is_same<TensorIndex, Index>::value) VERIFY(output_size > grid_stride);
verify_gpu_convolution(input, kernel, axes, expected);
}
EIGEN_DECLARE_TEST(tensor_convolution_large_gpu) {
GpuStreamDevice stream;
GpuDevice device(&stream);
// Always exceed this device's shared-memory limit, including newer GPUs.
const Index oversized = device.sharedMemPerBlock() / (sizeof(float) * 3 * 50) + 1;
const Index capacity = device.sharedMemPerBlock() / sizeof(float);
const array<Index, 3> shapes[] = {{{3, 4, 2}}, {{3, 50, 120}}, {{3, 50, 145}},
{{3, 50, oversized}}, {{1, 1, capacity - 1}}, {{1, 1, capacity}},
{{1, 1, capacity + 1}}};
const array<Index, 3> axes[] = {{{1, 2, 3}}, {{0, 2, 4}}, {{4, 0, 2}}};
for (const auto& shape : shapes) {
for (const auto& axis : axes) {
CALL_SUBTEST(check_large_convolution<ColMajor>(shape, axis));
CALL_SUBTEST(check_large_convolution<RowMajor>(shape, axis));
}
}
CALL_SUBTEST(check_convolution_grid_stride<ColMajor>());
CALL_SUBTEST(check_convolution_grid_stride<RowMajor>());
CALL_SUBTEST((check_convolution_grid_stride<ColMajor, int16_t>(18000)));
CALL_SUBTEST((check_convolution_grid_stride<RowMajor, int16_t>(18000)));
}