/******************************************************************************** * Copyright (c) 2024 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. ********************************************************************************/ #ifndef SpTS_H #define SpTS_H #define TEST_NUM 9999999999ULL #include "InputFlags.h" #include "SparseMatrix.h" #include "MatrixMarketReader.h" #include #include #include #include #include #include #include #include #include #ifdef USE_ROC_SHMEM #include "roc_shmem.hpp" #include "mpi.h" #endif #ifdef USE_HIP #include "spts_kernel.h" #endif #ifdef DBL_DECIMAL_DIG #define OP_DBL_Digs (DBL_DECIMAL_DIG) #else #ifdef DECIMAL_DIG #define OP_DBL_Digs (DECIMAL_DIG) #else #define OP_DBL_Digs (DBL_DIG + 3) #endif #endif #ifdef FLT_DECIMAL_DIG #define OP_FLT_Digs (FLT_DECIMAL_DIG) #else #ifdef DECIMAL_DIG #define OP_FLT_Digs (DECIMAL_DIG) #else #define OP_FLT_Digs (FLT_DIG + 3) #endif #endif using namespace rocshmem; template class SparseTriangularSolve : public InputFlags, public SparseMatrix { FloatType *x; FloatType *y; FloatType *y_zero; FloatType *yref; std::vector rowBlocks; memPointer xDev; memPointer yDev; memPointer completedRowsDev; memPointer rowBlocksDev; memPointer doneArrayDev; memPointer shadowDoneArrayDev; memPointer remoteInProgressArrayDev; memPointer reqUpdateArrayDev; memPointer numRowsAtLevelDev; memPointer maxDepthDev; memPointer rowMapDev; memPointer totalSpinDev; memPointer oneBufDev; int nNZ; int nRows; int nCols; int numBlocks; /* #ifdef USE_ROC_SHMEM roc_shmem_t* handle; #endif */ std::unordered_map *observed_errors; int *errors_seen; public: SparseTriangularSolve() : nNZ(0), nRows(0), nCols(0), numBlocks(0) { x = NULL; y = NULL; y_zero = NULL, yref = NULL, observed_errors = NULL, errors_seen = NULL; xDev = yDev = completedRowsDev = remoteInProgressArrayDev = rowBlocksDev = doneArrayDev = shadowDoneArrayDev = numRowsAtLevelDev = maxDepthDev = rowMapDev = totalSpinDev = oneBufDev = 0; #ifdef USE_ROC_SHMEM int roc_shmem_queues = (2560 / WF_PER_WG); if (2560 % WF_PER_WG) roc_shmem_queues++; printf("roc_shmem_queues %d WF_PER_WG %d \n",roc_shmem_queues, WF_PER_WG); roc_shmem_init(roc_shmem_queues); this->Set_total_pes(roc_shmem_n_pes()); this->Set_this_pe(roc_shmem_my_pe()); #else this->Set_total_pes(1); this->Set_this_pe(0); #endif } void AddDerivedInputFlags(); void AllocateVectors(MatrixMarketReader &mm_reader); void CSRSpTSCPU(FloatType alpha); bool CSRCheckCPU(FloatType alpha); float CSRSpTSGPU(uint64_t &ns_per_iter, uint64_t &ns_per_analysis_iter, uint64_t &ns_per_syncfree_iter, uint64_t &ns_per_levelset_iter, uint64_t &ns_per_levelsync_iter, FloatType alpha); int VerifyResults(int); int NonDeterministicErrors(); int MaxErrors(); int ComputeRowBlocks(std::vector &, int *, int); ~SparseTriangularSolve() { if (x != NULL) delete[] x; if (y != NULL) delete[] y; if (y_zero != NULL) delete[] y_zero; if (yref != NULL) delete[] yref; if (errors_seen != NULL) delete[] errors_seen; if (xDev != 0) this->GPU->FreeMem(xDev); if (rowBlocksDev != 0) this->GPU->FreeMem(rowBlocksDev); if (completedRowsDev != 0) this->GPU->FreeMem(completedRowsDev); if (numRowsAtLevelDev != 0) this->GPU->FreeMem(numRowsAtLevelDev); if (maxDepthDev != 0) this->GPU->FreeMem(maxDepthDev); if (rowMapDev != 0) this->GPU->FreeMem(rowMapDev); if (totalSpinDev != 0) this->GPU->FreeMem(totalSpinDev); if (oneBufDev != 0) this->GPU->FreeMem(oneBufDev); if (remoteInProgressArrayDev != 0) this->GPU->FreeMem(remoteInProgressArrayDev); #ifndef USE_ROC_SHMEM if (yDev != 0) this->GPU->FreeMem(yDev); if (doneArrayDev != 0) this->GPU->FreeMem(doneArrayDev); if (reqUpdateArrayDev != 0) this->GPU->FreeMem(reqUpdateArrayDev); if (shadowDoneArrayDev != 0) this->GPU->FreeMem(shadowDoneArrayDev); #else if (yDev != 0) roc_shmem_free(yDev); if (doneArrayDev != 0) roc_shmem_free(doneArrayDev); if (reqUpdateArrayDev != 0) roc_shmem_free(reqUpdateArrayDev); if (shadowDoneArrayDev != 0) roc_shmem_free(shadowDoneArrayDev); roc_shmem_finalize(); #endif } }; template void SparseTriangularSolve::AddDerivedInputFlags() { AddInputFlag("filename", 'f', "", "Matrix-Market File", "string"); AddInputFlag("iterations", 'i', "10", "Number of SpTS Iterations (Default=10)", "int"); AddInputFlag("exp_zeroes", 'z', "false", "Include Explicit Zeroes in Matrix-Market File (Default=false)", "bool"); AddInputFlag("device", 'd', "0", "Choose the GPU to Execute SpTS (Default=0)", "int"); AddInputFlag("alpha", 'A', "1.0", "A*y=alpha*x. Known vector 'x' is multiplied by scalar alpha befoer solving for vector 'y'. (Default=1.0)", "float"); AddInputFlag("non_symmetric", 'n', "false", "Force the program to work on non-symmetric matrices. This will ignore the upper triangular entirely. (Default=false)", "bool"); AddInputFlag("levelsync_size", 'l', "0", "Number of rows to launch in a level-sync kernel invocation (Default = auto-tune)", "int"); AddInputFlag("verify", 'v', "false", "Verify results", "bool"); AddInputFlag("rocshmem_algorithm", 'a', "0", "ROC_SHMEM algorithm type", "int"); AddInputFlag("block_size", 'b', "32768", "Use get-based algorithm for ROC_SHMEM", "int"); AddInputFlag("put_block_size", 'p', "1024", "Block size for puts", "int"); AddInputFlag("get_backoff_factor", 'g', "128", "Backoff factor for gets", "int"); } template void SparseTriangularSolve::AllocateVectors( MatrixMarketReader &mm_reader) { nRows = mm_reader.GetNumRows(); nCols = mm_reader.GetNumCols(); nNZ = mm_reader.GetNumNonZeroes(); x = new FloatType[nCols]; y = new FloatType[nRows]; y_zero = new FloatType[nRows]; yref = new FloatType[nRows]; observed_errors = new std::unordered_map[InputFlags::GetValueInt("iterations")]; for(int i = 0; i < nRows; i++) { y[i] = (FloatType)0.0; y_zero[i] = (FloatType)0.0; yref[i] = (FloatType)0.0; } for(int i = 0; i < nCols; i++) { //x[i] = (FloatType)rand() / (FloatType)RAND_MAX; x[i] = 2.; } xDev = this->GPU->AllocateMem("xDev", nCols*sizeof(FloatType), GPU_MEM_READ_ONLY, NULL); #ifndef USE_ROC_SHMEM yDev = this->GPU->AllocateMem("yDev", nRows*sizeof(FloatType), GPU_MEM_READ_WRITE, NULL); #else yDev = (memPointer) roc_shmem_malloc(nRows*sizeof(FloatType)); #endif } template void SparseTriangularSolve::CSRSpTSCPU(FloatType alpha) { FloatType *NZvalues = SparseMatrix::GetVals(); int *Cols = SparseMatrix::GetCols(); int *rowptrs = SparseMatrix::GetRowPtrs(); double internal_alpha = alpha; uint64_t local_nnz = 0; uint64_t remote_nnz = 0; uint64_t rows_with_nonlocal = 0; for(int i = 0; i < nRows; i++) { bool row_has_nonlocal = false; double diagonal = 0.; double temp = 0.; int diag_j = -1; for(int j = rowptrs[i]; j < rowptrs[i+1]; j++) { int ci = Cols[j]; int row_pe = (i / SPTS_BLOCK_SIZE) % this->Get_total_pes(); int col_pe = (ci / SPTS_BLOCK_SIZE) % this->Get_total_pes(); int assigned_pe = (i / SPTS_BLOCK_SIZE) % this->Get_total_pes(); if (assigned_pe == this->Get_this_pe()) { if (row_pe == col_pe) { local_nnz++; } else { row_has_nonlocal = true; remote_nnz++; } } // Skip adding in the diagonal. We need to solve for that. if (ci != i) { if (i == TEST_NUM) fprintf(stderr, "NZvalues[%d](%lf) * yref[%d](%lf)\n", j, NZvalues[j], ci, yref[ci]); temp += NZvalues[j] * yref[ci]; } else { if (i==TEST_NUM) fprintf(stderr, "\t\tDIAG = %lf\n", NZvalues[j]); diagonal = NZvalues[j]; diag_j = j; } } if (row_has_nonlocal) rows_with_nonlocal++; if (diag_j == -1) { fflush(stdout); printf("\nERROR in SpTS CPU\n"); printf("No diagonal found in row %d\n", i); } // y = (x-sum_of_vals_from_A) / diag double alpha_x = internal_alpha * (double)x[i]; if (i == TEST_NUM) { char buf[128]; char buf2[128]; char buf3[128]; snprintf(buf, sizeof(buf), "%.20f", alpha_x); snprintf(buf2, sizeof(buf2), "%.20f", internal_alpha); fprintf(stderr, "alpha_x: %s (%s * %lf)\n", buf, buf2, x[i]); snprintf(buf3, sizeof(buf3), "%.20f", temp); fprintf(stderr, "temp: %s\n", buf3); } yref[i] = (FloatType)((alpha_x - temp)/diagonal); if (i == TEST_NUM) fprintf(stderr, "\tsupposed answer [%d]: %lf\n", i, yref[i]); } double ratio = ((double) local_nnz) / ((double) remote_nnz + local_nnz); double rows_remote_ratio = ((double) rows_with_nonlocal) / ((double) this->nRows_p); if (this->Get_this_pe() == 0) { printf("\nRANK 0: global NNZ = %lu\n", remote_nnz + local_nnz); printf("RANK 0: global Rows = %d\n", nRows); } printf("\nLOCALITY %d : Remote/Local cols %lu/%lu Fraction Columns Local %f Fraction Rows with Remote Columns %f\n", this->Get_this_pe(), remote_nnz, local_nnz, ratio, rows_remote_ratio); } template bool SparseTriangularSolve::CSRCheckCPU(FloatType alpha) { FloatType *NZvalues = SparseMatrix::GetVals(); int *Cols = SparseMatrix::GetCols(); int *rowptrs = SparseMatrix::GetRowPtrs(); double internal_alpha = alpha; bool all_worked = true; #pragma omp parallel for for(int i = 0; i < nRows; i++) { #pragma omp flush (all_worked) if (all_worked) { double temp = 0.; for(int j = rowptrs[i]; j < rowptrs[i+1]; j++) { int ci = Cols[j]; // Skip anything that lies on the diagonal. We need to solve for that. temp += NZvalues[j] * yref[ci]; } double compare_val = 0.; double alpha_x = internal_alpha * x[i]; if(typeid(FloatType) == typeid(float)) { compare_val = fabs(alpha_x*1e-3); if (compare_val < 10*FLT_EPSILON) compare_val = 10*FLT_EPSILON; if ((FloatType)(alpha_x - compare_val) > (FloatType)temp || (FloatType)(alpha_x + compare_val) < (FloatType)temp) { fflush(stdout); fprintf(stderr, " CPU CALCULATION ERROR on row %d\n", i); fprintf(stderr, "\tReal value for row %d: %.*e\n", i, OP_FLT_Digs-1, (float)alpha_x); fprintf(stderr, "\tCalculated value for row %d: %.*e\n", i, OP_FLT_Digs-1, (float)temp); all_worked = false; #pragma omp flush (all_worked) } } else if(typeid(FloatType) == typeid(double)) { compare_val = fabs(alpha_x*1e-4); if (compare_val < 10*DBL_EPSILON) compare_val = 10*DBL_EPSILON; if ((FloatType)(alpha_x - compare_val) > (FloatType)temp || (FloatType)(alpha_x + compare_val) < (FloatType)temp) { fflush(stdout); fprintf(stderr, " CPU CALCULATION ERROR on row %d\n", i); fprintf(stderr, "\tReal value for row %d: %.*le\n", i, OP_DBL_Digs-1, (double)alpha_x); fprintf(stderr, "\tCalculated value for row %d: %.*le\n", i, OP_DBL_Digs-1, (double)temp); all_worked = false; #pragma omp flush (all_worked) } } } } return all_worked; } template<> int SparseTriangularSolve::VerifyResults(int iteration) { int errors = 0; #pragma omp parallel for for (int i = 0; i < nRows; i++) { int assigned_pe = (i / SPTS_BLOCK_SIZE) % this->Get_total_pes(); if (this->Get_this_pe() == assigned_pe) { float compare_val = fabs(yref[i]*1e-3); if (compare_val < 10*FLT_EPSILON) compare_val = 10*FLT_EPSILON; if ((yref[i] - compare_val) > y[i] || (yref[i] + compare_val) < y[i]) { #pragma omp critical { if(errors == 0) { fflush(stdout); fprintf(stderr, "\nDetected some differences between CPU and GPU results on iteration %d...", iteration); } fprintf(stderr, "%d GPU CALCULATION ERROR on row %d\n", this->Get_this_pe(), i); fprintf(stderr, "\tCPU value for y[%d]: %.*e\n", i, OP_FLT_Digs-1, yref[i]); fprintf(stderr, "\tGPU value for y[%d]: %.*e\n", i, OP_FLT_Digs-1, y[i]); errors += 1; observed_errors[iteration].insert(std::pair (i, y[i])); } } } } return errors; } template<> int SparseTriangularSolve::VerifyResults(int iteration) { int errors = 0; #pragma omp parallel for for (int i = 0; i < nRows; i++) { double compare_val = fabs(yref[i]*1e-4); if (compare_val < 10*DBL_EPSILON) compare_val = 10*DBL_EPSILON; if ((yref[i] - compare_val) > y[i] || (yref[i] + compare_val) < y[i]) { #pragma omp critical { if(errors == 0) { fflush(stdout); fprintf(stderr, "\nDetected differences between CPU and GPU results on iteration %d...", iteration); } fprintf(stderr, "GPU CALCULATION ERROR on row %d\n", i); fprintf(stderr, "\tCPU value for y[%d]: %.*e\n", i, OP_DBL_Digs-1, yref[i]); fprintf(stderr, "\tGPU value for y[%d]: %.*e\n", i, OP_DBL_Digs-1, y[i]); errors += 1; observed_errors[iteration].insert(std::pair (i, y[i])); } } } return errors; } template int SparseTriangularSolve::NonDeterministicErrors() { int iter = InputFlags::GetValueInt("iterations"); int non_det_errors = 0; #ifdef ALL_SYNCFREE for (int i = 1; i < iter; i++) { if (errors_seen[i] != errors_seen[0]) { non_det_errors++; if (non_det_errors == 1) { fprintf(stderr, "Different SpTS iterations saw different error counts -- non-deterministic bug possible.\n"); fprintf(stderr, "\te.g. saw %d errors during iteration 0. Saw %d errors during iteration %i\n", errors_seen[0], errors_seen[i], i); } } else if (observed_errors[i] != observed_errors[0]) { non_det_errors++; if (non_det_errors == 1) { fprintf(stderr, "ERRORS were seen. Different iterations saw errors on different rows -- non-deterministic bug possible.\n"); fprintf(stderr, "\te.g. Iterations 0 and %d were different.\n", i); } } } #else if (iter >= 1) { if (errors_seen[0] != errors_seen[1]) { non_det_errors++; fprintf(stderr, "Different SpTS iterations saw different error counts -- non-deterministic bug possible.\n"); fprintf(stderr, "\te.g. saw %d errors during iteration 0. Saw %d errors during iteration %i\n", errors_seen[0], errors_seen[1], 1); } } for (int i = 2; i < iter; i++) { if (errors_seen[i] != errors_seen[1]) { non_det_errors++; if (non_det_errors == 1) { fprintf(stderr, "Different SpTS iterations saw different error counts -- non-deterministic bug possible.\n"); fprintf(stderr, "\te.g. saw %d errors during iteration 1. Saw %d errors during iteration %i\n", errors_seen[1], errors_seen[i], i); } } else if (observed_errors[i] != observed_errors[1]) { non_det_errors++; if (non_det_errors == 1) { fprintf(stderr, "ERRORS were seen. Different iterations saw errors on different rows -- non-deterministic bug possible.\n"); fprintf(stderr, "\te.g. Iterations 1 and %d were different.\n", i); } } } #endif return non_det_errors; } template int SparseTriangularSolve::MaxErrors() { int iter = InputFlags::GetValueInt("iterations"); int max_errors = 0; for (int i = 0; i < iter; i++) { if (errors_seen[i] > max_errors) max_errors = errors_seen[i]; } return max_errors; } #ifndef __has_builtin #define __has_builtin(x) 0 #endif static inline unsigned int flp2(unsigned int x) { x |= (x >> 1); x |= (x >> 2); x |= (x >> 4); x |= (x >> 8); x |= (x >> 16); return x - (x >> 1); } // Short rows in CSR-Adaptive are batched together into a single row block. // If there are a relatively small number of these, then we choose to do // a horizontal reduction (groups of threads all reduce the same row). // If there are many threads (e.g. more threads than the maximum size // of our workgroup) then we choose to have each thread serially reduce // the row. // This function calculates the number of threads that could team up // to reduce these groups of rows. For instance, if you have a // workgroup size of 256 and 4 rows, you could have 64 threads // working on each row. If you have 5 rows, only 32 threads could // reliably work on each row because our reduction assumes power-of-2. template< typename rowBlockType > static inline rowBlockType numThreadsForReduction(const rowBlockType num_rows) { #if defined(__INTEL_COMPILER) return 256 >> (_bit_scan_reverse(num_rows-1)+1); #elif (defined(__clang__) && __has_builtin(__builtin_clz)) || \ !defined(__clang) && \ defined(__GNUG__) && ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 30202) return (256 >> (8*sizeof(int)-__builtin_clz(num_rows-1))); #elif defined(_MSC_VER) && (_MSC_VER >= 1400) unsigned long bit_returned; _BitScanReverse(&bit_returned, (num_rows-1)); return 256 >> (bit_returned+1); #else return flp2(256/num_rows); #endif } template int SparseTriangularSolve::ComputeRowBlocks(std::vector &rowBlocks, int *rowDelimiters, int nRows) { rowBlocks.erase(rowBlocks.begin(), rowBlocks.end()); rowBlocks.push_back(0); uint64_t sum = 0; uint64_t i, last_i = 0; // Check to ensure nRows can fit in 32 bits if ((uint64_t) nRows > (uint64_t)pow(2, ROW_BITS)) { fflush(stdout); fprintf(stderr, "\nNumber of Rows in the Sparse Matrix is greater than what is supported at present (%d bits) !", ROW_BITS ); exit(0); } int consecutive_long_rows = 0; for(i = 1; i <= nRows; i++) { int row_length = ( rowDelimiters[ i ] - rowDelimiters[ i - 1 ] ); sum += row_length; // The following section of code calculates whether you're moving between // a series of "short" rows and a series of "long" rows. // This is because the reduction in CSR-Adaptive likes things to be // roughly the same length. Long rows can be reduced horizontally. // Short rows can be reduced one-thread-per-row. Try not to mix them. if ( row_length > 128 ) consecutive_long_rows++; else if ( consecutive_long_rows > 0 ) { // If it turns out we WERE in a long-row region, cut if off now. if (row_length < 32) // Now we're in a short-row region consecutive_long_rows = -1; else consecutive_long_rows++; } // If you just entered into a "long" row from a series of short rows, // then we need to make sure we cut off those short rows. Put them in // their own workgroup. if ( consecutive_long_rows == 1 ) { // Assuming there *was* a previous workgroup. If not, nothing to do here. if( i - last_i > 1 ) { rowBlocks.push_back( (i - 1) << (64 - ROW_BITS) ); // If this row fits into CSR-Stream, calculate how many rows // can be used to do a parallel reduction. // Fill in the low-order bits with the numThreadsForRed if (((i-1) - last_i) > 2) rowBlocks[rowBlocks.size() - 2] |= numThreadsForReduction((i - 1) - last_i); last_i = i-1; sum = row_length; } } else if (consecutive_long_rows == -1) { // We see the first short row after some long ones that // didn't previously fill up a row block. rowBlocks.push_back( (i - 1) << (64 - ROW_BITS) ); if (((i-1) - last_i) > 2) rowBlocks[rowBlocks.size() - 2] |= numThreadsForReduction((i - 1) - last_i); last_i = i-1; sum = row_length; consecutive_long_rows = 0; } // Now, what's up with this row? What did it do? // exactly one row results in non-zero elements to be greater than blockSize // This is csr-vector case; bottom WG_BITS == workgroup ID if( ( i - last_i == 1 ) && sum > 1024 ) { int numWGReq = ceil( (double)row_length / (1024) ); // Check to ensure #workgroups can fit in WG_BITS bits, if not // then the last workgroup will do all the remaining work numWGReq = ( numWGReq < (int)pow( 2, WG_BITS ) ) ? numWGReq : (int)pow( 2, WG_BITS ); for( int w = 1; w < numWGReq; w++ ) { rowBlocks.push_back((i-1) << ROW_BITS); rowBlocks[rowBlocks.size() - 1] |= (uint64_t)w; } rowBlocks.push_back(i << ROW_BITS); last_i = i; sum = 0; consecutive_long_rows = 0; } // more than one row results in non-zero elements to be greater than blockSize // This is csr-stream case; bottom WG_BITS = number of parallel reduction threads else if( ( i - last_i > 1 ) && sum > 1024 ) { i--; // This row won't fit, so back off one. rowBlocks.push_back( i << (64 - ROW_BITS) ); if ((i - last_i) > 2) rowBlocks[rowBlocks.size() - 2] |= numThreadsForReduction(i - last_i); last_i = i; sum = 0; consecutive_long_rows = 0; } // This is csr-stream case; bottom WG_BITS = number of parallel reduction threads else if( sum == 1024 ) { rowBlocks.push_back( i << (64 - ROW_BITS) ); if ((i - last_i) > 2) rowBlocks[rowBlocks.size() - 2] |= numThreadsForReduction(i - last_i); last_i = i; sum = 0; consecutive_long_rows = 0; } } // If we didn't fill a row block with the last row, make sure we don't lose it. if ( (rowBlocks[rowBlocks.size() - 2] >> (64 - ROW_BITS)) != (uint64_t)(nRows) ) { rowBlocks.push_back( (uint64_t)( nRows ) << (64 - ROW_BITS) ); if ((nRows - last_i) > 2) rowBlocks[rowBlocks.size() - 2] |= numThreadsForReduction(i - last_i); } return rowBlocks.size(); } template float SparseTriangularSolve::CSRSpTSGPU(uint64_t &ns_per_iter, uint64_t &ns_per_analysis_iter, uint64_t &ns_per_syncfree_iter, uint64_t &ns_per_levelset_iter, uint64_t &ns_per_levelsync_iter, FloatType alpha) { gpuInt status; gpuEvent* event_array; #ifdef USE_HIP hipSetDevice(this->Get_this_pe()); hipDeviceProp_t props; hipGetDeviceProperties(&props, this->Get_this_pe()); printf("\nPE %d: PCIe BUS ID %d DEV ID %d\n", this->Get_this_pe(), props.pciBusID, props.pciDeviceID); event_array = (gpuEvent*)malloc(sizeof(gpuEvent) * 2); hipEventCreate(&event_array[0]); hipEventCreate(&event_array[1]); #else event_array = (gpuEvent*)malloc(sizeof(gpuEvent)); #endif size_t global_work_size; size_t local_work_size = WF_PER_WG * WF_SIZE; /*************************** Setup and create buffers ********************/ /****** Matrix Setup Code ******/ /* Get the OpenCL buffers for the input matrix */ memPointer bufNonZeroes = SparseMatrix::GetDevVals(); memPointer bufColumnIndices = SparseMatrix::GetDevCols(); memPointer bufRowPtrs = SparseMatrix::GetDevRowPtrs(); /* Get the host buffers for the input matrix */ FloatType *Avalues = SparseMatrix::GetVals(); int *Acols = SparseMatrix::GetCols(); int *rowptrs = SparseMatrix::GetRowPtrs(); /****** Adaptive RowBlocks Setup Code ******/ numBlocks = ComputeRowBlocks(rowBlocks, rowptrs, nRows); rowBlocksDev = this->GPU->AllocateMem("rowBlocks", numBlocks*sizeof(int64_t), GPU_MEM_READ_WRITE, NULL); uint64_t completedRows = 0; completedRowsDev = this->GPU->AllocateMem("completedRows", sizeof(uint64_t), GPU_MEM_READ_WRITE|GPU_MEM_USE_HOST_PTR, &completedRows); /****** SpTS Meta-Data Setup Code ******/ /* Set up the OpenCL buffers for the SpTS meta-data */ // TODO -- is this +1 in doneArray nRows+1 required? Why? #ifdef USE_ROC_SHMEM doneArrayDev = roc_shmem_malloc((nRows+1)*sizeof(uint32_t)); reqUpdateArrayDev = roc_shmem_malloc((nRows+1)*sizeof(uint32_t)); shadowDoneArrayDev = roc_shmem_malloc((nRows+1)*sizeof(uint32_t)); #else doneArrayDev = this->GPU->AllocateMem("doneArray", (nRows+1)*sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); reqUpdateArrayDev = this->GPU->AllocateMem("reqUpdateArray", (nRows+1)*sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); shadowDoneArrayDev = this->GPU->AllocateMem("shadowDoneArray", (nRows+1)*sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); #endif remoteInProgressArrayDev = this->GPU->AllocateMem("remoteInProgressArray", (nRows+1)*sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); numRowsAtLevelDev = this->GPU->AllocateMem("numRowsAtLevel", (nRows)*sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); rowMapDev = this->GPU->AllocateMem("rowMap", (nRows+1)*sizeof(uint32_t), GPU_MEM_READ_ONLY, NULL); maxDepthDev = this->GPU->AllocateMem("maxDepth", sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); totalSpinDev = this->GPU->AllocateMem("totalSpin", sizeof(uint64_t), GPU_MEM_READ_WRITE, NULL); oneBufDev = this->GPU->AllocateMem("oneBuf", sizeof(uint32_t), GPU_MEM_READ_WRITE, NULL); /* Set up the host buffers for the SpTS meta-data */ uint32_t *doneArray = (uint32_t*)calloc((nRows+1), sizeof(uint32_t)); uint32_t *numRowsAtLevel = (uint32_t*)calloc(nRows, sizeof(uint32_t)); uint32_t *rowMap = (uint32_t*)calloc((nRows+1), sizeof(uint32_t)); uint32_t maxDepth = 0; uint64_t totalSpin = 0; uint32_t *nrows_plus1_zero = (uint32_t*)calloc((nRows+1), sizeof(uint32_t)); uint64_t u64_zero = 0; uint32_t u32_zero = 0; //uint32_t uns_int_one = 0x42280000; uint32_t u32_one = 1; // TODO: Gather and flatten out Avalues, Acols, and rowptrs based on // row cyclic decomposition. For now, we just copy the hole vals, cols, // and row_ptrs matrix, even though we really only access 1/num_pes of the // whole thing. We can do some more sophisticated stuff here if we run out // of space on the GPU or we don't like the copy overheads of the initial // buffers. /************************ Copy initial buffers to device *****************/ /****** Copy matrix ******/ this->GPU->CopyToDevice(bufNonZeroes, Avalues, this->nNZ*sizeof(FloatType), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(bufColumnIndices, Acols, this->nNZ*sizeof(int), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(bufRowPtrs, rowptrs, (this->nRows+1)*sizeof(int), 0, GPU_TRUE, NULL); /****** Copy vectors ******/ this->GPU->CopyToDevice(xDev, x, nCols*sizeof(FloatType), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(yDev, y_zero, nRows*sizeof(FloatType), 0, GPU_TRUE, NULL); /****** Copy adaptive rowBlock information ******/ this->GPU->CopyToDevice(rowBlocksDev, rowBlocks.data(), numBlocks*sizeof(int64_t), 0, GPU_TRUE, NULL); /****** Copy SpTS meta-data needed for analyze_and_solve run ******/ this->GPU->CopyToDevice(doneArrayDev, nrows_plus1_zero, (nRows+1)*sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(shadowDoneArrayDev, nrows_plus1_zero, (nRows+1)*sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(reqUpdateArrayDev, nrows_plus1_zero, (nRows+1)*sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(remoteInProgressArrayDev, nrows_plus1_zero, (nRows+1)*sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(numRowsAtLevelDev, nrows_plus1_zero, nRows*sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(maxDepthDev, &u32_zero, sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(totalSpinDev, &u64_zero, sizeof(uint64_t), 0, GPU_TRUE, NULL); this->GPU->CopyToDevice(oneBufDev, &u32_one, sizeof(uint32_t), 0, GPU_TRUE, NULL); /************************** Set up iteration printing ********************/ /* We want to print, ideally, every iteration that gets up 10% closer to * completion. This sets that up */ int iter = InputFlags::GetValueInt("iterations"); double print_iter = (float)iter / 10.; if (print_iter < 1.) print_iter = 1.; double next_to_print = 0.; /**************************** Set up perf analysis ************************/ // For performance analysis, keep track of how much time we've spent doing // kernel work. // TODO -- Take in from the command line whether to get kernel or total time. // If doing total time, try launching all of the kernels at once and waiting // outside. This will apparently reduce the overheads. uint64_t total_kern_time = 0; uint64_t analyze_kern_time = 0; double analyze_kern_flops = 0.; uint64_t syncfree_kern_time = 0; uint64_t levelset_kern_time = 0; uint64_t levelsync_kern_time = 0; errors_seen = new int[iter]; int analysis_iter = 0; int syncfree_iter = 0; int levelset_iter = 0; int levelsync_iter = 0; int level_sync_cutoff = InputFlags::GetValueInt("levelsync_size"); bool syncfree_better = false; int total_workitems_per_workgroup = WF_SIZE * WF_PER_WG; //bool roc_shmem_initialized = false; /*********************** Actual work of the benchmark *********************/ for(int i = 0; i < iter; i++) { if (i == (int)next_to_print || i == (iter - 1)) { printf("%d..", i+1);fflush(stdout); next_to_print += print_iter; } #ifndef ALL_SYNCFREE #ifdef ALL_ANALYZE // When we only want to run the analyze-and-solve mechanism, rather than // the more optimized syncfree algorithm, we always go into here. if (1) #else // In any version of the program that has the possibility of running the // level-set algorithm, we need to start with the syncfree-and-analyze // version of the program, so that we can set up the potential to run the // level-set algorithm. This will take place on the first iteration. if (i == 0) #endif { analysis_iter++; global_work_size = nRows * WF_SIZE; #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel_analyze, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, alpha, doneArrayDev, numRowsAtLevelDev, maxDepthDev, totalSpinDev); status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel_analyze, 1, NULL, &global_work_size, NULL, 0, NULL, &event_array[0]); CL->checkStatus(status,"clEnqueueNDRangeKernel failed"); this->GPU->Flush(); total_kern_time += CL->ComputeTime(event_array[0]); analyze_kern_time += CL->ComputeTime(event_array[0]); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; #ifdef USE_ROC_SHMEM global_work_size = this->nRows_p * WF_SIZE; num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; /* int roc_shmem_queues = (2560 / WF_PER_WG); if (2560 % WF_PER_WG) roc_shmem_queues++; if (!roc_shmem_initialized) { int num_threads = InputFlags::GetValueInt("num_roshmem_threads"); roc_shmem_init(&handle, roc_shmem_queues); roc_shmem_initialized = true; } */ int rocshmem_algorithm = InputFlags::GetValueInt("rocshmem_algorithm"); int rocshmem_put_block_size = InputFlags::GetValueInt("put_block_size"); int rocshmem_get_backoff_factor = InputFlags::GetValueInt("get_backoff_factor"); switch (rocshmem_algorithm) { case 0: printf("Using Put-based intra-kernel algorithm\n"); break; case 1: printf("Using Get-based intra-kernel algorithm (Backoff factor %d)\n", rocshmem_get_backoff_factor); break; case 2: printf("Using blocked Put-based intra-kernel algorithm\n"); printf("Using blocked Put-based intra-kernel algorithm (Block Size %d)\n", rocshmem_put_block_size); break; case 3: printf("Using put/get hybrid intra-kernel algorithm\n"); break; default: printf("Unknown ROC_SHMEM algoirthm\n"); exit(-1); } size_t LDS_size; roc_shmem_dynamic_shared(&LDS_size); printf("Work size %zu, wg size %d num workgroups %d LDS %zu thisPE %d Global %d \n", global_work_size, total_workitems_per_workgroup, num_of_workgroups, LDS_size, this->Get_this_pe(), this->Get_total_pes()); MPI_Barrier(MPI_COMM_WORLD); hipEventRecord(event_array[0], NULL); hipLaunchKernelGGL(amd_spts_analyze_and_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), LDS_size, 0, global_work_size, this->Get_this_pe(), this->Get_total_pes(), static_cast(shadowDoneArrayDev), static_cast(reqUpdateArrayDev), static_cast(remoteInProgressArrayDev), static_cast(oneBufDev), rocshmem_algorithm, rocshmem_put_block_size, rocshmem_get_backoff_factor, SPTS_BLOCK_SIZE, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(doneArrayDev), static_cast(numRowsAtLevelDev), static_cast(maxDepthDev), static_cast(totalSpinDev)); #else hipEventRecord(event_array[0], NULL); hipLaunchKernelGGL(amd_spts_analyze_and_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(doneArrayDev), static_cast(numRowsAtLevelDev), static_cast(maxDepthDev), static_cast(totalSpinDev)); #endif hipEventRecord(event_array[1], NULL); hipEventSynchronize(event_array[1]); #ifdef USE_ROC_SHMEM // Wait for any outstanding network messages to finish up. We // can have straggler updates to the doneArray that we don't // have any dependencies for but we still eed it to finish so // the below statistics can work correctly. //ro_shmem_dump_stats(handle); //ro_shmem_reset_stats(handle); //sleep(10); /* if( this->Get_this_pe() == 0 && (this->Get_total_pes() > 1)){ PRINT_SQ(get_rtn_handle(handle), 0, 1, 0); PRINT_CQ(get_rtn_handle(handle), 0, 1, 0); PRINT_SQ(get_rtn_handle(handle), 0, 1, 1); PRINT_CQ(get_rtn_handle(handle), 0, 1, 1); PRINT_SQ(get_rtn_handle(handle), 0, 1, 2); PRINT_CQ(get_rtn_handle(handle), 0, 1, 2); }*/ MPI_Barrier(MPI_COMM_WORLD); #endif float elapsed; hipEventElapsedTime(&elapsed, event_array[0], event_array[1]); total_kern_time += elapsed * 1000000; analyze_kern_time += elapsed * 1000000; #endif analyze_kern_flops = (2 * (double)nNZ * 1000000000.) / (double)analyze_kern_time; this->GPU->CopyToHost(yDev, y, nRows*sizeof(FloatType), 0, GPU_FALSE, NULL); this->GPU->CopyToHost(maxDepthDev, &maxDepth, sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->CopyToHost(doneArrayDev, doneArray, (nRows+1)*sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->CopyToHost(totalSpinDev, &totalSpin, sizeof(uint64_t), 0, GPU_FALSE, NULL); this->GPU->CopyToHost(numRowsAtLevelDev, numRowsAtLevel, nRows*sizeof(uint32_t), 0, GPU_TRUE, NULL); this->GPU->Flush(); #ifdef USE_ROC_SHMEM // Combine global statistics MPI_Allreduce(MPI_IN_PLACE, (void *) &maxDepth, 1, MPI_UNSIGNED, MPI_MAX, MPI_COMM_WORLD); MPI_Allreduce(MPI_IN_PLACE, (void *) &totalSpin, 1, MPI_UNSIGNED_LONG, MPI_SUM, MPI_COMM_WORLD); // TODO: Broadcast out the doneArray and yDev values to all nodes. This // is needed for the below calculations to work since in the 'pull' // distributed model we don't request data for rows that we don't // have a dependency on. #endif bool verify = InputFlags::GetValueBool("verify"); if (verify) { printf("Performing results verification\n"); errors_seen[i] = VerifyResults(i); } printf("\nTotalSpin: %lu\n", totalSpin); /* Prefix sum of the number of rows at each level, so that we can * calculate how much to offset each level into the rowMap */ // TODO -- Do this prefix sum on the GPU while copying maxDepth and // doneArray back into the host. Set non-blocking on the previous ones. this->GPU->CopyToHost(numRowsAtLevelDev, numRowsAtLevel, nRows*sizeof(uint32_t), 0, GPU_TRUE, NULL); for (unsigned int joe = 1; joe < maxDepth; joe++) numRowsAtLevel[joe] = numRowsAtLevel[joe] + numRowsAtLevel[joe-1]; /* Build up the rowMap so that each iteration of the no-wait solve * knows what it's global_id->row mapping is. * The general mechanism for this is as follows: * doneArray[row] holds the level that a particular row is in. * * We know the total number of levels needed (maxDepth), so rowMap * has maxDepth 'buckets'. * * The numRowsAtLevel array (after the above prefix-sum) tells us * how many values are in all of the previous buckets, so that * we can get an appropriate array offset for each bucket. * * The counters array keeps track of how many items are in each * bucket so far. Add this to the numRowsAtLevel[] offset. * * As we walk through all the rows, we check to see which level's * bucket we should put this row in. Add it at the end of the * current bucket, then increment the counter. */ uint32_t *counters = (uint32_t *)calloc(maxDepth, sizeof(uint32_t)); /* for (unsigned int this_row = 0; this_row < nRows; this_row++) { // We must subtract one here, because the first level is '1' // The GPU kernel does that because a value of '0' means // 'not done, keep waiting' in the analysis kernel. assert(doneArray[this_row] != 0); unsigned int this_rows_level = doneArray[this_row] - 1; unsigned int previous_level = this_rows_level - 1; unsigned int depth_offset; if (this_rows_level == 0) // can't check previous level depth_offset = 0; else depth_offset = numRowsAtLevel[previous_level]; rowMap[depth_offset + counters[this_rows_level]] = this_row; counters[this_rows_level] += 1; } */ free(counters); this->GPU->CopyToDevice(rowMapDev, rowMap, (nRows+1)*sizeof(uint32_t), 0, GPU_TRUE, NULL); free(event_array); #ifdef USE_HIP event_array = (gpuEvent*)malloc(maxDepth * sizeof(gpuEvent) * 2); for (int i = 0; i < maxDepth * 2; i++) hipEventCreate(&event_array[i]); #else event_array = (gpuEvent*)malloc(maxDepth * sizeof(gpuEvent)); #endif #ifdef ALL_ANALYZE // We will be coming back into this kernel. Time to reset its data. if (i != (iter - 1)) { this->GPU->CopyToDevice(maxDepthDev, &u32_zero, sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(totalSpinDev, &u64_zero, sizeof(uint64_t), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(numRowsAtLevelDev, nrows_plus1_zero, nRows*sizeof(uint32_t), 0, GPU_FALSE, NULL); } #endif this->GPU->CopyToDevice(yDev, y_zero, nRows*sizeof(FloatType), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(doneArrayDev, nrows_plus1_zero,(nRows+1)*sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(shadowDoneArrayDev, nrows_plus1_zero,(nRows+1)*sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(remoteInProgressArrayDev, nrows_plus1_zero,(nRows+1)*sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(reqUpdateArrayDev, nrows_plus1_zero, (nRows+1)*sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->Flush(); // Either we always want to run just this function block // (ALL_ANALYZE), or the first iteration is the analyze-and-solve // kernel. Either way, don't continue to the code below this time. continue; } #endif // If ALL_SYNCFREE is defined, we always run the amd_spts_syncfree_solve // kernel. We never try to speed it up by paying attention to the output // levels and running the levelset kernel. // If ALL_LEVELSET is set, we only run the analysis kernel up above to get the // level-set and do the first solve; after that we skip the // amd_spts_analyze_and_solve kernel and do the level-set based solve. // Otherwise, we dynamically choose between those kernels based on some // statistics that we gathered during the analyze-and-solve run. #ifdef ALL_SYNCFREE if (1) // always run syncfree algorithm #elif defined(ALL_LEVELSET) || defined(ALL_LEVELSYNC) if (0) // always *do not* run syncfree algorithm #else if (totalSpin == 0 || analyze_kern_flops/totalSpin > 25000 || syncfree_better) // Try to run syncfree #endif { syncfree_iter++; // TODO -- Eventually get this working with numer of RowBlocks - 1 global_work_size = nRows * WF_SIZE; uint32_t current_iteration = 0; #ifdef USE_ROC_SHMEM fprintf(stderr, "ROC_SHMEM not supported for selected algorithm\n"); exit(-1); #endif #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, alpha, doneArrayDev, numRowsAtLevelDev, maxDepthDev, totalSpinDev); status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel, 1, NULL, &global_work_size, NULL, 0, NULL, &event_array[0]); CL->checkStatus(status,"clEnqueueNDRangeKernel failed"); current_iteration++; this->GPU->Flush(); total_kern_time += CL->ComputeTime(event_array[0]); syncfree_kern_time += CL->ComputeTime(event_array[0]); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; hipEventRecord(event_array[0], NULL); hipLaunchKernelGGL(amd_spts_syncfree_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(doneArrayDev), static_cast(numRowsAtLevelDev), static_cast(maxDepthDev), static_cast(totalSpinDev)); hipEventRecord(event_array[1], NULL); hipEventSynchronize(event_array[1]); current_iteration++; float elapsed; hipEventElapsedTime(&elapsed, event_array[0], event_array[1]); total_kern_time += elapsed * 1000000; syncfree_kern_time += elapsed * 1000000; #endif this->GPU->CopyToHost(yDev, y, nRows*sizeof(FloatType), 0, GPU_TRUE, NULL); errors_seen[i] = VerifyResults(i); this->GPU->Flush(); current_iteration = 0; completedRows = 0; } #if defined(ALL_LEVELSYNC) else if (1) // always run levelset+syncfree combination #elif defined (ALL_LEVELSET) else if (0) // Fall through to level-set #else else if (1) // always run levelset+syncfree, never fall through to level-set only #endif { // This is the "level-sync" algorithm, where we take the level-set // information and launch kernels that combine multiple levels // together. This allows us to find parallelism to run on the GPU, // even if technically there are some data dependencies between the // levels. Within the kernel, we use the synchronization-free algorithm // to ensure that we get the right answer. // This algorithm reduces the spin-loop overhead of the sync-free // algorithm if there are many levels, but it finds more parallelism // than the pure level-set algorithms which can only run on one CU. levelsync_iter++; // Keep track of total kernels we launch so we can watch for events. int total_enqueues = 0; /* The rowMap tells each workgroup within the kernel what * rows it is working on. However, each each kernel invocation * is working on a different level. Each level is in a separate * 'bucket' in the rowMap. We must tell each invocation how far * into the rowMap it much index. That's the depth_offset. * numRowsAtLevel (after the above prefix-sum) tells us how * many rows were in all previous levels combined. */ unsigned int depth_offset = 0; unsigned int running_total = 0; // How many rows in this launch if (level_sync_cutoff == 0) { if (nRows/maxDepth < 32) level_sync_cutoff = 2560; else level_sync_cutoff = 81920; } #ifdef USE_ROC_SHMEM fprintf(stderr, "ROC_SHMEM not supported for selected algorithm\n"); exit(-1); #endif for (int this_level = 0; this_level < maxDepth; this_level++) { if (this_level != 0 && running_total == 0) depth_offset = numRowsAtLevel[this_level-1]; running_total = numRowsAtLevel[this_level] - depth_offset; if (running_total >= level_sync_cutoff) { global_work_size = (running_total + (running_total % WF_PER_WG)) * WF_SIZE; #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel_levelsync, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, alpha, doneArrayDev, rowMapDev, depth_offset); status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel_levelsync, 1, NULL, &global_work_size, &local_work_size, 0, NULL, &event_array[total_enqueues]); this->GPU->checkStatus(status,"clEnqueueNDRangeKernel failed"); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; hipEventRecord(event_array[total_enqueues * 2], NULL); hipLaunchKernelGGL(amd_spts_levelsync_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(doneArrayDev), static_cast(rowMapDev), depth_offset); hipEventRecord(event_array[total_enqueues * 2 + 1], NULL); #endif total_enqueues++; running_total = 0; } } if (running_total) { global_work_size = (running_total + (running_total % WF_PER_WG)) * WF_SIZE; #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel_levelsync, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, alpha, doneArrayDev, rowMapDev, depth_offset); status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel_levelsync, 1, NULL, &global_work_size, &local_work_size, 0, NULL, &event_array[total_enqueues]); this->GPU->checkStatus(status,"clEnqueueNDRangeKernel failed"); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; hipEventRecord(event_array[total_enqueues * 2], NULL); hipLaunchKernelGGL(amd_spts_levelsync_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(doneArrayDev), static_cast(rowMapDev), depth_offset); hipEventRecord(event_array[total_enqueues * 2 + 1], NULL); #endif total_enqueues++; } // After we cross this clFinish, all of the kernel invocations have // completed, and the final answer is in yDev. Now we should add up // all of the kernel runtimes from all levels to see how long this // levelset solve took. this->GPU->Flush(); for (int this_enqueue = 0; this_enqueue < total_enqueues; this_enqueue++) { #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); total_kern_time += CL->ComputeTime(event_array[this_enqueue]); levelsync_kern_time += CL->ComputeTime(event_array[this_enqueue]); #else float elapsed; hipEventElapsedTime(&elapsed, event_array[this_enqueue * 2], event_array[this_enqueue * 2 + 1]); total_kern_time += elapsed * 1000000; levelsync_kern_time += elapsed * 1000000; #endif } // The analyze kernel is about 15% slower than the syncfree kernel. // As such, if the level-sync verseion is < 15% faster, it's likely // that syncfree will win. Let's go back to doing that. if (i == 1 && (analyze_kern_time < (levelsync_kern_time * 1.15))) syncfree_better = true; this->GPU->CopyToHost(yDev, y, nRows*sizeof(FloatType), 0, GPU_TRUE, NULL); errors_seen[i] = VerifyResults(i); } else // Run level-set algorithm { // This is the level-set SpTS kernel, which can be done after the // first analyze-and-solve kernel. In this case, we know the levels // that each row is in, so we can launch one kernel per level with // exactly the right number of workgroups (one WG per row). // This means that we don't have any in-kernel atomics, spin-loops, // etc, so things run much faster. However, we much launch a // potentially large number of kernels. // Number of levels is maxDepth. */ levelset_iter++; #ifdef USE_ROC_SHMEM fprintf(stderr, "ROC_SHMEM not supported for selected algorithm\n"); exit(-1); #endif // Keep track of total kernels we launch so we can watch for events. int total_enqueues = 0; unsigned int start_level = 0; unsigned int end_level = 0; unsigned int in_a_run = 0; unsigned int running_total = 0; // How far into the rowMap that lists which rows are in each level unsigned int depth_offset = 0; unsigned int total_vector = 0; unsigned int total_levelset = 0; for (int this_level = 0; this_level < maxDepth; this_level++) { unsigned int inner_depth_offset; if (this_level == 0) inner_depth_offset = 0; else inner_depth_offset = numRowsAtLevel[this_level-1]; unsigned int total_in_this_depth = numRowsAtLevel[this_level] - inner_depth_offset; if (total_in_this_depth == 0) continue; end_level = this_level; // Comment out this if(){} section to force us to always // launch the levelset kernel. if (total_in_this_depth <= 2*WF_PER_WG) { running_total += total_in_this_depth; if (in_a_run == 0) { start_level = this_level; depth_offset = inner_depth_offset; in_a_run = 1; } } else { if (in_a_run) { global_work_size = WF_SIZE * WF_PER_WG; #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel_vector, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, alpha, rowMapDev, numRowsAtLevelDev, depth_offset, start_level, end_level); status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel_vector, 1, NULL, &global_work_size, &global_work_size, 0, NULL, &event_array[total_enqueues]); this->GPU->checkStatus(status,"clEnqueueNDRangeKernel failed"); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; hipEventRecord(event_array[total_enqueues * 2], NULL); hipLaunchKernelGGL(amd_spts_vector_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(rowMapDev), static_cast(numRowsAtLevelDev), depth_offset, start_level, end_level); hipEventRecord(event_array[total_enqueues * 2 + 1], NULL); #endif total_enqueues++; //printf("\n\tVector. offset %u Start %u End %u Rows in this enq %u\n", depth_offset, start_level, end_level, running_total); in_a_run = start_level = end_level = running_total = 0; depth_offset = numRowsAtLevel[this_level-1]; total_vector++; } global_work_size = WF_SIZE * total_in_this_depth; #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel_levelset, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, rowMapDev, depth_offset, alpha); status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel_levelset, 1, NULL, &global_work_size, NULL, 0, NULL, &event_array[total_enqueues]); this->GPU->checkStatus(status,"clEnqueueNDRangeKernel failed"); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; hipEventRecord(event_array[total_enqueues * 2], NULL); hipLaunchKernelGGL(amd_spts_levelset_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), static_cast(rowMapDev), depth_offset, alpha); hipEventRecord(event_array[total_enqueues * 2 + 1], NULL); #endif total_enqueues++; depth_offset = numRowsAtLevel[this_level]; total_levelset++; } } end_level++; if (in_a_run) { #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); CL->SetArgs(CLHelper::SpTSKernel_vector, 0, bufNonZeroes, bufColumnIndices, bufRowPtrs, xDev, yDev, alpha, rowMapDev, numRowsAtLevelDev, depth_offset, start_level, end_level); global_work_size = WF_SIZE * WF_PER_WG; status = clEnqueueNDRangeKernel(CLHelper::commandQueue, CLHelper::SpTSKernel_vector, 1, NULL, &global_work_size, &global_work_size, 0, NULL, &event_array[total_enqueues]); this->GPU->checkStatus(status,"clEnqueueNDRangeKernel failed"); #else int num_of_workgroups = (global_work_size + total_workitems_per_workgroup - 1) / total_workitems_per_workgroup; hipEventRecord(event_array[total_enqueues * 2], NULL); hipLaunchKernelGGL(amd_spts_vector_solve, dim3(num_of_workgroups), dim3(total_workitems_per_workgroup), 0, 0, global_work_size, static_cast(bufNonZeroes), static_cast(bufColumnIndices), static_cast(bufRowPtrs), static_cast(xDev), static_cast(yDev), alpha, static_cast(rowMapDev), static_cast(numRowsAtLevelDev), depth_offset, start_level, end_level); hipEventRecord(event_array[total_enqueues * 2 + 1], NULL); #endif total_enqueues++; //printf("\n\tVector. offset %u Start %u End %u Rows in this enq %u\n", depth_offset, start_level, end_level, running_total); in_a_run = start_level = end_level = running_total = 0; total_vector++; } if (i == 1) printf("\nTotal Vector: %u\nTotal levelset: %u\n", total_vector, total_levelset); // After we cross this clFinish, all of the kernel invocations have // completed, and the final answer is in yDev. Now we should add up // all of the kernel runtimes from all levels to see how long this // levelset solve took. this->GPU->Flush(); for (int this_enqueue = 0; this_enqueue < total_enqueues; this_enqueue++) { #ifndef USE_HIP CLHelper *CL = dynamic_cast(this->GPU); total_kern_time += CL->ComputeTime(event_array[this_enqueue]); levelset_kern_time += CL->ComputeTime(event_array[this_enqueue]); #else float elapsed; hipEventElapsedTime(&elapsed, event_array[this_enqueue * 2], event_array[this_enqueue * 2 + 1]); total_kern_time += elapsed * 1000000; levelset_kern_time += elapsed * 1000000; #endif } this->GPU->CopyToHost(yDev, y, nRows*sizeof(FloatType), 0, GPU_TRUE, NULL); errors_seen[i] = VerifyResults(i); } #ifndef ALL_SYNCFREE if (i == 1) printf("\nmaxDepth %d\n", maxDepth); #endif if (i != (iter - 1)) { this->GPU->CopyToDevice(yDev, y_zero, nRows*sizeof(FloatType), 0, GPU_FALSE, NULL); this->GPU->CopyToDevice(doneArrayDev, nrows_plus1_zero,(nRows+1)*sizeof(uint32_t), 0, GPU_FALSE, NULL); this->GPU->Flush(); } } float gflops = 0.f; printf("\n\nnnz: %d\n", nNZ); gflops = (float)(2 * nNZ) / (float)(total_kern_time/iter); ns_per_iter = total_kern_time/iter; if (analysis_iter > 0) ns_per_analysis_iter = analyze_kern_time / analysis_iter; else ns_per_analysis_iter = 0; if (syncfree_iter > 0) ns_per_syncfree_iter = syncfree_kern_time / syncfree_iter; else ns_per_syncfree_iter = 0; if (levelset_iter > 0) ns_per_levelset_iter = levelset_kern_time / levelset_iter; else ns_per_levelset_iter = 0; if (levelsync_iter > 0) ns_per_levelsync_iter = levelsync_kern_time / levelsync_iter; else ns_per_levelsync_iter = 0; this->GPU->CopyToHost(yDev, y, nRows*sizeof(FloatType), 0, GPU_TRUE, NULL); if (doneArray) free(doneArray); if (numRowsAtLevel) free(numRowsAtLevel); if (rowMap) free(rowMap); if (event_array) free(event_array); return gflops; } #endif //SpTS_H