#pragma once #include #include #include #include #include #include #define CEIL_DIV(M, N) (((M) + (N)-1) / (N)) const int K9_NUM_THREADS = 256; template __global__ void __launch_bounds__(K9_NUM_THREADS) sgemmAutotuned(int M, int N, int K, float alpha, float *A, float *B, float beta, float *C) { const uint cRow = blockIdx.y; const uint cCol = blockIdx.x; // size of warptile constexpr int WM = TM * 16; constexpr int WN = TN * 16; // iterations of warptile constexpr int WMITER = CEIL_DIV(BM, WM); constexpr int WNITER = CEIL_DIV(BN, WN); // Placement of the thread in the warptile const int threadCol = threadIdx.x % (WN / TN); const int threadRow = threadIdx.x / (WN / TN); // allocate space for the current blocktile in smem __shared__ float As[BM * BK]; __shared__ float Bs[BK * BN]; // Move blocktile to beginning of A's row and B's column A += cRow * BM * K; B += cCol * BN; C += cRow * BM * N + cCol * BN; // calculating the indices that this thread will load into SMEM // we'll load 128bit / 32bit = 4 elements per thread at each step const uint innerRowA = threadIdx.x / (BK / 4); const uint innerColA = threadIdx.x % (BK / 4); constexpr uint rowStrideA = (K9_NUM_THREADS * 4) / BK; const uint innerRowB = threadIdx.x / (BN / 4); const uint innerColB = threadIdx.x % (BN / 4); constexpr uint rowStrideB = K9_NUM_THREADS / (BN / 4); // allocate thread-local cache for results in registerfile float threadResults[WMITER * WNITER * TM * TN] = {0.0}; float regM[TM] = {0.0}; float regN[TN] = {0.0}; // outer-most loop over block tiles for (uint bkIdx = 0; bkIdx < K; bkIdx += BK) { // populate the SMEM caches for (uint offset = 0; offset + rowStrideA <= BM; offset += rowStrideA) { float4 tmp = reinterpret_cast( &A[(innerRowA + offset) * K + innerColA * 4])[0]; // transpose A while storing it As[(innerColA * 4 + 0) * BM + innerRowA + offset] = tmp.x; As[(innerColA * 4 + 1) * BM + innerRowA + offset] = tmp.y; As[(innerColA * 4 + 2) * BM + innerRowA + offset] = tmp.z; As[(innerColA * 4 + 3) * BM + innerRowA + offset] = tmp.w; } for (uint offset = 0; offset + rowStrideB <= BK; offset += rowStrideB) { reinterpret_cast( &Bs[(innerRowB + offset) * BN + innerColB * 4])[0] = reinterpret_cast( &B[(innerRowB + offset) * N + innerColB * 4])[0]; } __syncthreads(); for (uint wmIdx = 0; wmIdx < WMITER; ++wmIdx) { for (uint wnIdx = 0; wnIdx < WNITER; ++wnIdx) { // calculate per-thread results for (uint dotIdx = 0; dotIdx < BK; ++dotIdx) { // block into registers for (uint i = 0; i < TM; ++i) { regM[i] = As[dotIdx * BM + (wmIdx * WM) + threadRow * TM + i]; } for (uint i = 0; i < TN; ++i) { regN[i] = Bs[dotIdx * BN + (wnIdx * WN) + threadCol * TN + i]; } for (uint resIdxM = 0; resIdxM < TM; ++resIdxM) { for (uint resIdxN = 0; resIdxN < TN; ++resIdxN) { threadResults[(wmIdx * TM + resIdxM) * (WNITER * TN) + wnIdx * TN + resIdxN] += regM[resIdxM] * regN[resIdxN]; } } } } } __syncthreads(); // advance blocktile A += BK; // move BK columns to right B += BK * N; // move BK rows down } // write out the results for (uint wmIdx = 0; wmIdx < WMITER; ++wmIdx) { for (uint wnIdx = 0; wnIdx < WNITER; ++wnIdx) { float *C_interim = C + (wmIdx * WM * N) + (wnIdx * WN); for (uint resIdxM = 0; resIdxM < TM; resIdxM += 1) { for (uint resIdxN = 0; resIdxN < TN; resIdxN += 4) { // load C vector into registers float4 tmp = reinterpret_cast( &C_interim[(threadRow * TM + resIdxM) * N + threadCol * TN + resIdxN])[0]; // perform GEMM update in reg const int i = (wmIdx * TM + resIdxM) * (WNITER * TN) + wnIdx * TN + resIdxN; tmp.x = alpha * threadResults[i + 0] + beta * tmp.x; tmp.y = alpha * threadResults[i + 1] + beta * tmp.y; tmp.z = alpha * threadResults[i + 2] + beta * tmp.z; tmp.w = alpha * threadResults[i + 3] + beta * tmp.w; // write back reinterpret_cast(&C_interim[(threadRow * TM + resIdxM) * N + threadCol * TN + resIdxN])[0] = tmp; } } } } }