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

510 lines
19 KiB
C++

/******************************************************************************
* Copyright (C) 2024 - 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.
*******************************************************************************/
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <hip/hip_runtime.h>
#include <mpi.h>
#include <numeric>
#include <type_traits>
#include <vector>
#include "../../../shared/CLI11.hpp"
#include "rocfft.h"
// Check all ranks for an rocFFT non-success status.
auto rocfft_status_sync(const rocfft_status fftrc, const MPI_Comm comm)
{
// Since hipSuccess is the lowest enum value, we can find if there are any errors
// by getting the maximum value of the return code over all procs.
// Guarantee that the enum is an unsigned int so that we can send this via MPI:
static_assert(std::is_same_v<std::underlying_type_t<decltype(fftrc)>, unsigned int>);
auto global_fftrc = rocfft_status_success;
const auto mpirc = MPI_Allreduce(&fftrc, &global_fftrc, 1, MPI_UNSIGNED, MPI_MAX, comm);
if(mpirc != MPI_SUCCESS)
{
return rocfft_status_failure;
}
return global_fftrc;
}
// Check all ranks for an hip runtime non-success status.
auto hip_status_sync(const hipError_t hiprc, const MPI_Comm comm)
{
// Since rocfft_status_success is the lowest enum value, we can find if there are any errors
// by getting the maximum value of the return code over all procs.
// Guarantee that the enum is an unsigned int so that we can send this via MPI:
static_assert(std::is_same_v<std::underlying_type_t<decltype(hiprc)>, unsigned int>);
auto global_hiprc = hipSuccess;
const auto mpirc = MPI_Allreduce(&hiprc, &global_hiprc, 1, MPI_UNSIGNED, MPI_MAX, comm);
if(mpirc != MPI_SUCCESS)
{
return hipErrorUnknown;
}
return global_hiprc;
}
int main(int argc, char** argv)
{
MPI_Init(&argc, &argv);
MPI_Comm mpi_comm = MPI_COMM_WORLD;
int mpi_size = 0;
MPI_Comm_size(mpi_comm, &mpi_size);
int mpi_rank = 0;
MPI_Comm_rank(mpi_comm, &mpi_rank);
// General FFT parameters:
std::vector<size_t> length = {8, 8};
// Number of independent FFTs of size `length` per execution
// (override with `--nbatch N`; 1 reproduces the unbatched case)
// Decomposition is purely spatial, so each rank holds its slab
// of all `nbatch` transforms
size_t nbatch = 4;
// MPI launchers replicate argv to every rank, so parsing
// identically on each rank gives a consistent value
CLI::App app{"rocFFT batched MPI example"};
app.add_option("-b,--nbatch", nbatch, "Number of batched 2-D transforms")
->default_val(nbatch)
->check(CLI::PositiveNumber);
try
{
app.parse(argc, argv);
}
catch(const CLI::ParseError& e)
{
return app.exit(e);
}
// Slab decomposition splits the slowest spatial dimension across
// ranks; more ranks than slabs would leave some bricks empty
if(static_cast<size_t>(mpi_size) > length.back())
{
if(mpi_rank == 0)
{
std::cerr << "Error: mpi_size (" << mpi_size
<< ") exceeds the slowest FFT dimension length.back() (" << length.back()
<< "). Re-run with at most " << length.back() << " ranks.\n";
}
MPI_Finalize();
return 1;
}
const rocfft_transform_type direction = rocfft_transform_type_complex_forward;
const rocfft_result_placement place = rocfft_placement_notinplace;
if(mpi_rank == 0)
{
std::cout << "rocFFT batched MPI example\n";
std::cout << "MPI size: " << mpi_size << "\n";
std::cout << "FFT length: " << length[0] << " x " << length[1] << ", nbatch: " << nbatch
<< "\n";
}
auto fftrc = rocfft_status_success;
auto hiprc = hipSuccess;
fftrc = rocfft_setup();
if(fftrc != rocfft_status_success)
throw std::runtime_error("failed to set up rocFFT");
rocfft_plan_description description = nullptr;
rocfft_plan_description_create(&description);
fftrc = rocfft_plan_description_set_comm(description, rocfft_comm_mpi, &mpi_comm);
if(fftrc != rocfft_status_success)
throw std::runtime_error("failed add communicator to description");
// Do not set stride information via the descriptor, they are to be defined during field
// creation below
fftrc = rocfft_plan_description_set_data_layout(description,
rocfft_array_type_complex_interleaved,
rocfft_array_type_complex_interleaved,
nullptr,
nullptr,
0,
nullptr,
0,
0,
nullptr,
0);
if(fftrc != rocfft_status_success)
throw std::runtime_error("failed to create description");
if(mpi_rank == 0)
{
std::cout << "input data decomposition:\n";
}
std::vector<void*> gpu_in = {nullptr};
{
rocfft_field infield = nullptr;
rocfft_field_create(&infield);
// This rank's slab in the slowest spatial dimension
const size_t inbrick_length1 = length[1] / (size_t)mpi_size
+ ((size_t)mpi_rank < length[1] % (size_t)mpi_size ? 1 : 0);
const size_t inbrick_lower1
= mpi_rank * (length[1] / mpi_size) + std::min((size_t)mpi_rank, length[1] % mpi_size);
const size_t inbrick_upper1 = inbrick_lower1 + inbrick_length1;
// Brick coordinates and strides are column-major (fastest-moving
// dimension first) and include the batch dimension as the last
// (slowest) entry, so the arrays have N+1 entries for an N-dim FFT.
// For a packed layout the batch stride is the local plane size
const size_t inbrick_dist = length[0] * inbrick_length1;
std::vector<size_t> inbrick_stride = {1, length[0], inbrick_dist};
std::vector<size_t> inbrick_lower = {0, inbrick_lower1, 0};
std::vector<size_t> inbrick_upper = {length[0], inbrick_upper1, nbatch};
rocfft_brick inbrick = nullptr;
rocfft_brick_create(&inbrick,
inbrick_lower.data(),
inbrick_upper.data(),
inbrick_stride.data(),
inbrick_lower.size(),
0);
rocfft_field_add_brick(infield, inbrick);
rocfft_brick_destroy(inbrick);
inbrick = nullptr;
const size_t memSize = nbatch * inbrick_dist * sizeof(std::complex<double>);
std::vector<std::complex<double>> host_in(nbatch * inbrick_dist);
// idx0 (unit stride) innermost for contiguous access
for(size_t batch = 0; batch < nbatch; ++batch)
{
for(auto idx1 = inbrick_lower[1]; idx1 < inbrick_upper[1]; ++idx1)
{
for(auto idx0 = inbrick_lower[0]; idx0 < inbrick_upper[0]; ++idx0)
{
const auto pos = batch * inbrick_stride[2]
+ (idx0 - inbrick_lower[0]) * inbrick_stride[0]
+ (idx1 - inbrick_lower[1]) * inbrick_stride[1];
// Offset by batch so each transform's output is distinct,
// confirming batches were processed independently
const double real = static_cast<double>(idx0 + batch);
const double imag = static_cast<double>(idx1);
host_in[pos] = std::complex<double>(real, imag);
}
}
}
// Serialize output:
for(int irank = 0; irank < mpi_size; ++irank)
{
if(mpi_rank == irank)
{
std::cout << "in-brick rank " << irank;
std::cout << "\n\tlower indices:";
for(const auto val : inbrick_lower)
std::cout << " " << val;
std::cout << "\n\tupper indices:";
for(const auto val : inbrick_upper)
std::cout << " " << val;
std::cout << "\n\tstrides:";
for(const auto val : inbrick_stride)
std::cout << " " << val;
std::cout << "\n";
std::cout << "\tnbatch: " << nbatch << "\n";
std::cout << "\tbuffer size: " << memSize << "\n";
std::cout << "\tinput batch 0:\n";
const size_t batch0 = 0;
for(auto idx1 = inbrick_lower[1]; idx1 < inbrick_upper[1]; ++idx1)
{
for(auto idx0 = inbrick_lower[0]; idx0 < inbrick_upper[0]; ++idx0)
{
const auto pos = batch0 * inbrick_stride[2]
+ (idx0 - inbrick_lower[0]) * inbrick_stride[0]
+ (idx1 - inbrick_lower[1]) * inbrick_stride[1];
std::cout << host_in[pos] << " ";
}
std::cout << "\n";
}
}
MPI_Barrier(mpi_comm);
}
hiprc = hipMalloc(&gpu_in[0], memSize);
if(hiprc != hipSuccess)
throw std::runtime_error("inbrick hipMalloc failed");
hiprc = hipMemcpy(gpu_in[0], host_in.data(), memSize, hipMemcpyHostToDevice);
if(hiprc != hipSuccess)
throw std::runtime_error("inbrick hipMemcpy failed");
rocfft_plan_description_add_infield(description, infield);
fftrc = rocfft_field_destroy(infield);
if(fftrc != rocfft_status_success)
throw std::runtime_error("failed destroy infield");
}
if(mpi_rank == 0)
{
std::cout << "output data decomposition:\n";
}
std::vector<void*> gpu_out = {nullptr};
std::vector<size_t> outbrick_lower;
std::vector<size_t> outbrick_upper;
std::vector<size_t> outbrick_stride;
size_t outbrick_dist = 0;
{
const size_t outbrick_length1 = length[1] / (size_t)mpi_size
+ ((size_t)mpi_rank < length[1] % (size_t)mpi_size ? 1 : 0);
const size_t outbrick_lower1
= mpi_rank * (length[1] / mpi_size) + std::min((size_t)mpi_rank, length[1] % mpi_size);
const size_t outbrick_upper1 = outbrick_lower1 + outbrick_length1;
// Same column-major + batched-last layout as the input brick
outbrick_dist = length[0] * outbrick_length1;
outbrick_stride = {1, length[0], outbrick_dist};
outbrick_lower = {0, outbrick_lower1, 0};
outbrick_upper = {length[0], outbrick_upper1, nbatch};
const size_t memSize = nbatch * outbrick_dist * sizeof(std::complex<double>);
for(int irank = 0; irank < mpi_size; ++irank)
{
if(mpi_rank == irank)
{
std::cout << "out-brick rank " << irank;
std::cout << "\n\tlower indices:";
for(const auto val : outbrick_lower)
std::cout << " " << val;
std::cout << "\n\tupper indices:";
for(const auto val : outbrick_upper)
std::cout << " " << val;
std::cout << "\n\tstrides:";
for(const auto val : outbrick_stride)
std::cout << " " << val;
std::cout << "\n";
std::cout << "\tnbatch: " << nbatch << "\n";
std::cout << "\tbuffer size: " << memSize << "\n";
}
MPI_Barrier(mpi_comm);
}
rocfft_field outfield = nullptr;
rocfft_field_create(&outfield);
rocfft_brick outbrick = nullptr;
rocfft_brick_create(&outbrick,
outbrick_lower.data(),
outbrick_upper.data(),
outbrick_stride.data(),
outbrick_lower.size(),
0);
rocfft_field_add_brick(outfield, outbrick);
rocfft_brick_destroy(outbrick);
outbrick = nullptr;
hiprc = hipMalloc(&gpu_out[0], memSize);
if(hiprc != hipSuccess)
throw std::runtime_error("outbrick hipMalloc failed");
rocfft_plan_description_add_outfield(description, outfield);
fftrc = rocfft_field_destroy(outfield);
if(fftrc != rocfft_status_success)
throw std::runtime_error("failed destroy outfield");
}
// In order still handle non-success return codes without killing all of the MPI processes, we
// put object creation in a try/catch block and destroy non-nullptr objects.
// Serialize output:
for(int irank = 0; irank < mpi_size; ++irank)
{
if(mpi_rank == irank)
{
std::cout << "rank " << irank << "\n";
std::cout << "input ";
for(const auto& b : gpu_in)
std::cout << " " << b;
std::cout << "\n";
std::cout << "output ";
for(const auto& b : gpu_out)
std::cout << " " << b;
std::cout << "\n";
}
MPI_Barrier(mpi_comm);
}
fftrc = rocfft_status_sync(fftrc, mpi_comm);
hiprc = hip_status_sync(hiprc, mpi_comm);
if(mpi_rank == 0)
{
if(fftrc == rocfft_status_success && hiprc == hipSuccess)
{
std::cout << "so far so good, trying to make a plan....\n";
}
else
{
std::cout << "failure: will not make a plan....\n";
}
}
// Create a multi-process plan:
rocfft_plan gpu_plan = nullptr;
if(fftrc == rocfft_status_success && hiprc == hipSuccess)
{
fftrc = rocfft_plan_create(&gpu_plan,
place,
direction,
rocfft_precision_double,
length.size(), // Dimension
length.data(), // lengths
nbatch, // Number of transforms
description); // Description
}
fftrc = rocfft_status_sync(fftrc, mpi_comm);
if(mpi_rank == 0)
{
if(fftrc == rocfft_status_success)
{
std::cout << "so far so good, we have a plan....\n";
}
else
{
std::cout << "failure: we do not have a plan....\n";
}
}
// Execute plan:
if(fftrc == rocfft_status_success)
{
fftrc = rocfft_execute(gpu_plan, (void**)gpu_in.data(), (void**)gpu_out.data(), nullptr);
}
fftrc = rocfft_status_sync(fftrc, mpi_comm);
if(mpi_rank == 0)
{
if(fftrc == rocfft_status_success)
{
std::cout << "The FFT was successful....\n";
}
else
{
std::cout << "The FFT execution failed....\n";
}
}
// Copy back all batches; print only batch 0 for compactness
for(int irank = 0; irank < mpi_size; ++irank)
{
if(mpi_rank == irank)
{
std::cout << "out brick rank " << irank << " (showing batch 0 of " << nbatch << ")\n";
const size_t outcount = nbatch * outbrick_dist;
std::vector<std::complex<double>> host_out(outcount);
hiprc = hipMemcpy(host_out.data(),
gpu_out[0],
outcount * sizeof(std::complex<double>),
hipMemcpyDeviceToHost);
if(hiprc != hipSuccess)
throw std::runtime_error("hipMemcpy failed");
const size_t batch0 = 0;
for(auto idx1 = outbrick_lower[1]; idx1 < outbrick_upper[1]; ++idx1)
{
for(auto idx0 = outbrick_lower[0]; idx0 < outbrick_upper[0]; ++idx0)
{
const auto pos = batch0 * outbrick_stride[2]
+ (idx0 - outbrick_lower[0]) * outbrick_stride[0]
+ (idx1 - outbrick_lower[1]) * outbrick_stride[1];
std::cout << host_out[pos] << " ";
}
std::cout << "\n";
}
}
MPI_Barrier(mpi_comm);
}
// Cleanup anything plan-generation structs (that aren't null pointers):
if(description != nullptr)
{
if(rocfft_plan_description_destroy(description) != rocfft_status_success)
{
std::cerr << "description destruction failed\n";
}
else
{
description = nullptr;
}
}
// Clean up the plan and rocfft:
try
{
if(gpu_plan != nullptr)
{
if(rocfft_plan_destroy(gpu_plan) != rocfft_status_success)
throw std::runtime_error("rocfft_plan_destroy failed.");
gpu_plan = nullptr;
}
}
catch(const std::exception&)
{
std::cerr << "rank " << mpi_rank << " plan destroy failed\n";
}
for(auto& buf : gpu_in)
{
if(buf != nullptr)
{
hiprc = hipFree(buf);
if(hiprc != hipSuccess)
std::cerr << "hipFree failed\n";
buf = nullptr;
}
}
for(auto& buf : gpu_out)
{
if(buf != nullptr)
{
hiprc = hipFree(buf);
if(hiprc != hipSuccess)
std::cerr << "hipFree failed\n";
buf = nullptr;
}
}
fftrc = rocfft_cleanup();
MPI_Finalize();
return 0;
}