Files
TheRock/rocm-libraries/projects/hipfft/clients/tests/simple_test.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

827 lines
31 KiB
C++

// Copyright (c) 2018 - 2022 Advanced Micro Devices, Inc. All rights
// reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "hipfft/hipfft.h"
#include <fftw3.h>
#include <gtest/gtest.h>
#include <hip/hip_vector_types.h>
#include <random>
#include <vector>
#include "../../shared/fftw_transform.h"
#include "../../shared/params_gen.h"
#include "../hipfft_params.h"
DISABLE_WARNING_PUSH
DISABLE_WARNING_DEPRECATED_DECLARATIONS
DISABLE_WARNING_RETURN_TYPE
#include <hip/hip_runtime_api.h>
DISABLE_WARNING_POP
// Function to return maximum error for float and double types for
// the simple tests below
template <typename Tfloat>
inline double type_epsilon_simple();
template <>
inline double type_epsilon_simple<float>()
{
return 1e-6;
}
template <>
inline double type_epsilon_simple<double>()
{
return 1e-7;
}
/* Static utility class template helping with the definition of valid/invalid
values for arguments of (un)scoped enumeration types in API testing.
Usage: for an enumeration type of interest, say "enum_of_interest", define
template <>
const std::vector<enum_of_interest> enum_helper<enum_of_interest>::valid_values =
{all, the, known, valid, values, of, type, enum_of_interest};
before using any of this class' self-explanatory public member functions.
*/
template <typename T, std::enable_if_t<std::is_enum_v<T>, bool> = true>
class enum_helper
{
using base_t = typename std::underlying_type<T>::type;
// static class cannot be instantiated, copied, or moved
enum_helper() = delete;
~enum_helper() = delete;
enum_helper(const enum_helper&) = delete;
enum_helper(enum_helper&& other) = delete;
enum_helper& operator=(const enum_helper&) = delete;
enum_helper& operator=(enum_helper&& other) = delete;
public:
static const std::vector<T> valid_values;
static bool has_value(const T& val)
{
return std::any_of(
valid_values.begin(), valid_values.end(), [&](const T& v) { return v == val; });
}
static size_t num_valid_values()
{
return valid_values.size();
}
static T get_any_valid_value(size_t prng_seed = 0)
{
const size_t nvals = num_valid_values();
if(nvals == 0)
throw std::runtime_error(
"enum_helper::get_any_valid_value: no valid value is defined.");
std::ranlux24_base gen(prng_seed);
return valid_values[static_cast<size_t>(gen()) % nvals];
}
static T get_invalid_value(size_t prng_seed = 0)
{
constexpr base_t max_base_val = std::numeric_limits<base_t>::max();
constexpr base_t min_base_val = std::numeric_limits<base_t>::min();
std::ranlux24_base gen(prng_seed);
std::uniform_int_distribution<base_t> dis(min_base_val, max_base_val);
// limit number of attempts to 10x the number of possible value
size_t num_attempts = 0;
T made_up_value;
auto generate_candidate = [&]() {
num_attempts++;
made_up_value = static_cast<T>(dis(gen));
return;
};
generate_candidate();
while(has_value(made_up_value) && num_attempts < 10 * num_valid_values())
{
generate_candidate();
}
if(has_value(made_up_value))
throw std::runtime_error(
"enum_helper::get_invalid_value failed to generate an invalid valid");
return made_up_value;
}
};
// definition of valid values for various enum types
template <>
const std::vector<hipfftLibraryPropertyType> enum_helper<hipfftLibraryPropertyType>::valid_values
= {hipfftLibraryPropertyType::HIPFFT_MAJOR_VERSION,
hipfftLibraryPropertyType::HIPFFT_MINOR_VERSION,
hipfftLibraryPropertyType::HIPFFT_PATCH_LEVEL};
TEST(hipfftTest, Create1dPlan)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
size_t length = 1024;
ASSERT_EQ(hipfftPlan1d(&plan, length, HIPFFT_C2C, 1), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, CreatePlanMany)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
int const rank = 3;
int const nX = 64;
int const nY = 128;
int const nZ = 23;
int n[3] = {nX, nY, nZ};
int inembed[3] = {nX, nY, nZ};
int* inembed_null = nullptr;
int const istride = 1;
int const idist = nX * nY * nZ;
int onembed[3] = {nX, nY, nZ};
int* onembed_null = nullptr;
int const ostride = 1;
int const odist = nX * nY * nZ;
hipfftType type = HIPFFT_C2C;
int const batch = 1000;
size_t workSize;
// Tests plan creation with null and not null
// combinations of inembed and onembed.
//
// Valid combinations:
// inembed == null && onembed == null
// or
// inembed != null && onembed != null
//
// otherwise HIPFFT_INVALID_VALUE should be
// returned to maintain compatibility with cuFFT
// inembed == null && onembed == null
{
hipfftHandle plan_valid_1 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_valid_1), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany(plan_valid_1,
rank,
(int*)n,
inembed_null,
istride,
idist,
onembed_null,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS)
<< "inembed == null && onembed == null failed: " << hipfftResult_string(ret_hipfft);
ASSERT_EQ(hipfftSetAutoAllocation(plan_valid_1, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan_valid_1), HIPFFT_SUCCESS);
}
// inembed != null && onembed != null
{
hipfftHandle plan_valid_2 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_valid_2), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany(plan_valid_2,
rank,
(int*)n,
inembed,
istride,
idist,
onembed,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS)
<< "inembed != null && onembed != null failed: " << hipfftResult_string(ret_hipfft);
ASSERT_EQ(hipfftSetAutoAllocation(plan_valid_2, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan_valid_2), HIPFFT_SUCCESS);
}
// inembed != null && onembed == null
{
hipfftHandle plan_invalid_1 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_invalid_1), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany(plan_invalid_1,
rank,
(int*)n,
inembed,
istride,
idist,
onembed_null,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_VALUE)
<< "inembed != null && onembed == null failed: " << hipfftResult_string(ret_hipfft);
ASSERT_EQ(hipfftDestroy(plan_invalid_1), HIPFFT_SUCCESS);
}
// inembed == null && onembed != null
{
hipfftHandle plan_invalid_2 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_invalid_2), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany(plan_invalid_2,
rank,
(int*)n,
inembed_null,
istride,
idist,
onembed,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_VALUE)
<< "inembed == null && onembed != null failed: " << hipfftResult_string(ret_hipfft);
ASSERT_EQ(hipfftDestroy(plan_invalid_2), HIPFFT_SUCCESS);
}
}
TEST(hipfftTest, CreatePlanMany64)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
int const rank = 3;
long long int const nX = 64;
long long int const nY = 128;
long long int const nZ = 23;
long long int n[3] = {nX, nY, nZ};
long long int inembed[3] = {nX, nY, nZ};
long long int const istride = 1;
long long int const idist = nX * nY * nZ;
long long int onembed[3] = {nX, nY, nZ};
long long int onembed_invalid[3] = {nX, nY, -nZ};
long long int const ostride = 1;
long long int const odist = nX * nY * nZ;
hipfftType type = HIPFFT_C2C;
long long int const batch = 1000;
long long int const batch_invalid = -2;
size_t workSize;
// Tests the 64-bit version of plan creation
// with valid/invalid data layouts.
// First test with a valid data layout
{
hipfftHandle plan_valid = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_valid), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany64(plan_valid,
rank,
(long long int*)n,
inembed,
istride,
idist,
onembed,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS);
ASSERT_EQ(hipfftSetAutoAllocation(plan_valid, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan_valid), HIPFFT_SUCCESS);
}
// invalid data layout (n array has a negative entry). only test rocFFT
// backend, since it's more strict
#ifdef __HIP_PLATFORM_AMD__
long long int n_invalid[3] = {nX, -nY, nZ};
{
hipfftHandle plan_invalid_1 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_invalid_1), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany64(plan_invalid_1,
rank,
(long long int*)n_invalid,
inembed,
istride,
idist,
onembed,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_VALUE);
ASSERT_EQ(hipfftSetAutoAllocation(plan_invalid_1, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan_invalid_1), HIPFFT_SUCCESS);
}
#endif
// invalid data layout (onembed array has a negative entry)
{
hipfftHandle plan_invalid_2 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_invalid_2), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany64(plan_invalid_2,
rank,
(long long int*)n,
inembed,
istride,
idist,
onembed_invalid,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_SIZE);
ASSERT_EQ(hipfftSetAutoAllocation(plan_invalid_2, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan_invalid_2), HIPFFT_SUCCESS);
}
// invalid data layout (batch is negative)
{
hipfftHandle plan_invalid_3 = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan_invalid_3), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftMakePlanMany64(plan_invalid_3,
rank,
(long long int*)n,
inembed,
istride,
idist,
onembed,
ostride,
odist,
type,
batch_invalid,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_INVALID_SIZE);
ASSERT_EQ(hipfftSetAutoAllocation(plan_invalid_3, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan_invalid_3), HIPFFT_SUCCESS);
}
}
TEST(hipfftTest, hipfftGetSizeMany)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
int const rank = 3;
int const nX = 33;
int const nY = 128;
int const nZ = 100;
int n[3] = {nX, nY, nZ};
int inembed[3] = {nX, nY, nZ};
int const istride = 1;
int const idist = nX * nY * nZ;
int onembed[3] = {nX, nY, nZ};
int const ostride = 1;
int const odist = nX * nY * nZ;
hipfftType type = HIPFFT_C2C;
int const batch = 1;
size_t workSize;
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftGetSizeMany(plan,
rank,
(int*)n,
inembed,
istride,
idist,
onembed,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS);
ASSERT_EQ(hipfftSetAutoAllocation(plan, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, hipfftGetSizeMany64)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
int const rank = 3;
long long int const nX = 133;
long long int const nY = 354;
long long int const nZ = 256;
long long int n[3] = {nX, nY, nZ};
long long int inembed[3] = {nX, nY, nZ};
long long int const istride = 1;
long long int const idist = nX * nY * nZ;
long long int onembed[3] = {nX, nY, nZ};
long long int const ostride = 1;
long long int const odist = nX * nY * nZ;
hipfftType type = HIPFFT_C2C;
long long int const batch = 2;
size_t workSize;
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
auto ret_hipfft = hipfftGetSizeMany64(plan,
rank,
(long long int*)n,
inembed,
istride,
idist,
onembed,
ostride,
odist,
type,
batch,
&workSize);
ASSERT_EQ(ret_hipfft, HIPFFT_SUCCESS);
ASSERT_EQ(hipfftSetAutoAllocation(plan, 0), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, CheckBufferSizeC2C)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
size_t n = 1024;
size_t workSize = 0;
ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_C2C, 1, &workSize), HIPFFT_SUCCESS);
#ifdef __HIP_PLATFORM_AMD__
// No extra work buffer for C2C
EXPECT_EQ(workSize, 0);
#endif
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, CheckBufferSizeR2C)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
// real forward transform cannot modify input, so we need to pick
// a sufficiently small N such that rocFFT can fuse
// post-processing into one kernel and avoid a temp buffer
size_t n = 256;
size_t workSize = 0;
ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_R2C, 1, &workSize), HIPFFT_SUCCESS);
#ifdef __HIP_PLATFORM_AMD__
// NOTE: keep this condition for ease of changing n for ad-hoc tests
//
// cppcheck-suppress knownConditionTrueFalse
if(n % 2 == 0)
{
EXPECT_EQ(workSize, 0);
}
else
{
EXPECT_EQ(workSize, 2 * n * sizeof(float));
}
#endif
EXPECT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, CheckBufferSizeC2R)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
size_t n = 2048;
size_t workSize = 0;
ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_C2R, 1, &workSize), HIPFFT_SUCCESS);
#ifdef __HIP_PLATFORM_AMD__
// NOTE: keep this condition for ease of changing n for ad-hoc tests
//
// cppcheck-suppress knownConditionTrueFalse
if(n % 2 == 0)
{
EXPECT_EQ(workSize, 0);
}
else
{
EXPECT_EQ(workSize, 2 * n * sizeof(float));
}
#endif
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, CheckBufferSizeD2Z)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
// real forward transform cannot modify input, so we need to pick
// a sufficiently small N such that rocFFT can fuse
// post-processing into one kernel and avoid a temp buffer
size_t n = 256;
size_t batch = 1000;
size_t workSize = 0;
ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_D2Z, batch, &workSize), HIPFFT_SUCCESS);
#ifdef __HIP_PLATFORM_AMD__
// NOTE: keep this condition for ease of changing n for ad-hoc tests
//
// cppcheck-suppress knownConditionTrueFalse
if(n % 2 == 0)
{
EXPECT_EQ(workSize, 0);
}
else
{
EXPECT_EQ(workSize, 2 * n * sizeof(double));
}
#endif
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
TEST(hipfftTest, CheckBufferSizeZ2D)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
size_t n = 2048;
size_t batch = 1000;
size_t workSize = 0;
ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_Z2D, batch, &workSize), HIPFFT_SUCCESS);
#ifdef __HIP_PLATFORM_AMD__
// NOTE: keep this condition for ease of changing n for ad-hoc tests
//
// cppcheck-suppress knownConditionTrueFalse
if(n % 2 == 0)
{
EXPECT_EQ(workSize, 0);
}
else
{
EXPECT_EQ(workSize, 2 * n * sizeof(double));
}
#endif
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
#ifdef __HIP_PLATFORM_AMD__
TEST(hipfftTest, CheckNullWorkBuffer)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
size_t n = 2048;
size_t batch = 1000;
size_t workSize = 0;
ASSERT_EQ(hipfftMakePlan1d(plan, n, HIPFFT_Z2D, batch, &workSize), HIPFFT_SUCCESS);
EXPECT_EQ(hipfftSetWorkArea(plan, nullptr), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
}
#endif
TEST(hipfftTest, RunR2C)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
const size_t N = 4096;
float in[N];
for(size_t i = 0; i < N; i++)
in[i] = i + (i % 3) - (i % 7);
hipfftReal* d_in;
hipfftComplex* d_out;
ASSERT_EQ(hipMalloc(&d_in, N * sizeof(hipfftReal)), hipSuccess);
ASSERT_EQ(hipMalloc(&d_out, (N / 2 + 1) * sizeof(hipfftComplex)), hipSuccess);
ASSERT_EQ(hipMemcpy(d_in, in, N * sizeof(hipfftReal), hipMemcpyHostToDevice), hipSuccess);
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
size_t workSize;
ASSERT_EQ(hipfftMakePlan1d(plan, N, HIPFFT_R2C, 1, &workSize), HIPFFT_SUCCESS);
EXPECT_EQ(hipfftExecR2C(plan, d_in, d_out), HIPFFT_SUCCESS);
std::vector<hipfftComplex> out(N / 2 + 1);
ASSERT_EQ(hipMemcpy(&out[0], d_out, (N / 2 + 1) * sizeof(hipfftComplex), hipMemcpyDeviceToHost),
hipSuccess);
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
ASSERT_EQ(hipFree(d_in), hipSuccess);
ASSERT_EQ(hipFree(d_out), hipSuccess);
;
// NOTE: keep this condition for ease of changing n for ad-hoc tests
//
// cppcheck-suppress knownConditionTrueFalse
if(N % 2 != 0)
{
EXPECT_TRUE(workSize != 0);
}
double ref_in[N];
for(size_t i = 0; i < N; i++)
ref_in[i] = in[i];
fftw_complex* ref_out;
ref_out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * (N / 2 + 1));
fftw_plan_wrapper_t<double> ref_p
= fftw_trait<double>::make_wrapper(fftw_plan_dft_r2c_1d(N, ref_in, ref_out, FFTW_ESTIMATE));
fftw_execute_type<double>(ref_p);
double maxv = 0;
double nrmse = 0; // normalized root mean square error
for(size_t i = 0; i < (N / 2 + 1); i++)
{
// printf("element %d: FFTW result %f, %f; hipFFT result %f, %f \n", (int)i, ref_out[i][0], ref_out[i][1], out[i].x, out[i].y);
double dr = ref_out[i][0] - out[i].x;
double di = ref_out[i][1] - out[i].y;
maxv = fabs(ref_out[i][0]) > maxv ? fabs(ref_out[i][0]) : maxv;
maxv = fabs(ref_out[i][1]) > maxv ? fabs(ref_out[i][1]) : maxv;
nrmse += ((dr * dr) + (di * di));
}
nrmse /= (double)((N / 2 + 1));
nrmse = sqrt(nrmse);
nrmse /= maxv;
fftw_free(ref_out);
EXPECT_LT(nrmse, type_epsilon_simple<double>());
}
// ask for a transform whose parameters are only valid out-of-place.
// since hipFFT generates both in-place and out-place plans up front
// (because it's not told about the placement until exec time), this
// ensures that a failure to create an in-place plan doesn't prevent
// the out-place plan from working.
TEST(hipfftTest, OutplaceOnly)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
static const int N_in_const = 4;
static const int N_out_const = N_in_const / 2 + 1;
// mutable sizes for passing to hipFFT
int N_in = N_in_const;
int N_out = N_out_const;
float in[N_in_const];
for(int i = 0; i < N_in; i++)
in[i] = i + (i % 3) - (i % 7);
hipfftReal* d_in;
hipfftComplex* d_out;
ASSERT_EQ(hipMalloc(&d_in, N_in * sizeof(hipfftReal)), hipSuccess);
ASSERT_EQ(hipMalloc(&d_out, N_out * sizeof(hipfftComplex)), hipSuccess);
ASSERT_EQ(hipMemcpy(d_in, in, N_in * sizeof(hipfftReal), hipMemcpyHostToDevice), hipSuccess);
hipfftHandle plan = hipfft_params::INVALID_PLAN_HANDLE;
ASSERT_EQ(hipfftCreate(&plan), HIPFFT_SUCCESS);
ASSERT_EQ(hipfftPlanMany(&plan, 1, &N_in, &N_in, 1, N_in, &N_out, 1, N_out, HIPFFT_R2C, 1),
HIPFFT_SUCCESS);
ASSERT_EQ(plan == hipfft_params::INVALID_PLAN_HANDLE, false);
ASSERT_EQ(hipfftExecR2C(plan, d_in, d_out), HIPFFT_SUCCESS) << "hipfftExecR2C failed";
std::vector<hipfftComplex> out(N_out);
ASSERT_EQ(hipMemcpy(out.data(), d_out, N_out * sizeof(hipfftComplex), hipMemcpyDeviceToHost),
hipSuccess);
// in-place transform isn't really *supposed* to work - this
// might or might not fail but we can at least check that it
// doesn't blow up.
//hipfftExecR2C(plan, reinterpret_cast<hipfftReal*>(d_out), d_out);
ASSERT_EQ(hipfftDestroy(plan), HIPFFT_SUCCESS);
ASSERT_EQ(hipFree(d_in), hipSuccess);
ASSERT_EQ(hipFree(d_out), hipSuccess);
double ref_in[N_in_const];
for(int i = 0; i < N_in_const; i++)
ref_in[i] = in[i];
fftw_complex* ref_out;
ref_out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * N_out);
fftw_plan_wrapper_t<double> ref_p = fftw_trait<double>::make_wrapper(
fftw_plan_dft_r2c_1d(N_in, ref_in, ref_out, FFTW_ESTIMATE));
fftw_execute_type<double>(ref_p);
double maxv = 0;
double nrmse = 0; // normalized root mean square error
for(int i = 0; i < N_out; i++)
{
// printf("element %d: FFTW result %f, %f; hipFFT result %f, %f \n", (int)i, ref_out[i][0], ref_out[i][1], out[i].x, out[i].y);
double dr = ref_out[i][0] - out[i].x;
double di = ref_out[i][1] - out[i].y;
maxv = fabs(ref_out[i][0]) > maxv ? fabs(ref_out[i][0]) : maxv;
maxv = fabs(ref_out[i][1]) > maxv ? fabs(ref_out[i][1]) : maxv;
nrmse += ((dr * dr) + (di * di));
}
nrmse /= (double)(N_out);
nrmse = sqrt(nrmse);
nrmse /= maxv;
fftw_free(ref_out);
ASSERT_LT(nrmse, type_epsilon_simple<double>());
}
static constexpr int absurd_version_or_property = std::numeric_limits<int>::min();
TEST(hipfftTest, GetVersion)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
// valid use case(s)
int tmp = absurd_version_or_property;
EXPECT_EQ(hipfftGetVersion(&tmp), HIPFFT_SUCCESS);
EXPECT_NE(tmp, absurd_version_or_property);
EXPECT_EQ(hipfftGetVersion(nullptr), HIPFFT_INVALID_VALUE);
}
TEST(hipfftTest, GetProperty)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
// valid use case(s)
int tmp;
for(auto prop_type : enum_helper<hipfftLibraryPropertyType>::valid_values)
{
tmp = absurd_version_or_property;
EXPECT_EQ(hipfftGetProperty(prop_type, &tmp), HIPFFT_SUCCESS);
EXPECT_NE(tmp, absurd_version_or_property);
}
// invalid use case(s)
const auto valid_property_type = enum_helper<hipfftLibraryPropertyType>::get_any_valid_value();
EXPECT_EQ(hipfftGetProperty(valid_property_type, nullptr), HIPFFT_INVALID_VALUE);
const auto invalid_property_type = enum_helper<hipfftLibraryPropertyType>::get_invalid_value();
EXPECT_EQ(hipfftGetProperty(invalid_property_type, &tmp), HIPFFT_INVALID_VALUE);
}