mirror of
https://github.com/paboyle/Grid.git
synced 2026-09-04 16:59:36 +01:00
Barrel shift -> all to all (x2) and distributed FFT work fully load balanced without redundant work. There is little more I can do now on FFT. Comms dominated and running distributed work dividing bandwidth optimal RingAllToAll /ccs/home/paboyle/ParallelIO/systems/Frontier/tests/core/Test_fft_prop --mpi 3.6.4.4 --grid 48.48.48.96 --accelerator-threads 8 --shm 4096 --shm-mpi 1 --device-mem 32000 --log Error,Warning,Message,Performance ************************************************* Benchmarking FFT of LatticeFermionD on plane wave ************************************************* Grid : Performance : 0.501524 s : FFT took 0.001311 s (transpose P=3) Grid : Performance : 0.501531 s : FFT pack 5.9e-05 s Grid : Performance : 0.501533 s : FFT alltoall 0.000828 s Grid : Performance : 0.501534 s : FFT reorder 0.000204 s Grid : Performance : 0.501535 s : FFT kernels 1e-05 s Grid : Performance : 0.501536 s : FFT unpack 5e-05 s Grid : Performance : 0.509992 s : FFT took 0.001829 s (transpose P=6) Grid : Performance : 0.510000 s : FFT pack 6e-05 s Grid : Performance : 0.510002 s : FFT alltoall 0.001436 s Grid : Performance : 0.510003 s : FFT reorder 0.000202 s Grid : Performance : 0.510005 s : FFT kernels 9e-06 s Grid : Performance : 0.510006 s : FFT unpack 5.3e-05 s Grid : Performance : 0.517690 s : FFT took 0.001599 s (transpose P=4) Grid : Performance : 0.517698 s : FFT pack 6e-05 s Grid : Performance : 0.517700 s : FFT alltoall 0.001258 s Grid : Performance : 0.517701 s : FFT reorder 0.0002 s Grid : Performance : 0.517702 s : FFT kernels 9e-06 s Grid : Performance : 0.517703 s : FFT unpack 4.9e-05 s Grid : Performance : 0.524858 s : FFT took 0.001561 s (transpose P=4) Grid : Performance : 0.524865 s : FFT pack 5.8e-05 s Grid : Performance : 0.524867 s : FFT alltoall 0.001213 s Grid : Performance : 0.524868 s : FFT reorder 0.000209 s Grid : Performance : 0.524869 s : FFT kernels 8e-06 s Grid : Performance : 0.524870 s : FFT unpack 4.9e-05 s ************************************************* FFT of [48 48 48 96] LatticeFermionD took 0.030916 s *************************************************
706 lines
25 KiB
C++
706 lines
25 KiB
C++
/*************************************************************************************
|
|
|
|
Grid physics library, www.github.com/paboyle/Grid
|
|
|
|
Source file: ./lib/Cshift.h
|
|
|
|
Copyright (C) 2015
|
|
|
|
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
See the full license in the file "LICENSE" in the top level distribution directory
|
|
*************************************************************************************/
|
|
/* END LEGAL */
|
|
#ifndef _GRID_FFT_H_
|
|
#define _GRID_FFT_H_
|
|
|
|
#ifdef GRID_CUDA
|
|
#include <cufft.h>
|
|
#endif
|
|
|
|
#ifdef GRID_HIP
|
|
#include <hipfft/hipfft.h>
|
|
#endif
|
|
|
|
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
|
|
#ifdef HAVE_FFTW
|
|
#if defined(USE_MKL) || defined(GRID_SYCL)
|
|
#include <fftw/fftw3.h>
|
|
#else
|
|
#include <fftw3.h>
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
NAMESPACE_BEGIN(Grid);
|
|
|
|
#ifndef FFTW_FORWARD
|
|
#define FFTW_FORWARD (-1)
|
|
#define FFTW_BACKWARD (+1)
|
|
#define FFTW_ESTIMATE (0)
|
|
#endif
|
|
|
|
template<class scalar> struct FFTW {
|
|
};
|
|
|
|
#ifdef GRID_HIP
|
|
template<> struct FFTW<ComplexD> {
|
|
public:
|
|
static const int forward=FFTW_FORWARD;
|
|
static const int backward=FFTW_BACKWARD;
|
|
typedef hipfftDoubleComplex FFTW_scalar;
|
|
typedef hipfftHandle FFTW_plan;
|
|
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
|
FFTW_scalar *in, int *inembed,
|
|
int istride, int idist,
|
|
FFTW_scalar *out, int *onembed,
|
|
int ostride, int odist,
|
|
int sign, unsigned flags) {
|
|
FFTW_plan p;
|
|
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_Z2Z,howmany);
|
|
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
|
return p;
|
|
}
|
|
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
|
hipfftResult rv;
|
|
if ( sign == forward ) rv =hipfftExecZ2Z(p,in,out,HIPFFT_FORWARD);
|
|
else rv =hipfftExecZ2Z(p,in,out,HIPFFT_BACKWARD);
|
|
accelerator_barrier();
|
|
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
|
}
|
|
inline static void fftw_destroy_plan(const FFTW_plan p) { hipfftDestroy(p); }
|
|
};
|
|
template<> struct FFTW<ComplexF> {
|
|
public:
|
|
static const int forward=FFTW_FORWARD;
|
|
static const int backward=FFTW_BACKWARD;
|
|
typedef hipfftComplex FFTW_scalar;
|
|
typedef hipfftHandle FFTW_plan;
|
|
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
|
FFTW_scalar *in, int *inembed,
|
|
int istride, int idist,
|
|
FFTW_scalar *out, int *onembed,
|
|
int ostride, int odist,
|
|
int sign, unsigned flags) {
|
|
FFTW_plan p;
|
|
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_C2C,howmany);
|
|
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
|
return p;
|
|
}
|
|
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
|
hipfftResult rv;
|
|
if ( sign == forward ) rv =hipfftExecC2C(p,in,out,HIPFFT_FORWARD);
|
|
else rv =hipfftExecC2C(p,in,out,HIPFFT_BACKWARD);
|
|
accelerator_barrier();
|
|
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
|
}
|
|
inline static void fftw_destroy_plan(const FFTW_plan p) { hipfftDestroy(p); }
|
|
};
|
|
#endif
|
|
|
|
#ifdef GRID_CUDA
|
|
template<> struct FFTW<ComplexD> {
|
|
public:
|
|
static const int forward=FFTW_FORWARD;
|
|
static const int backward=FFTW_BACKWARD;
|
|
typedef cufftDoubleComplex FFTW_scalar;
|
|
typedef cufftHandle FFTW_plan;
|
|
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
|
FFTW_scalar *in, int *inembed,
|
|
int istride, int idist,
|
|
FFTW_scalar *out, int *onembed,
|
|
int ostride, int odist,
|
|
int sign, unsigned flags) {
|
|
FFTW_plan p;
|
|
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_Z2Z,howmany);
|
|
return p;
|
|
}
|
|
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
|
if ( sign == forward ) cufftExecZ2Z(p,in,out,CUFFT_FORWARD);
|
|
else cufftExecZ2Z(p,in,out,CUFFT_INVERSE);
|
|
accelerator_barrier();
|
|
}
|
|
inline static void fftw_destroy_plan(const FFTW_plan p) { cufftDestroy(p); }
|
|
};
|
|
template<> struct FFTW<ComplexF> {
|
|
public:
|
|
static const int forward=FFTW_FORWARD;
|
|
static const int backward=FFTW_BACKWARD;
|
|
typedef cufftComplex FFTW_scalar;
|
|
typedef cufftHandle FFTW_plan;
|
|
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
|
FFTW_scalar *in, int *inembed,
|
|
int istride, int idist,
|
|
FFTW_scalar *out, int *onembed,
|
|
int ostride, int odist,
|
|
int sign, unsigned flags) {
|
|
FFTW_plan p;
|
|
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_C2C,howmany);
|
|
return p;
|
|
}
|
|
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
|
if ( sign == forward ) cufftExecC2C(p,in,out,CUFFT_FORWARD);
|
|
else cufftExecC2C(p,in,out,CUFFT_INVERSE);
|
|
accelerator_barrier();
|
|
}
|
|
inline static void fftw_destroy_plan(const FFTW_plan p) { cufftDestroy(p); }
|
|
};
|
|
#endif
|
|
|
|
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
|
|
#ifdef HAVE_FFTW
|
|
template<> struct FFTW<ComplexD> {
|
|
public:
|
|
typedef fftw_complex FFTW_scalar;
|
|
typedef fftw_plan FFTW_plan;
|
|
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
|
FFTW_scalar *in, int *inembed,
|
|
int istride, int idist,
|
|
FFTW_scalar *out, int *onembed,
|
|
int ostride, int odist,
|
|
int sign, unsigned flags) {
|
|
return ::fftw_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
|
|
}
|
|
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
|
::fftw_execute_dft(p,in,out);
|
|
}
|
|
inline static void fftw_destroy_plan(const FFTW_plan p) { ::fftw_destroy_plan(p); }
|
|
};
|
|
template<> struct FFTW<ComplexF> {
|
|
public:
|
|
typedef fftwf_complex FFTW_scalar;
|
|
typedef fftwf_plan FFTW_plan;
|
|
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
|
FFTW_scalar *in, int *inembed,
|
|
int istride, int idist,
|
|
FFTW_scalar *out, int *onembed,
|
|
int ostride, int odist,
|
|
int sign, unsigned flags) {
|
|
return ::fftwf_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
|
|
}
|
|
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
|
::fftwf_execute_dft(p,in,out);
|
|
}
|
|
inline static void fftw_destroy_plan(const FFTW_plan p) { ::fftwf_destroy_plan(p); }
|
|
};
|
|
#endif
|
|
#endif
|
|
|
|
struct FFTbase {
|
|
double flops;
|
|
double flops_call;
|
|
uint64_t usec;
|
|
GridCartesian *_grid;
|
|
|
|
static const int forward = FFTW_FORWARD;
|
|
static const int backward = FFTW_BACKWARD;
|
|
|
|
double Flops(void) { return flops; }
|
|
double MFlops(void) { return flops / usec; }
|
|
double USec(void) { return (double)usec; }
|
|
|
|
FFTbase(GridCartesian *grid) : _grid(grid), flops(0), flops_call(0), usec(0) {}
|
|
};
|
|
|
|
// Barrel-shift gather, FFT execute, and insert. Called by both FFT and PlannedFFT.
|
|
// The caller is responsible for plan acquisition and destruction.
|
|
template<class vobj>
|
|
static void FFT_dim_execute(
|
|
Lattice<vobj> &result,
|
|
const Lattice<vobj> &source,
|
|
int dim, int sign,
|
|
typename FFTW<typename vobj::scalar_type>::FFTW_plan p,
|
|
GridCartesian *grid,
|
|
double &flops, double &flops_call, uint64_t &usec)
|
|
{
|
|
typedef typename vobj::scalar_type scalar;
|
|
typedef typename vobj::scalar_object sobj;
|
|
typedef typename vobj::scalar_type scalar_type;
|
|
typedef typename vobj::vector_type vector_type;
|
|
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
|
|
|
#if 0
|
|
// ======================= ORIGINAL barrel-shift path =======================
|
|
// Preserved for reference. Superseded by the transpose / all-to-all path
|
|
// below (the single active path for ALL P): the barrel is a P-fold redundant
|
|
// all-gather -- every rank assembles and transforms all Nperp lines of length
|
|
// G, keeping only its own L points. The transpose partitions the Nperp
|
|
// perpendicular lines across the P ranks along dim, so each rank transforms
|
|
// only ceil(Nperp/P) lines and moves (P-1)/P of its data instead of P-1
|
|
// redundant copies. See CartesianRingAllToAll in communicator/RingAllReduce.h.
|
|
const int Ndim = grid->Nd();
|
|
int L = grid->_ldimensions[dim];
|
|
int G = grid->_fdimensions[dim];
|
|
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
|
int64_t Nlow = 1, Nhigh = 1;
|
|
for (int d = 0; d < dim; d++) Nlow *= grid->_ldimensions[d];
|
|
for (int d = dim+1; d < Ndim; d++) Nhigh *= grid->_ldimensions[d];
|
|
int64_t Nperp = Nlow * Nhigh;
|
|
|
|
deviceVector<scalar> pgbuf(Nperp * Ncomp * G);
|
|
scalar *pgbuf_v = &pgbuf[0];
|
|
int howmany = Ncomp * Nperp;
|
|
|
|
scalar div;
|
|
if (sign == FFTW_BACKWARD) div = 1.0 / G;
|
|
else if (sign == FFTW_FORWARD) div = 1.0;
|
|
else GRID_ASSERT(0);
|
|
|
|
double t_pencil = 0, t_fft = 0, t_copy = 0, t_shift = 0;
|
|
double t_total = -usecond();
|
|
|
|
result = source;
|
|
int pc = grid->_processor_coor[dim];
|
|
|
|
const Coordinate ldims = grid->_ldimensions;
|
|
const Coordinate rdims = grid->_rdimensions;
|
|
const Coordinate sdims = grid->_simd_layout;
|
|
const Coordinate processors = grid->_processors;
|
|
|
|
Coordinate pgdims(Ndim);
|
|
pgdims[0] = G;
|
|
for (int d = 0, dd = 1; d < Ndim; d++)
|
|
if (d != dim) pgdims[dd++] = ldims[d];
|
|
int64_t pgvol = 1;
|
|
for (int d = 0; d < Ndim; d++) pgvol *= pgdims[d];
|
|
|
|
const int Nsimd = vobj::Nsimd();
|
|
t_pencil = -usecond();
|
|
for (int p_idx = 0; p_idx < processors[dim]; p_idx++) {
|
|
t_copy -= usecond();
|
|
autoView(r_v, result, AcceleratorRead);
|
|
accelerator_for(idx, grid->oSites(), vobj::Nsimd(), {
|
|
#ifdef GRID_SIMT
|
|
{
|
|
int lane = acceleratorSIMTlane(Nsimd);
|
|
#else
|
|
for (int lane = 0; lane < Nsimd; lane++) {
|
|
#endif
|
|
Coordinate icoor, ocoor, pgcoor;
|
|
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
|
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
|
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + ((pc+p_idx)%processors[dim])*L;
|
|
for (int d = 0, dd = 1; d < Ndim; d++)
|
|
if (d != dim) { pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d]; dd++; }
|
|
int64_t pgidx;
|
|
Lexicographic::IndexFromCoor(pgcoor, pgidx, pgdims);
|
|
vector_type *from = (vector_type *)&r_v[idx];
|
|
scalar_type stmp;
|
|
for (int w = 0; w < Ncomp; w++) {
|
|
stmp = getlane(from[w], lane);
|
|
pgbuf_v[pgidx + w*pgvol] = stmp;
|
|
}
|
|
#ifdef GRID_SIMT
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
});
|
|
t_copy += usecond();
|
|
if (p_idx != processors[dim] - 1) {
|
|
Lattice<vobj> temp(grid);
|
|
t_shift -= usecond();
|
|
temp = Cshift(result, dim, L); result = temp;
|
|
t_shift += usecond();
|
|
}
|
|
}
|
|
t_pencil += usecond();
|
|
|
|
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
|
|
FFTW_scalar *out = (FFTW_scalar *)pgbuf_v;
|
|
t_fft = -usecond();
|
|
FFTW<scalar>::fftw_execute_dft(p, in, out, sign);
|
|
t_fft += usecond();
|
|
|
|
flops_call = 5.0 * howmany * G * log2(G);
|
|
usec = t_fft;
|
|
flops = flops_call;
|
|
|
|
result = Zero();
|
|
double t_insert = -usecond();
|
|
{
|
|
autoView(r_v, result, AcceleratorWrite);
|
|
accelerator_for(idx, grid->oSites(), Nsimd, {
|
|
#ifdef GRID_SIMT
|
|
{
|
|
int lane = acceleratorSIMTlane(Nsimd);
|
|
#else
|
|
for (int lane = 0; lane < Nsimd; lane++) {
|
|
#endif
|
|
Coordinate icoor(Ndim), ocoor(Ndim), pgcoor(Ndim);
|
|
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
|
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
|
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + pc*L;
|
|
for (int d = 0, dd = 1; d < Ndim; d++)
|
|
if (d != dim) { pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d]; dd++; }
|
|
int64_t pgidx;
|
|
Lexicographic::IndexFromCoor(pgcoor, pgidx, pgdims);
|
|
vector_type *to = (vector_type *)&r_v[idx];
|
|
scalar_type stmp;
|
|
for (int w = 0; w < Ncomp; w++) {
|
|
stmp = pgbuf_v[pgidx + w*pgvol];
|
|
putlane(to[w], stmp, lane);
|
|
}
|
|
#ifdef GRID_SIMT
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
});
|
|
}
|
|
result = result * div;
|
|
t_insert += usecond();
|
|
t_total += usecond();
|
|
|
|
std::cout << GridLogPerformance << " FFT took " << t_total/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT pencil " << t_pencil/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " of which copy " << t_copy/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " of which shift" << t_shift/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT kernels " << t_fft/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT insert " << t_insert/1.0e6 << " s" << std::endl;
|
|
#endif
|
|
|
|
// ==================== transpose / all-to-all pencil FFT ====================
|
|
//
|
|
// P ranks lie along dim; each holds a local L-slab (L=ldim) of every global
|
|
// line (length G=fdim=L*P) and Nperp perpendicular lines. We partition the
|
|
// Nperp lines across the P ranks: the rank at coord `own` owns the lines with
|
|
// olin/Oloc == own, gathers their full G points via a cartesian all-to-all,
|
|
// transforms only Oloc = ceil(Nperp/P) of them, then scatters the result back
|
|
// with the inverse all-to-all.
|
|
//
|
|
// The owned-line count is CEIL-padded to a multiple of P (Oloc*P >= Nperp) so
|
|
// the all-to-all stays SYMMETRIC (one uniform chunk) for ANY (P,Nperp) -- in
|
|
// particular a P carrying a factor absent from Nperp, e.g. P=3 on the T axis
|
|
// of 128^3x288 whose perpendicular volume is a pure power of two. This keeps
|
|
// the transpose as total over decompositions as the barrel it replaces (no
|
|
// new geometry constraint), at a cost of <= (P-1) padded lines out of Nperp.
|
|
// Padding slots olin in [Nperp, Oloc*P) are never packed and never unpacked.
|
|
//
|
|
// The load-bearing identity: the all-to-all block index == cartesian coord
|
|
// along dim == which L-slab [c*L, c*L+L) of the global line -- so the block a
|
|
// rank receives carries the global-x tag needed to order the FFT input.
|
|
//
|
|
// Degenerate P: P=1 -> both all-to-alls are self-copies and the reorders are
|
|
// the identity (G=L), i.e. a pure local FFT. P=2 -> each all-to-all moves
|
|
// half a field, two of them one field, equal to the barrel's single Cshift.
|
|
{
|
|
const int Ndim = grid->Nd();
|
|
int L = grid->_ldimensions[dim];
|
|
int G = grid->_fdimensions[dim];
|
|
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
|
int P = grid->_processors[dim];
|
|
int64_t Nperp = 1;
|
|
for (int d = 0; d < Ndim; d++)
|
|
if (d != dim) Nperp *= grid->_ldimensions[d];
|
|
|
|
int64_t Oloc = (Nperp + P - 1) / P; // ceil: owned (padded) lines/rank
|
|
int64_t chunk = (int64_t)L * Oloc * Ncomp; // one all-to-all block (uniform)
|
|
int64_t nbuf = (int64_t)P * chunk; // == Ncomp*Oloc*G, one field's worth
|
|
int64_t howmany_local = (int64_t)Ncomp * Oloc;
|
|
|
|
scalar div;
|
|
if (sign == FFTW_BACKWARD) div = 1.0 / G;
|
|
else if (sign == FFTW_FORWARD) div = 1.0;
|
|
else GRID_ASSERT(0);
|
|
|
|
double t_total = -usecond();
|
|
double t_pack = 0, t_a2a = 0, t_reorder = 0, t_fft = 0, t_unpack = 0;
|
|
|
|
deviceVector<scalar> sbuf(nbuf);
|
|
deviceVector<scalar> rbuf(nbuf);
|
|
deviceVector<scalar> pgbuf(nbuf); // FFTW pencil buffer, Ncomp*Oloc lines of G
|
|
scalar *sbuf_v = &sbuf[0];
|
|
scalar *rbuf_v = &rbuf[0];
|
|
scalar *pgbuf_v = &pgbuf[0];
|
|
|
|
// deterministic ceil-pad slots (never read back, but keeps padded FFT lines finite)
|
|
acceleratorMemSet(sbuf_v, 0, nbuf*sizeof(scalar));
|
|
|
|
const Coordinate ldims = grid->_ldimensions;
|
|
const Coordinate rdims = grid->_rdimensions;
|
|
const Coordinate sdims = grid->_simd_layout;
|
|
const int Nsimd = vobj::Nsimd();
|
|
|
|
// ---- 1. pack: source -> sbuf. block = owner coord; payload xloc + L*(slot + Oloc*w)
|
|
t_pack -= usecond();
|
|
{
|
|
autoView(s_v, source, AcceleratorRead);
|
|
accelerator_for(idx, grid->oSites(), Nsimd, {
|
|
#ifdef GRID_SIMT
|
|
{
|
|
int lane = acceleratorSIMTlane(Nsimd);
|
|
#else
|
|
for (int lane = 0; lane < Nsimd; lane++) {
|
|
#endif
|
|
Coordinate icoor(Ndim), ocoor(Ndim);
|
|
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
|
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
|
int64_t xloc = ocoor[dim] + icoor[dim]*rdims[dim];
|
|
int64_t olin = 0, str = 1;
|
|
for (int d = 0; d < Ndim; d++) {
|
|
if (d == dim) continue;
|
|
int64_t c = ocoor[d] + icoor[d]*rdims[d];
|
|
olin += str * c;
|
|
str *= ldims[d];
|
|
}
|
|
int64_t own = olin / Oloc;
|
|
int64_t slot = olin - own*Oloc;
|
|
vector_type *from = (vector_type *)&s_v[idx];
|
|
for (int w = 0; w < Ncomp; w++) {
|
|
scalar_type stmp = getlane(from[w], lane);
|
|
sbuf_v[ own*chunk + xloc + L*(slot + Oloc*w) ] = stmp;
|
|
}
|
|
#ifdef GRID_SIMT
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
});
|
|
}
|
|
t_pack += usecond();
|
|
|
|
// ---- 2. forward all-to-all: gather my owned lines' L-slabs from every rank
|
|
t_a2a -= usecond();
|
|
CartesianRingAllToAll(grid, sbuf_v, rbuf_v, (uint64_t)chunk, dim);
|
|
t_a2a += usecond();
|
|
|
|
// ---- 3. reorder rbuf -> pgbuf: contiguous G-lines (w,slot), xpos = src*L + xloc
|
|
t_reorder -= usecond();
|
|
accelerator_for(q, nbuf, 1, {
|
|
int64_t xpos = q % G;
|
|
int64_t t = q / G; // = w*Oloc + slot
|
|
int64_t slot = t % Oloc;
|
|
int64_t w = t / Oloc;
|
|
int64_t src = xpos / L;
|
|
int64_t xloc = xpos % L;
|
|
pgbuf_v[q] = rbuf_v[ src*chunk + xloc + L*(slot + Oloc*w) ];
|
|
});
|
|
t_reorder += usecond();
|
|
|
|
// ---- 4. FFT: Ncomp*Oloc contiguous lines of length G (istride 1, idist G)
|
|
{
|
|
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
|
|
FFTW_scalar *out = (FFTW_scalar *)pgbuf_v;
|
|
t_fft -= usecond();
|
|
FFTW<scalar>::fftw_execute_dft(p, in, out, sign);
|
|
t_fft += usecond();
|
|
}
|
|
flops_call = 5.0 * (double)howmany_local * G * log2(G);
|
|
usec = (uint64_t)t_fft;
|
|
flops = flops_call;
|
|
|
|
// ---- 5a. reorder pgbuf -> sbuf: block = destination coord; xpos = dst*L + xloc
|
|
t_reorder -= usecond();
|
|
accelerator_for(j, nbuf, 1, {
|
|
int64_t dst = j / chunk;
|
|
int64_t r = j % chunk;
|
|
int64_t xloc = r % L;
|
|
int64_t u = r / L; // = slot + Oloc*w
|
|
int64_t slot = u % Oloc;
|
|
int64_t w = u / Oloc;
|
|
int64_t xpos = dst*L + xloc;
|
|
sbuf_v[j] = pgbuf_v[ w*Oloc*G + slot*G + xpos ];
|
|
});
|
|
t_reorder += usecond();
|
|
|
|
// ---- 5b. inverse all-to-all: scatter transformed L-slabs back
|
|
t_a2a -= usecond();
|
|
CartesianRingAllToAll(grid, sbuf_v, rbuf_v, (uint64_t)chunk, dim);
|
|
t_a2a += usecond();
|
|
|
|
// ---- 5c. unpack rbuf -> result (x div); block = owner coord of each line
|
|
t_unpack -= usecond();
|
|
{
|
|
autoView(r_v, result, AcceleratorWrite);
|
|
accelerator_for(idx, grid->oSites(), Nsimd, {
|
|
#ifdef GRID_SIMT
|
|
{
|
|
int lane = acceleratorSIMTlane(Nsimd);
|
|
#else
|
|
for (int lane = 0; lane < Nsimd; lane++) {
|
|
#endif
|
|
Coordinate icoor(Ndim), ocoor(Ndim);
|
|
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
|
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
|
int64_t xloc = ocoor[dim] + icoor[dim]*rdims[dim];
|
|
int64_t olin = 0, str = 1;
|
|
for (int d = 0; d < Ndim; d++) {
|
|
if (d == dim) continue;
|
|
int64_t c = ocoor[d] + icoor[d]*rdims[d];
|
|
olin += str * c;
|
|
str *= ldims[d];
|
|
}
|
|
int64_t own = olin / Oloc;
|
|
int64_t slot = olin - own*Oloc;
|
|
vector_type *to = (vector_type *)&r_v[idx];
|
|
for (int w = 0; w < Ncomp; w++) {
|
|
scalar_type stmp = div * rbuf_v[ own*chunk + xloc + L*(slot + Oloc*w) ];
|
|
putlane(to[w], stmp, lane);
|
|
}
|
|
#ifdef GRID_SIMT
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
});
|
|
}
|
|
t_unpack += usecond();
|
|
t_total += usecond();
|
|
|
|
std::cout << GridLogPerformance << " FFT took " << t_total/1.0e6 << " s (transpose P=" << P << ")" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT pack " << t_pack/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT alltoall " << t_a2a/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT reorder " << t_reorder/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT kernels " << t_fft/1.0e6 << " s" << std::endl;
|
|
std::cout << GridLogPerformance << " FFT unpack " << t_unpack/1.0e6 << " s" << std::endl;
|
|
}
|
|
}
|
|
|
|
class FFT : public FFTbase {
|
|
public:
|
|
FFT(GridCartesian *grid) : FFTbase(grid) {}
|
|
~FFT() {}
|
|
|
|
template<class vobj>
|
|
void FFT_dim_mask(Lattice<vobj> &result, const Lattice<vobj> &source, Coordinate mask, int sign) {
|
|
const int Ndim = _grid->Nd();
|
|
Lattice<vobj> tmp = source;
|
|
for (int d = 0; d < Ndim; d++) {
|
|
if (mask[d]) {
|
|
FFT_dim(result, tmp, d, sign);
|
|
tmp = result;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class vobj>
|
|
void FFT_all_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int sign) {
|
|
Coordinate mask(_grid->Nd(), 1);
|
|
FFT_dim_mask(result, source, mask, sign);
|
|
}
|
|
|
|
template<class vobj>
|
|
void FFT_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int dim, int sign) {
|
|
GRID_ASSERT(source.Grid() == _grid);
|
|
GRID_ASSERT(result.Grid() == _grid);
|
|
conformable(result.Grid(), source.Grid());
|
|
|
|
typedef typename vobj::scalar_type scalar;
|
|
typedef typename vobj::scalar_object sobj;
|
|
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
|
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
|
|
|
|
const int Ndim = _grid->Nd();
|
|
int G = _grid->_fdimensions[dim];
|
|
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
|
int64_t Nperp = 1;
|
|
for (int d = 0; d < Ndim; d++)
|
|
if (d != dim) Nperp *= _grid->_ldimensions[d];
|
|
int P = _grid->_processors[dim];
|
|
int64_t Oloc = (Nperp + P - 1) / P; // ceil-padded owned lines/rank (see FFT_dim_execute)
|
|
int n[] = {G};
|
|
int howmany = Ncomp * (int)Oloc;
|
|
|
|
deviceVector<scalar> dummy(2);
|
|
FFTW_scalar *buf = (FFTW_scalar *)&dummy[0];
|
|
FFTW_plan p = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany,
|
|
buf, n, 1, G,
|
|
buf, n, 1, G,
|
|
sign, FFTW_ESTIMATE);
|
|
FFT_dim_execute(result, source, dim, sign, p, _grid, flops, flops_call, usec);
|
|
FFTW<scalar>::fftw_destroy_plan(p);
|
|
}
|
|
};
|
|
|
|
template<class vobj>
|
|
class PlannedFFT : public FFTbase {
|
|
private:
|
|
typedef typename vobj::scalar_type scalar;
|
|
typedef typename vobj::scalar_object sobj;
|
|
typedef typename vobj::vector_type vector_type;
|
|
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
|
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
|
|
|
|
std::vector<FFTW_plan> forward_plans;
|
|
std::vector<FFTW_plan> backward_plans;
|
|
|
|
void PlanCreate() {
|
|
const int Ndim = _grid->Nd();
|
|
forward_plans.resize(Ndim);
|
|
backward_plans.resize(Ndim);
|
|
|
|
for (int d = 0; d < Ndim; d++) {
|
|
int G = _grid->_fdimensions[d];
|
|
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
|
int64_t Nperp = 1;
|
|
for (int dd = 0; dd < Ndim; dd++)
|
|
if (dd != d) Nperp *= _grid->_ldimensions[dd];
|
|
int P = _grid->_processors[d];
|
|
int64_t Oloc = (Nperp + P - 1) / P; // ceil-padded owned lines/rank (see FFT_dim_execute)
|
|
int howmany = Ncomp * (int)Oloc;
|
|
int n[] = {G};
|
|
|
|
deviceVector<scalar> dummy(2);
|
|
FFTW_scalar *buf = (FFTW_scalar *)&dummy[0];
|
|
|
|
forward_plans[d] = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany, buf, n, 1, G, buf, n, 1, G, FFTW_FORWARD, FFTW_ESTIMATE);
|
|
backward_plans[d] = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany, buf, n, 1, G, buf, n, 1, G, FFTW_BACKWARD, FFTW_ESTIMATE);
|
|
}
|
|
}
|
|
|
|
void PlanDestroy() {
|
|
for (auto p : forward_plans) FFTW<scalar>::fftw_destroy_plan(p);
|
|
for (auto p : backward_plans) FFTW<scalar>::fftw_destroy_plan(p);
|
|
forward_plans.clear();
|
|
backward_plans.clear();
|
|
}
|
|
|
|
public:
|
|
PlannedFFT(GridCartesian *grid) : FFTbase(grid) { PlanCreate(); }
|
|
~PlannedFFT() { PlanDestroy(); }
|
|
|
|
void FFT_dim_mask(Lattice<vobj> &result, const Lattice<vobj> &source, Coordinate mask, int sign) {
|
|
const int Ndim = _grid->Nd();
|
|
Lattice<vobj> tmp = source;
|
|
for (int d = 0; d < Ndim; d++) {
|
|
if (mask[d]) {
|
|
FFT_dim(result, tmp, d, sign);
|
|
tmp = result;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FFT_all_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int sign) {
|
|
Coordinate mask(_grid->Nd(), 1);
|
|
FFT_dim_mask(result, source, mask, sign);
|
|
}
|
|
|
|
void FFT_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int dim, int sign) {
|
|
GRID_ASSERT(source.Grid() == _grid);
|
|
GRID_ASSERT(result.Grid() == _grid);
|
|
GRID_ASSERT((int)forward_plans.size() == _grid->Nd());
|
|
conformable(result.Grid(), source.Grid());
|
|
FFTW_plan p = (sign == forward ? forward_plans : backward_plans)[dim];
|
|
FFT_dim_execute(result, source, dim, sign, p, _grid, flops, flops_call, usec);
|
|
}
|
|
};
|
|
|
|
NAMESPACE_END(Grid);
|
|
|
|
#endif
|