// Copyright (C) 2016 - 2026 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. #pragma once #ifndef FFTWTRANSFORM_H #define FFTWTRANSFORM_H #include "hostbuf.h" #include "rocfft_complex.h" #include "test_params.h" #include #include // Function to return maximum error for float and double types. // // Following Schatzman (1996; Accuracy of the Discrete Fourier // Transform and the Fast Fourier Transform), the shape of relative // l_2 error vs length should look like // // epsilon * sqrt(log2(length)). // // The magic epsilon constants below were chosen so that we get a // reasonable upper bound for (all of) our tests. // // For rocFFT, prime lengths result in the highest error. As such, // the epsilons below are perhaps too loose for pow2 lengths; but they // are appropriate for prime lengths. template inline double type_epsilon(); template <> inline double type_epsilon() { return half_epsilon; } template <> inline double type_epsilon() { return single_epsilon; } template <> inline double type_epsilon() { return double_epsilon; } static constexpr double default_half_epsilon() { return 9.77e-4; } static constexpr double default_single_epsilon() { return 3.75e-5; } static constexpr double default_double_epsilon() { return 1e-15; } // C++ traits to simplify type selection for FFTW plans, complex and/or real // FFTW types, based on the base real type of the floating-point arithmetic of // interest. The correct FFTW complex type can be accessed via, for example, // using complex_t = typename fftw_complex_trait::complex_t; // NOTE: FFTW does not support half-precision: members of fftw_trait // are made equivalent to the members of fftw_trait. template ::value || std::is_same::value || std::is_same::value, bool> = true> struct fftw_trait { private: using raw_fftw_plan_type = std::conditional_t, std::remove_pointer::type, std::remove_pointer::type>; static void plan_deleter(raw_fftw_plan_type* plan_ptr) { static_assert( std::is_same::type>::value || std::is_same::type>::value); if constexpr(std::is_same::type>::value) fftw_destroy_plan(plan_ptr); else fftwf_destroy_plan(plan_ptr); } public: using real_t = std::conditional_t::value, double, float>; using complex_t = std::conditional_t::value, fftw_complex, fftwf_complex>; using plan_wrapper_t = std::unique_ptr; static plan_wrapper_t make_wrapper(raw_fftw_plan_type* raw_plan_ptr) { return plan_wrapper_t{raw_plan_ptr, plan_deleter}; } }; // External alias templates to avoid verbose syntax thereafter template using fftw_plan_wrapper_t = typename fftw_trait::plan_wrapper_t; template using fftw_complex_t = typename fftw_trait::complex_t; template using fftw_real_t = typename fftw_trait::real_t; // Copies the half-precision input buffer to a single-precision buffer. static hostbuf half_to_single_copy(const hostbuf& in) { hostbuf out; out.alloc(2 * in.size()); auto in_begin = reinterpret_cast(in.data()); std::copy_n(in_begin, in.size() / sizeof(rocfft_fp16), reinterpret_cast(out.data())); return out; } // converts a wider precision buffer to a narrower precision, in-place template void narrow_precision_inplace(hostbuf& in) { // ensure we're actually shrinking the data static_assert(sizeof(TfloatIn) > sizeof(TfloatOut)); auto readPtr = reinterpret_cast(in.data()); auto writePtr = reinterpret_cast(in.data()); std::copy_n(readPtr, in.size() / sizeof(TfloatIn), writePtr); in.shrink(in.size() / (sizeof(TfloatIn) / sizeof(TfloatOut))); } static void single_to_half_inplace(hostbuf& in) { narrow_precision_inplace(in); } // Template wrappers for real-valued FFTW allocators: template inline Tfloat* fftw_alloc_real_type(size_t n); template <> inline float* fftw_alloc_real_type(size_t n) { return fftwf_alloc_real(n); } template <> inline double* fftw_alloc_real_type(size_t n) { return fftw_alloc_real(n); } // Template wrappers for complex-valued FFTW allocators: template inline fftw_complex_t* fftw_alloc_complex_type(size_t n); template <> inline fftw_complex_t* fftw_alloc_complex_type(size_t n) { return fftwf_alloc_complex(n); } template <> inline fftw_complex_t* fftw_alloc_complex_type(size_t n) { return fftw_alloc_complex(n); } template inline fftw_type* fftw_alloc_type(size_t n); template <> inline float* fftw_alloc_type(size_t n) { return fftw_alloc_real_type(n); } template <> inline double* fftw_alloc_type(size_t n) { return fftw_alloc_real_type(n); } template <> inline fftwf_complex* fftw_alloc_type(size_t n) { return fftw_alloc_complex_type(n); } template <> inline fftw_complex* fftw_alloc_type(size_t n) { return fftw_alloc_complex_type(n); } template <> inline rocfft_complex* fftw_alloc_type>(size_t n) { return (rocfft_complex*)fftw_alloc_complex_type(n); } template <> inline rocfft_complex* fftw_alloc_type>(size_t n) { return (rocfft_complex*)fftw_alloc_complex_type(n); } // Template wrappers for FFTW plan executors: template inline void fftw_execute_type(const fftw_plan_wrapper_t& plan); template <> inline void fftw_execute_type(const fftw_plan_wrapper_t& plan) { return fftwf_execute(plan.get()); } template <> inline void fftw_execute_type(const fftw_plan_wrapper_t& plan) { return fftw_execute(plan.get()); } // Template wrappers for FFTW c2c planners: template inline fftw_plan_wrapper_t fftw_plan_guru64_dft(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, fftw_complex_t* out, int sign, unsigned flags); template <> inline fftw_plan_wrapper_t fftw_plan_guru64_dft(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, fftw_complex_t* out, int sign, unsigned flags) { return fftw_trait::make_wrapper( fftwf_plan_guru64_dft(rank, dims, howmany_rank, howmany_dims, in, out, sign, flags)); } template <> inline fftw_plan_wrapper_t fftw_plan_guru64_dft(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, fftw_complex_t* out, int sign, unsigned flags) { return fftw_trait::make_wrapper( fftwf_plan_guru64_dft(rank, dims, howmany_rank, howmany_dims, in, out, sign, flags)); } template <> inline fftw_plan_wrapper_t fftw_plan_guru64_dft(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, fftw_complex_t* out, int sign, unsigned flags) { return fftw_trait::make_wrapper( fftw_plan_guru64_dft(rank, dims, howmany_rank, howmany_dims, in, out, sign, flags)); } // Template wrappers for FFTW c2c executors: template inline void fftw_plan_execute_c2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out); template <> inline void fftw_plan_execute_c2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { // since FFTW does not natively support half precision, convert // input to single, execute, then convert output back to half auto in_single = half_to_single_copy(in.front()); fftwf_execute_dft(plan.get(), reinterpret_cast(in_single.data()), reinterpret_cast(out.front().data())); single_to_half_inplace(out.front()); } template <> inline void fftw_plan_execute_c2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { fftwf_execute_dft(plan.get(), reinterpret_cast(in.front().data()), reinterpret_cast(out.front().data())); } template <> inline void fftw_plan_execute_c2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { fftw_execute_dft(plan.get(), reinterpret_cast(in.front().data()), reinterpret_cast(out.front().data())); } // Template wrappers for FFTW r2c planners: template inline fftw_plan_wrapper_t fftw_plan_guru64_r2c(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, Tfloat* in, fftw_complex_t* out, unsigned flags); template <> inline fftw_plan_wrapper_t fftw_plan_guru64_r2c(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, rocfft_fp16* in, fftw_complex_t* out, unsigned flags) { return fftw_trait::make_wrapper(fftwf_plan_guru64_dft_r2c( rank, dims, howmany_rank, howmany_dims, reinterpret_cast(in), out, flags)); } template <> inline fftw_plan_wrapper_t fftw_plan_guru64_r2c(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, float* in, fftw_complex_t* out, unsigned flags) { return fftw_trait::make_wrapper( fftwf_plan_guru64_dft_r2c(rank, dims, howmany_rank, howmany_dims, in, out, flags)); } template <> inline fftw_plan_wrapper_t fftw_plan_guru64_r2c(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, double* in, fftw_complex_t* out, unsigned flags) { return fftw_trait::make_wrapper( fftw_plan_guru64_dft_r2c(rank, dims, howmany_rank, howmany_dims, in, out, flags)); } // Template wrappers for FFTW r2c executors: template inline void fftw_plan_execute_r2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out); template <> inline void fftw_plan_execute_r2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { // since FFTW does not natively support half precision, convert // input to single, execute, then convert output back to half auto in_single = half_to_single_copy(in.front()); fftwf_execute_dft_r2c(plan.get(), reinterpret_cast(in_single.data()), reinterpret_cast(out.front().data())); single_to_half_inplace(out.front()); } template <> inline void fftw_plan_execute_r2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { fftwf_execute_dft_r2c(plan.get(), reinterpret_cast(in.front().data()), reinterpret_cast(out.front().data())); } template <> inline void fftw_plan_execute_r2c(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { fftw_execute_dft_r2c(plan.get(), reinterpret_cast(in.front().data()), reinterpret_cast(out.front().data())); } // Template wrappers for FFTW c2r planners: template inline fftw_plan_wrapper_t fftw_plan_guru64_c2r(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, Tfloat* out, unsigned flags); template <> inline fftw_plan_wrapper_t fftw_plan_guru64_c2r(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, rocfft_fp16* out, unsigned flags) { return fftw_trait::make_wrapper(fftwf_plan_guru64_dft_c2r( rank, dims, howmany_rank, howmany_dims, in, reinterpret_cast(out), flags)); } template <> inline fftw_plan_wrapper_t fftw_plan_guru64_c2r(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, float* out, unsigned flags) { return fftw_trait::make_wrapper( fftwf_plan_guru64_dft_c2r(rank, dims, howmany_rank, howmany_dims, in, out, flags)); } template <> inline fftw_plan_wrapper_t fftw_plan_guru64_c2r(int rank, const fftw_iodim64* dims, int howmany_rank, const fftw_iodim64* howmany_dims, fftw_complex_t* in, double* out, unsigned flags) { return fftw_trait::make_wrapper( fftw_plan_guru64_dft_c2r(rank, dims, howmany_rank, howmany_dims, in, out, flags)); } // Template wrappers for FFTW c2r executors: template inline void fftw_plan_execute_c2r(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out); template <> inline void fftw_plan_execute_c2r(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { // since FFTW does not natively support half precision, convert // input to single, execute, then convert output back to half auto in_single = half_to_single_copy(in.front()); fftwf_execute_dft_c2r(plan.get(), reinterpret_cast(in_single.data()), reinterpret_cast(out.front().data())); single_to_half_inplace(out.front()); } template <> inline void fftw_plan_execute_c2r(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { fftwf_execute_dft_c2r(plan.get(), reinterpret_cast(in.front().data()), reinterpret_cast(out.front().data())); } template <> inline void fftw_plan_execute_c2r(const fftw_plan_wrapper_t& plan, std::vector& in, std::vector& out) { fftw_execute_dft_c2r(plan.get(), reinterpret_cast(in.front().data()), reinterpret_cast(out.front().data())); } #ifdef FFTW_HAVE_SPRINT_PLAN // Template wrappers for FFTW print plan: template inline char* fftw_sprint_plan(const fftw_plan_wrapper_t& plan); template <> inline char* fftw_sprint_plan(const fftw_plan_wrapper_t& plan) { return fftwf_sprint_plan(plan.get()); } template <> inline char* fftw_sprint_plan(const fftw_plan_wrapper_t& plan) { return fftwf_sprint_plan(plan.get()); } template <> inline char* fftw_sprint_plan(const fftw_plan_wrapper_t& plan) { return fftw_sprint_plan(plan.get()); } #endif #endif