Files
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

490 lines
18 KiB
C++

// Copyright (C) 2021 - 2023 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 "../../shared/accuracy_test.h"
#include "../../shared/client_except.h"
#include "../../shared/gpubuf.h"
#include "../../shared/params_gen.h"
#include "../../shared/rocfft_params.h"
#include "../samples/rocfft/examplekernels.h"
#include "../samples/rocfft/exampleutils.h"
#include "rocfft/rocfft.h"
#include <functional>
#include <gtest/gtest.h>
#include <hip/hip_runtime_api.h>
#include <memory>
#include <random>
#include <thread>
#include <vector>
void run_1D_hermitian_test(size_t length)
{
// Run two 1D C2R transforms, on:
// * random input
// * identical random input, but modified to be Hermitian-symmetric
// We should tolerate the input being having non-zero imaginary part in the DC mode
// and the Nyquist frequency (of the length is even).
rocfft_params p;
p.length = {length};
p.precision = fft_precision_double;
p.transform_type = fft_transform_type_real_inverse;
p.placement = fft_placement_notinplace;
p.validate();
if(verbose)
{
std::cout << p.str("\n\t") << std::endl;
}
ASSERT_TRUE(p.valid(verbose));
std::vector<hipDoubleComplex> h_input(p.isize[0]);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> dis(0.0, 1.0);
for(auto& val : h_input)
{
val.x = dis(gen);
val.y = dis(gen);
}
if(verbose > 2)
{
std::cout << "non-Hermitian input:";
for(const auto& val : h_input)
{
std::cout << " "
<< "(" << val.x << ", " << val.y << ")";
}
std::cout << std::endl;
}
gpubuf ibuf;
ASSERT_TRUE(ibuf.alloc(p.ibuffer_sizes()[0]) == hipSuccess);
ASSERT_TRUE(hipMemcpy(ibuf.data(), h_input.data(), ibuf.size(), hipMemcpyHostToDevice)
== hipSuccess);
gpubuf obuf;
ASSERT_TRUE(obuf.alloc(p.obuffer_sizes()[0]) == hipSuccess);
ASSERT_TRUE(p.create_plan() == fft_status_success);
std::vector<void*> pibuf = {ibuf.data()};
std::vector<void*> pobuf = {obuf.data()};
ASSERT_TRUE(p.execute(pibuf.data(), pobuf.data()) == fft_status_success);
std::vector<double> h_output(p.osize[0]);
ASSERT_TRUE(hipMemcpy(h_output.data(), obuf.data(), obuf.size(), hipMemcpyDeviceToHost)
== hipSuccess);
ASSERT_TRUE(hipDeviceSynchronize() == hipSuccess);
if(verbose > 2)
{
std::cout << "output:";
for(const auto& val : h_output)
{
std::cout << " " << val;
}
std::cout << std::endl;
}
std::vector<hipDoubleComplex> h_input1(p.isize[0]);
std::copy(h_input.begin(), h_input.end(), h_input1.begin());
// Impose Hermitian symmetry on the input:
h_input1[0].y = 0.0;
if(p.length[0] % 2 == 0)
{
h_input1.back().y = 0.0;
}
if(verbose > 2)
{
std::cout << "Hermitian input:";
for(const auto& val : h_input1)
{
std::cout << " "
<< "(" << val.x << ", " << val.y << ")";
}
std::cout << std::endl;
}
double maxdiff = 0.0;
for(unsigned int i = 0; i < h_input.size(); ++i)
{
auto val = std::abs(
rocfft_complex<double>(h_input[i].x - h_input1[i].x, h_input[i].y - h_input1[i].y));
if(val > maxdiff)
maxdiff = val;
}
ASSERT_TRUE(maxdiff > 0.0);
ASSERT_TRUE(hipMemcpy(ibuf.data(), h_input1.data(), ibuf.size(), hipMemcpyHostToDevice)
== hipSuccess);
ASSERT_TRUE(p.execute(pibuf.data(), pobuf.data()) == fft_status_success);
std::vector<double> h_output1(p.osize[0]);
ASSERT_TRUE(hipMemcpy(h_output1.data(), obuf.data(), obuf.size(), hipMemcpyDeviceToHost)
== hipSuccess);
if(verbose > 2)
{
std::cout << "output:";
for(const auto& val : h_output1)
{
std::cout << " " << val;
}
std::cout << std::endl;
}
double maxerr = 0;
for(unsigned int i = 0; i < h_output.size(); ++i)
{
auto val = std::abs(h_output[i] - h_output1[i]);
if(val > maxerr)
maxerr = val;
}
if(verbose)
std::cout << maxerr << std::endl;
EXPECT_TRUE(maxerr == 0.0);
}
// test a case that's small enough that it only needs one kernel
TEST(rocfft_UnitTest, 1D_hermitian_single_small)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
try
{
run_1D_hermitian_test(8);
}
ROCFFT_CATCH_TEST_EXCEPTIONS;
}
// test a case that's big enough that it needs multiple kernels
TEST(rocfft_UnitTest, 1D_hermitian_single_large)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
try
{
run_1D_hermitian_test(8192);
}
ROCFFT_CATCH_TEST_EXCEPTIONS;
}
template <typename T>
std::string str(T begin, T end)
{
std::stringstream ss;
bool first = true;
for(; begin != end; begin++)
{
if(!first)
ss << ", ";
ss << *begin;
first = false;
}
return ss.str();
}
// Test that the GPU Hermitian symmetrizer code produces the correct results.
TEST(rocfft_UnitTest, gpu_symmetrizer)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
try
{
std::vector<std::vector<size_t>> lengths = {{4, 4, 3},
{5},
{8},
{5, 5},
{5, 8},
{8, 5},
{8, 8},
{5, 5, 5},
{8, 5, 5},
{5, 8, 5},
{5, 5, 8},
{5, 8, 8},
{8, 5, 8},
{8, 8, 5},
{8, 8, 8}};
for(const auto& length : lengths)
{
// Symmetrize complex data and ensure that the checker sees that it's symmetric.
// Use the params class to set up strides and lengths:
rocfft_params p;
p.length = length;
p.precision = fft_precision_double;
p.transform_type = fft_transform_type_real_inverse;
p.placement = fft_placement_notinplace;
p.validate();
if(verbose)
{
std::cout << "\t" << p.str("\n\t") << std::endl;
}
ASSERT_TRUE(p.valid(verbose));
// Data buffers:
gpubuf_t<hipDoubleComplex> buf;
ASSERT_TRUE(buf.alloc(sizeof(hipDoubleComplex) * p.isize[0]) == hipSuccess);
std::vector<hipDoubleComplex> hbuf(p.isize[0]);
// Initialize a Hermitian-symmetric array; it should be symmetric.
init_hermitiancomplex_cm(p.length_cm(), p.ilength_cm(), p.istride_cm(), buf.data());
ASSERT_TRUE(hipMemcpy(hbuf.data(), buf.data(), buf.size(), hipMemcpyDeviceToHost)
== hipSuccess);
if(verbose > 1)
{
printbuffer_cm(hbuf, p.ilength_cm(), p.istride_cm(), p.nbatch, p.idist);
}
EXPECT_TRUE(check_symmetry_cm(
hbuf, p.length_cm(), p.istride_cm(), p.nbatch, p.idist, verbose > 0))
<< "length: " << str(length.begin(), length.end());
// This should not be symmetric:
std::mt19937_64 rng;
std::seed_seq ss{uint32_t(10)};
rng.seed(ss);
std::uniform_real_distribution<double> unif(0, 1);
for(auto& v : hbuf)
{
v.x = unif(rng);
v.y = unif(rng);
}
if(verbose > 2)
{
printbuffer_cm(hbuf, p.ilength_cm(), p.istride_cm(), p.nbatch, p.idist);
}
EXPECT_TRUE(
!check_symmetry_cm(hbuf, p.length_cm(), p.istride_cm(), p.nbatch, p.idist, false))
<< "length: " << str(length.begin(), length.end());
}
for(const auto& length : lengths)
{
// Generate Hermitian-symmetric data and ensure that applying the symmetrizer has no effect.
rocfft_params p;
p.length = length;
p.precision = fft_precision_double;
p.transform_type = fft_transform_type_real_forward;
p.placement = fft_placement_notinplace;
p.validate();
if(verbose)
{
std::cout << "\t" << p.str("\n\t") << std::endl;
}
ASSERT_TRUE(p.valid(verbose));
ASSERT_TRUE(p.create_plan() == fft_status_success);
gpubuf_t<double> ibuf;
gpubuf_t<hipDoubleComplex> obuf;
ASSERT_TRUE(ibuf.alloc(p.ibuffer_sizes()[0]) == hipSuccess);
ASSERT_TRUE(obuf.alloc(p.obuffer_sizes()[0]) == hipSuccess);
initreal_cm(p.length_cm(), p.istride_cm(), ibuf.data());
std::vector<double*> pibuf = {ibuf.data()};
std::vector<hipDoubleComplex*> pobuf = {obuf.data()};
ASSERT_TRUE(p.execute(reinterpret_cast<void**>(pibuf.data()),
reinterpret_cast<void**>(pobuf.data()))
== fft_status_success);
std::vector<hipDoubleComplex> h_output(p.osize[0]);
std::fill(h_output.begin(), h_output.end(), hipDoubleComplex{0.0, 0.0});
ASSERT_TRUE(
hipMemcpy(h_output.data(), obuf.data(), p.obuffer_sizes()[0], hipMemcpyDeviceToHost)
== hipSuccess);
impose_hermitian_symmetry_cm(
p.length_cm(), p.olength_cm(), p.ostride_cm(), obuf.data());
std::vector<hipDoubleComplex> h_output_resym(p.osize[0]);
std::fill(h_output_resym.begin(), h_output_resym.end(), hipDoubleComplex{0.0, 0.0});
ASSERT_TRUE(
hipMemcpy(
h_output_resym.data(), obuf.data(), p.obuffer_sizes()[0], hipMemcpyDeviceToHost)
== hipSuccess);
double maxdiff = 0;
for(unsigned int i = 0; i < h_output.size(); ++i)
{
auto rdiff = std::abs(h_output[i].x - h_output_resym[i].x);
auto idiff = std::abs(h_output[i].y - h_output_resym[i].y);
maxdiff = std::max({maxdiff, rdiff, idiff});
}
if(verbose)
{
std::cout << "maxdiff: " << maxdiff << std::endl;
}
if(verbose > 2)
{
std::cout << "before symmetrization:\n";
printbuffer_cm(h_output, p.olength_cm(), p.ostride_cm(), p.nbatch, p.odist);
std::cout << "after symmetrization:\n";
printbuffer_cm(h_output_resym, p.olength_cm(), p.ostride_cm(), p.nbatch, p.odist);
}
EXPECT_TRUE(maxdiff < 1e-13) << maxdiff << "\n" << p.str() << "\n";
}
}
ROCFFT_CATCH_TEST_EXCEPTIONS;
}
// Test that the host and device Hermitian symmetrizers produce the same results.
TEST(rocfft_UnitTest, compare_cpu_gpu_symmetrizers)
{
if(hash_prob(random_seed, ::testing::UnitTest::GetInstance()->current_test_info()->name())
> unittest_prob)
{
GTEST_SKIP();
}
try
{
std::vector<std::vector<size_t>> lengths
= {{1}, {2}, {16}, {512}, {1, 1}, {1, 2}, {2, 1},
{2, 2}, {8, 1}, {1, 8}, {8, 2}, {2, 8}, {16, 1}, {1, 16},
{7, 2}, {2, 7}, {2, 512}, {512, 2}, {16, 512}, {512, 16}, {1, 1, 1},
{1, 2, 1}, {2, 1, 1}, {1, 1, 2}, {2, 2, 2}, {8, 1, 2}, {1, 2, 8}, {1, 8, 2},
{5, 5, 5}, {8, 5, 5}, {5, 8, 5}, {5, 5, 8}, {512, 7, 2}, {7, 2, 512}, {2, 512, 7}};
std::vector<size_t> nbatch = {1, 2, 5, 10, 50};
const double double_linf_cutoff = 1e-15;
std::vector<std::pair<size_t, size_t>> linf_failures;
const auto array_types
= {fft_array_type_hermitian_interleaved, fft_array_type_hermitian_planar};
for(const auto& batch : nbatch)
{
for(const auto& length : lengths)
{
for(const auto& itype : array_types)
{
// Set up the params class to generate strides etc:
rocfft_params p;
p.length = length;
p.precision = fft_precision_double;
p.transform_type = fft_transform_type_real_inverse;
p.itype = itype;
p.placement = fft_placement_notinplace;
p.nbatch = batch;
p.validate();
ASSERT_TRUE(p.valid(verbose));
const auto nbuffer = p.nbuffer(p.itype);
std::vector<gpubuf> gpu_symm_gpubuf(nbuffer);
std::vector<hostbuf> cpu_symm_hostbuf(nbuffer), gpu_symm_hostbuf(nbuffer);
for(size_t i = 0; i < nbuffer; ++i)
{
auto hip_status = gpu_symm_gpubuf[i].alloc(p.ibuffer_sizes()[i]);
if(hip_status != hipSuccess)
{
std::stringstream msg;
msg << "allocation failure for gpu buffer of " << i << " size "
<< p.ibuffer_sizes()[i] << "("
<< byte_size_to_str(p.ibuffer_sizes()[i]) << ") with code "
<< hipError_to_string(hip_status);
throw hip_runtime_error(msg.str(), hip_status);
}
cpu_symm_hostbuf[i].alloc(p.ibuffer_sizes()[i]);
}
// This test relies on the (deterministic) host and device input generators producing the same data
p.igen = fft_input_generator_device;
p.compute_input(gpu_symm_gpubuf);
p.igen = fft_input_generator_host;
p.compute_input(cpu_symm_hostbuf);
for(size_t i = 0; i < nbuffer; ++i)
{
gpu_symm_hostbuf[i].alloc(p.ibuffer_sizes()[i]);
auto hip_status = hipMemcpy(gpu_symm_hostbuf[i].data(),
gpu_symm_gpubuf[i].data(),
p.ibuffer_sizes()[i],
hipMemcpyDeviceToHost);
if(hip_status != hipSuccess)
{
std::stringstream msg;
msg << "hipMemcpy failure for buffer of " << i << " size "
<< p.ibuffer_sizes()[i] << "("
<< byte_size_to_str(p.ibuffer_sizes()[i]) << ") with code "
<< hipError_to_string(hip_status);
throw hip_runtime_error(msg.str(), hip_status);
}
}
const auto diff = distance(gpu_symm_hostbuf,
cpu_symm_hostbuf,
p.ilength(),
p.nbatch,
p.precision,
p.itype,
p.istride,
p.idist,
p.itype,
p.istride,
p.idist,
&linf_failures,
double_linf_cutoff,
p.ioffset,
p.ioffset);
EXPECT_TRUE(diff.l_inf <= double_linf_cutoff)
<< "Linf test failed. Linf:" << diff.l_inf
<< "\tcutoff: " << double_linf_cutoff << "\n"
<< p.str();
}
}
}
}
ROCFFT_CATCH_TEST_EXCEPTIONS;
}