#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(); }