O Cuda não possui nenhuma implementação direta de FFT 4D. Portanto, quero decompor uma FFT 4D em 4 FFTs 1D nas dimensões X, Y, Z e W. Entendo que a API cufftPlanMany é mais adequada para isso, pois elimina o uso de loops for e, portanto, é muito mais rápida.
Escrevi um programa exatamente para isso. No entanto, o resultado final do 4D FFT não corresponde à implementação do 4D FFTW.
Abaixo estão as duas implementações usando as bibliotecas FFTW e Cuda, respectivamente. Escolhi cuidadosamente o lote, o passo e o dist para a função cufftPlanMany. No entanto, não entendi o que estou fazendo errado. Qualquer ajuda será bem-vinda.
Implementação FFTW 4D
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <fftw3.h>
#define PRINT_FLAG 1
#define NPRINTS 5 // print size
void printf_fftw_cmplx_array(fftw_complex *complex_array, unsigned int size) {
for (unsigned int i = 0; i < NPRINTS; ++i) {
printf(" (%2.4f, %2.4fi)\n", complex_array[i][0], complex_array[i][1]);
}
printf("...\n");
for (unsigned int i = size - NPRINTS; i < size; ++i) {
printf(" (%2.4f, %2.4fi)\n", complex_array[i][0], complex_array[i][1]);
}
}
float run_test_fftw_4d(unsigned int nx, unsigned int ny, unsigned int nz, unsigned int nw) {
srand(2025);
// Declaration
fftw_complex *complex_data;
fftw_plan plan;
unsigned int element_size = nx * ny * nz * nw;
size_t size = sizeof(fftw_complex) * element_size;
clock_t start, stop;
float elapsed_time;
// Allocate memory for input and output arrays
complex_data = (fftw_complex *)fftw_malloc(size);
// Initialize input complex signal
for (unsigned int i = 0; i < element_size; ++i) {
complex_data[i][0] = rand() / (float)RAND_MAX;
complex_data[i][1] = 0;
}
// Print input stuff
if (PRINT_FLAG) {
printf("Complex data...\n");
printf_fftw_cmplx_array(complex_data, element_size);
}
// Setup the FFT plan
plan = fftw_plan_dft(4, (int[]){nx, ny, nz, nw}, complex_data, complex_data, FFTW_FORWARD, FFTW_ESTIMATE);
// Start time
start = clock();
// Execute the FFT
fftw_execute(plan);
// End time
stop = clock();
// Print output stuff
if (PRINT_FLAG) {
printf("Fourier Coefficients...\n");
printf_fftw_cmplx_array(complex_data, element_size);
}
// Compute elapsed time
elapsed_time = (double)(stop - start) / CLOCKS_PER_SEC;
// Clean up
fftw_destroy_plan(plan);
fftw_free(complex_data);
fftw_cleanup();
return elapsed_time;
}
int main(int argc, char **argv) {
if (argc != 6) {
printf("Error: This program requires exactly 5 command-line arguments.\n");
printf(" %s <arg0> <arg1> <arg2> <arg3> <arg4>\n", argv[0]);
printf(" arg0, arg1, arg2, arg3: FFT lengths in 4D\n");
printf(" arg4: Number of iterations\n");
printf(" e.g.: %s 64 64 64 64 5\n", argv[0]);
return -1;
}
unsigned int nx = atoi(argv[1]);
unsigned int ny = atoi(argv[2]);
unsigned int nz = atoi(argv[3]);
unsigned int nw = atoi(argv[4]);
unsigned int niter = atoi(argv[5]);
float sum = 0.0;
float span_s = 0.0;
for (unsigned int i = 0; i < niter; ++i) {
span_s = run_test_fftw_4d(nx, ny, nz, nw);
if (PRINT_FLAG) printf("[%d]: %.6f s\n", i, span_s);
sum += span_s;
}
printf("%.6f\n", sum/(float)niter);
return 0;
}
Implementação errônea do cuFFT4D
#include <stdio.h>
#include <stdlib.h>
#include <cuda_runtime.h>
#include <cufft.h>
#include <math.h>
#define PRINT_FLAG 1
#define NPRINTS 5 // print size
#define CHECK_CUDA(call) \
{ \
const cudaError_t error = call; \
if (error != cudaSuccess) \
{ \
fprintf(stderr, "Error: %s:%d, ", __FILE__, __LINE__); \
fprintf(stderr, "code: %d, reason: %s\n", error, \
cudaGetErrorString(error)); \
exit(EXIT_FAILURE); \
} \
}
#define CHECK_CUFFT(call) \
{ \
cufftResult error; \
if ( (error = (call)) != CUFFT_SUCCESS) \
{ \
fprintf(stderr, "Got CUFFT error %d at %s:%d\n", error, __FILE__, \
__LINE__); \
exit(EXIT_FAILURE); \
} \
}
void printf_cufft_cmplx_array(cufftComplex *complex_array, unsigned int size) {
for (unsigned int i = 0; i < NPRINTS; ++i) {
printf(" (%2.4f, %2.4fi)\n", complex_array[i].x, complex_array[i].y);
}
printf("...\n");
for (unsigned int i = size - NPRINTS; i < size; ++i) {
printf(" (%2.4f, %2.4fi)\n", complex_array[i].x, complex_array[i].y);
}
}
float run_test_cufft_4d_4x1d(unsigned int nx, unsigned int ny, unsigned int nz, unsigned int nw) {
srand(2025);
// Declaration
cufftComplex *complex_data;
cufftComplex *d_complex_data;
cufftHandle plan1d_x, plan1d_y, plan1d_z, plan1d_w;
unsigned int element_size = nx * ny * nz * nw;
size_t size = sizeof(cufftComplex) * element_size;
cudaEvent_t start, stop;
float elapsed_time;
// Allocate memory for the variables on the host
complex_data = (cufftComplex *)malloc(size);
// Initialize input complex signal
for (unsigned int i = 0; i < element_size; ++i) {
complex_data[i].x = rand() / (float)RAND_MAX;
complex_data[i].y = 0;
}
// Print input stuff
if (PRINT_FLAG) {
printf("Complex data...\n");
printf_cufft_cmplx_array(complex_data, element_size);
}
// Create CUDA events
CHECK_CUDA(cudaEventCreate(&start));
CHECK_CUDA(cudaEventCreate(&stop));
// Allocate device memory for complex signal and output frequency
CHECK_CUDA(cudaMalloc((void **)&d_complex_data, size));
int n[1] = { (int)nx };
int embed[1] = { (int)nx };
CHECK_CUFFT(cufftPlanMany(&plan1d_x, 1, n, // 1D FFT of size nx
embed, ny * nz * nw, 1, // inembed, istride, idist
embed, ny * nz * nw, 1, // onembed, ostride, odist
CUFFT_C2C, ny * nz * nw));
n[0] = (int)ny;
embed[0] = (int)ny;
CHECK_CUFFT(cufftPlanMany(&plan1d_y, 1, n, // 1D FFT of size ny
embed, nz * nw, 1, // inembed, istride, idist
embed, nz * nw, 1, // onembed, ostride, odist
CUFFT_C2C, nx * nz * nw));
n[0] = (int)nz;
embed[0] = (int)nz;
CHECK_CUFFT(cufftPlanMany(&plan1d_z, 1, n, // 1D FFT of size nz
embed, nw, 1, // inembed, istride, idist
embed, nw, 1, // onembed, ostride, odist
CUFFT_C2C, nx * ny * nw));
n[0] = (int)nw;
embed[0] = (int)nw;
CHECK_CUFFT(cufftPlanMany(&plan1d_w, 1, n, // 1D FFT of size nw
embed, 1, nw, // inembed, istride, idist
embed, 1, nw, // onembed, ostride, odist
CUFFT_C2C, nx * ny * nz));
// Record the start event
CHECK_CUDA(cudaEventRecord(start, 0));
// Copy host memory to device
CHECK_CUDA(cudaMemcpy(d_complex_data, complex_data, size, cudaMemcpyHostToDevice));
// Perform FFT along each dimension sequentially
CHECK_CUFFT(cufftExecC2C(plan1d_x, d_complex_data, d_complex_data, CUFFT_FORWARD));
CHECK_CUFFT(cufftDestroy(plan1d_x));
CHECK_CUFFT(cufftExecC2C(plan1d_y, d_complex_data, d_complex_data, CUFFT_FORWARD));
CHECK_CUFFT(cufftDestroy(plan1d_y));
CHECK_CUFFT(cufftExecC2C(plan1d_z, d_complex_data, d_complex_data, CUFFT_FORWARD));
CHECK_CUFFT(cufftDestroy(plan1d_z));
CHECK_CUFFT(cufftExecC2C(plan1d_w, d_complex_data, d_complex_data, CUFFT_FORWARD));
CHECK_CUFFT(cufftDestroy(plan1d_w));
// Retrieve the results into host memory
CHECK_CUDA(cudaMemcpy(complex_data, d_complex_data, size, cudaMemcpyDeviceToHost));
// Record the stop event
CHECK_CUDA(cudaEventRecord(stop, 0));
CHECK_CUDA(cudaEventSynchronize(stop));
// Print output stuff
if (PRINT_FLAG) {
printf("Fourier Coefficients...\n");
printf_cufft_cmplx_array(complex_data, element_size);
}
// Compute elapsed time
CHECK_CUDA(cudaEventElapsedTime(&elapsed_time, start, stop));
// Clean up
CHECK_CUDA(cudaFree(d_complex_data));
CHECK_CUDA(cudaEventDestroy(start));
CHECK_CUDA(cudaEventDestroy(stop));
free(complex_data);
return elapsed_time * 1e-3;
}
int main(int argc, char **argv) {
if (argc != 6) {
printf("Error: This program requires exactly 5 command-line arguments.\n");
printf(" %s <arg0> <arg1> <arg2> <arg3> <arg4>\n", argv[0]);
printf(" arg0, arg1, arg2, arg3: FFT lengths in 4D\n");
printf(" arg4: Number of iterations\n");
printf(" e.g.: %s 64 64 64 64 5\n", argv[0]);
return -1;
}
unsigned int nx = atoi(argv[1]);
unsigned int ny = atoi(argv[2]);
unsigned int nz = atoi(argv[3]);
unsigned int nw = atoi(argv[4]);
unsigned int niter = atoi(argv[5]);
float sum = 0.0;
float span_s = 0.0;
for (unsigned int i = 0; i < niter; ++i) {
span_s = run_test_cufft_4d_4x1d(nx, ny, nz, nw);
if (PRINT_FLAG) printf("[%d]: %.6f s\n", i, span_s);
sum += span_s;
}
printf("%.6f\n", sum/(float)niter);
CHECK_CUDA(cudaDeviceReset());
return 0;
}
Experimente ambas as implementações para um array 4x4x4x4 e você notará que apenas os primeiros coeficientes correspondem. Sei que a implementação FFTW produz o resultado correto, pois posso obter o mesmo resultado de diferentes maneiras, como uma FFT 3D seguida por uma FFT 1D ou 2 FFTs 2D usando as bibliotecas FFTW e cuFFT.