Files
rocm-systems/internal/clients/spts/SpTS.h
T
2024-07-01 09:57:08 -05:00

1600 строки
69 KiB
C++

/********************************************************************************
* 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 <vector>
#include <float.h>
#include <unordered_map>
#include <cmath>
#include <chrono>
#include <thread>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#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<typename FloatType>
class SparseTriangularSolve :
public InputFlags, public SparseMatrix<FloatType>
{
FloatType *x;
FloatType *y;
FloatType *y_zero;
FloatType *yref;
std::vector<uint64_t> 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<int, FloatType> *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<FloatType> &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<uint64_t> &, 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<typename FloatType>
void SparseTriangularSolve<FloatType>::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<typename FloatType>
void SparseTriangularSolve<FloatType>::AllocateVectors(
MatrixMarketReader<FloatType> &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<int, FloatType>[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<typename FloatType>
void SparseTriangularSolve<FloatType>::CSRSpTSCPU(FloatType alpha)
{
FloatType *NZvalues = SparseMatrix<FloatType>::GetVals();
int *Cols = SparseMatrix<FloatType>::GetCols();
int *rowptrs = SparseMatrix<FloatType>::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<typename FloatType>
bool SparseTriangularSolve<FloatType>::CSRCheckCPU(FloatType alpha)
{
FloatType *NZvalues = SparseMatrix<FloatType>::GetVals();
int *Cols = SparseMatrix<FloatType>::GetCols();
int *rowptrs = SparseMatrix<FloatType>::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<float>::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<int, float> (i, y[i]));
}
}
}
}
return errors;
}
template<>
int SparseTriangularSolve<double>::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<int, double> (i, y[i]));
}
}
}
return errors;
}
template<typename FloatType>
int SparseTriangularSolve<FloatType>::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<typename FloatType>
int SparseTriangularSolve<FloatType>::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<typename FloatType>
int SparseTriangularSolve<FloatType>::ComputeRowBlocks(std::vector<uint64_t> &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<typename FloatType>
float SparseTriangularSolve<FloatType>::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<FloatType>::GetDevVals();
memPointer bufColumnIndices = SparseMatrix<FloatType>::GetDevCols();
memPointer bufRowPtrs = SparseMatrix<FloatType>::GetDevRowPtrs();
/* Get the host buffers for the input matrix */
FloatType *Avalues = SparseMatrix<FloatType>::GetVals();
int *Acols = SparseMatrix<FloatType>::GetCols();
int *rowptrs = SparseMatrix<FloatType>::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<CLHelper*>(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<unsigned int *>(shadowDoneArrayDev),
static_cast<unsigned int *>(reqUpdateArrayDev),
static_cast<unsigned int *>(remoteInProgressArrayDev),
static_cast<unsigned int *>(oneBufDev),
rocshmem_algorithm,
rocshmem_put_block_size,
rocshmem_get_backoff_factor,
SPTS_BLOCK_SIZE,
static_cast<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(doneArrayDev),
static_cast<unsigned int *>(numRowsAtLevelDev),
static_cast<unsigned int *>(maxDepthDev),
static_cast<unsigned long long *>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(doneArrayDev),
static_cast<unsigned int *>(numRowsAtLevelDev),
static_cast<unsigned int *>(maxDepthDev),
static_cast<unsigned long long *>(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<CLHelper*>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(doneArrayDev),
static_cast<unsigned int *>(numRowsAtLevelDev),
static_cast<unsigned int *>(maxDepthDev),
static_cast<unsigned long long *>(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<CLHelper*>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(doneArrayDev),
static_cast<unsigned int*>(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<CLHelper*>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(doneArrayDev),
static_cast<unsigned int*>(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<CLHelper*>(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<CLHelper*>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(rowMapDev),
static_cast<unsigned int *>(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<CLHelper*>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
static_cast<unsigned int *>(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<CLHelper*>(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<FPTYPE *>(bufNonZeroes),
static_cast<int *>(bufColumnIndices),
static_cast<int *>(bufRowPtrs),
static_cast<FPTYPE *>(xDev),
static_cast<FPTYPE *>(yDev),
alpha,
static_cast<unsigned int *>(rowMapDev),
static_cast<unsigned int *>(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<CLHelper*>(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