diff --git a/MPI_benchmark/compile-command b/MPI_benchmark/compile-command index a2ed87472..efa7ce21c 100644 --- a/MPI_benchmark/compile-command +++ b/MPI_benchmark/compile-command @@ -1,2 +1,4 @@ mpicxx -fsycl halo_mpi.cc -o halo_mpi mpicxx -O2 -std=c++11 io_mpi.cc -o io_mpi +# Frontier: (hipcc via mpicxx wrapper; ACC_HIP is the default in-file) +mpicxx -O2 -x hip gather_mpi.cc -o gather_mpi -L${ROCM_PATH}/lib -lamdhip64 diff --git a/MPI_benchmark/gather_mpi.cc b/MPI_benchmark/gather_mpi.cc new file mode 100644 index 000000000..fdfe69e99 --- /dev/null +++ b/MPI_benchmark/gather_mpi.cc @@ -0,0 +1,373 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/************************************************************** + * Distributed dense-panel gather benchmark / reproducer. + * + * Pattern under test: P ranks each OWN a contiguous block of rows + * of a (krows x ncols) fp64-complex panel; every rank must end + * holding the WHOLE panel. This is the setup-time collective of a + * distributed recursive Schur inversion (row-distributed dense + * matrices, rank-major ordering => owned rows contiguous). + * + * Three implementations of identical semantics: + * A zero-fill + MPI_Allreduce(SUM) (simple; non-owners send zeros) + * B MPI_Allgatherv (owners-only send) + * C ring allgather via MPI_Sendrecv (halo-exchange primitive) + * + * Measured motivation (Frontier, 288 ranks, ~340MB panels): pattern A + * on host buffers delivered 0.48 GB/s effective payload -- ~8% of its + * own per-node wire floor, ~13x below the efficiency the Sendrecv + * halo exchange achieves on the same NICs (185 GB/s bidirectional, + * see halo_mpi.cc). + * + * Config: what is the target + ************************************************************** + */ +#undef ACC_CUDA +#define ACC_HIP +#undef ACC_SYCL +#undef ACC_NONE + +/************************************************************** + * Some MPI globals + ************************************************************** + */ +MPI_Comm WorldComm; +MPI_Comm WorldShmComm; + +int WorldSize; +int WorldRank; + +int WorldShmSize; +int WorldShmRank; + +/************************************************************** + * Allocate buffers on the GPU, SYCL needs an init call and context + ************************************************************** + */ +#ifdef ACC_CUDA +#include +void acceleratorInit(void){} +void *acceleratorAllocDevice(size_t bytes) +{ + void *ptr=NULL; + auto err = cudaMalloc((void **)&ptr,bytes); + assert(err==cudaSuccess); + return ptr; +} +void acceleratorFreeDevice(void *ptr){ cudaFree(ptr);} +void acceleratorMemSet(void *ptr,int val,size_t bytes){ cudaMemset(ptr,val,bytes);} +void acceleratorCopyToDevice(const void *from,void *to,size_t bytes){ cudaMemcpy(to,from,bytes,cudaMemcpyHostToDevice);} +#endif +#ifdef ACC_HIP +#include +void acceleratorInit(void){} +inline void *acceleratorAllocDevice(size_t bytes) +{ + void *ptr=NULL; + auto err = hipMalloc((void **)&ptr,bytes); + if( err != hipSuccess ) { + ptr = (void *) NULL; + printf(" hipMalloc failed for %ld %s \n",bytes,hipGetErrorString(err)); + } + return ptr; +}; +inline void acceleratorFreeDevice(void *ptr){ auto r=hipFree(ptr);}; +inline void acceleratorMemSet(void *ptr,int val,size_t bytes){ auto r=hipMemset(ptr,val,bytes);}; +inline void acceleratorCopyToDevice(const void *from,void *to,size_t bytes){ auto r=hipMemcpy(to,from,bytes,hipMemcpyHostToDevice);}; +#endif +#ifdef ACC_SYCL +#include +#include +cl::sycl::queue *theAccelerator; +void acceleratorInit(void) +{ + cl::sycl::gpu_selector selector; + cl::sycl::device selectedDevice { selector }; + theAccelerator = new sycl::queue (selectedDevice); + auto name = theAccelerator->get_device().get_info(); + printf("AcceleratorSyclInit: Selected device is %s\n",name.c_str()); fflush(stdout); +} +inline void *acceleratorAllocDevice(size_t bytes){ return malloc_device(bytes,*theAccelerator);}; +inline void acceleratorFreeDevice(void *ptr){free(ptr,*theAccelerator);}; +inline void acceleratorMemSet(void *ptr,int val,size_t bytes){ theAccelerator->memset(ptr,val,bytes).wait();}; +inline void acceleratorCopyToDevice(const void *from,void *to,size_t bytes){ theAccelerator->memcpy(to,from,bytes).wait();}; +#endif +#ifdef ACC_NONE +void acceleratorInit(void){} +inline void *acceleratorAllocDevice(size_t bytes){ return malloc(bytes);}; +inline void acceleratorFreeDevice(void *ptr){free(ptr);}; +inline void acceleratorMemSet(void *ptr,int val,size_t bytes){ memset(ptr,val,bytes);}; +inline void acceleratorCopyToDevice(const void *from,void *to,size_t bytes){ memcpy(to,from,bytes);}; +#endif + +/************************************************************** + * Microsecond timer + ************************************************************** + */ +inline double usecond(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return 1.0e6*tv.tv_sec + 1.0*tv.tv_usec; +} + +/************************************************************** + * Main benchmark routine. + * + * panel = krows x ncols complex, column major, krows = P*myrows. + * Rank r owns rows [r*myrows, (r+1)*myrows): with column-major layout + * the owned data is strided; for B and C the owners' contribution is + * packed contiguously (rank-major block layout), which is exactly how + * the production code would call it. Effective payload = full panel + * bytes; every rank must hold it all at the end. + ************************************************************** + */ +void Benchmark(size_t panel_bytes,bool use_device,int ncall) +{ + size_t words = panel_bytes/sizeof(double); // treat as flat doubles + size_t mywords = words/WorldSize; + words = mywords*WorldSize; // exact division + size_t bytes = words*sizeof(double); + size_t mybytes= mywords*sizeof(double); + + double *panel; + double *contrib; + if ( use_device ) { + panel = (double *)acceleratorAllocDevice(bytes); + contrib = (double *)acceleratorAllocDevice(mybytes); + if ( panel==NULL || contrib==NULL ) { printf("alloc failed\n"); return; } + } else { + panel = (double *)malloc(bytes); + contrib = (double *)malloc(mybytes); + } + // Owners deposit non-trivial data once (content is irrelevant to timing) + std::vector init(mywords,1.0*WorldRank); + acceleratorCopyToDevice(&init[0],contrib,mybytes); + if ( !use_device ) memcpy(contrib,&init[0],mybytes); + + double tA,tB,tC; + + /********************************************************* + * A: zero-fill + Allreduce(SUM) -- the simple idiom + *********************************************************/ + { + MPI_Barrier(WorldComm); + double t0=usecond(); + for(int n=0;n counts(WorldSize,(int)mywords); + std::vector displs(WorldSize); + for(int r=0;r0.0) ? GB/(tA2/1.0e6) : 0.0, + GB/(tD/1.0e6), + GB/(tB/1.0e6), + GB/(tC/1.0e6)); + fflush(stdout); + } + + if ( use_device ) { + acceleratorFreeDevice(panel); + acceleratorFreeDevice(contrib); + } else { + free(panel); + free(contrib); + } +} + +/************************************** + * Command line junk + **************************************/ +int main(int argc, char **argv) +{ + acceleratorInit(); + + MPI_Init(&argc,&argv); + + WorldComm = MPI_COMM_WORLD; + + MPI_Comm_split_type(WorldComm, MPI_COMM_TYPE_SHARED, 0, MPI_INFO_NULL,&WorldShmComm); + + MPI_Comm_rank(WorldComm ,&WorldRank); + MPI_Comm_size(WorldComm ,&WorldSize); + + MPI_Comm_rank(WorldShmComm ,&WorldShmRank); + MPI_Comm_size(WorldShmComm ,&WorldShmSize); + + if( !WorldRank ) { + printf("***********************************\n"); + printf("%d ranks\n",WorldSize); + printf("%d ranks-per-node\n",WorldShmSize); + printf("%d nodes\n",WorldSize/WorldShmSize);fflush(stdout); + printf("***********************************\n"); + printf("Panel gather: every rank owns 1/%d of the rows;\n",WorldSize); + printf("every rank must finish holding the whole panel.\n"); + printf("Effective payload rate = panel bytes / wall. Same semantics x3:\n"); + printf(" A zero-fill+Allreduce B Allgatherv C ring Sendrecv\n"); + } + + std::vector sizes({ (size_t)8<<20, (size_t)64<<20, (size_t)256<<20, (size_t)1<<30 }); + + if( !WorldRank ) { + printf("=========================================================\n"); + printf("= HOST memory \n"); + printf("=========================================================\n");fflush(stdout); + } + for( auto sz : sizes ) Benchmark(sz,false,5); + + if( !WorldRank ) { + printf("=========================================================\n"); + printf("= DEVICE memory \n"); + printf("=========================================================\n");fflush(stdout); + } + for( auto sz : sizes ) Benchmark(sz,true,5); + + if( !WorldRank ) { + printf("=========================================================\n"); + printf("= DONE \n"); + printf("=========================================================\n"); + } + MPI_Finalize(); +}