mirror of
https://github.com/NVIDIA/cuda-samples.git
synced 2026-10-25 22:28:17 +08:00
Apply consistent code formatting across the repo. Add clang-format and pre-commit hooks.
This commit is contained in:
@@ -115,7 +115,7 @@
|
||||
|
||||
// Implementation constants.
|
||||
|
||||
#define WARPS_PER_BLOCK 8
|
||||
#define WARPS_PER_BLOCK 8
|
||||
#define THREADS_PER_BLOCK (WARP_SIZE * WARPS_PER_BLOCK)
|
||||
|
||||
#if SHARED_MEMORY_LIMIT_64K
|
||||
@@ -132,10 +132,10 @@
|
||||
#define CHUNK_K 16
|
||||
#endif
|
||||
|
||||
#define CHUNK_LINE_BYTES (CHUNK_K * K * sizeof(uint8_t))
|
||||
#define WARP_COPY_BYTES (WARP_SIZE * sizeof(int4))
|
||||
#define CHUNK_LINE_BYTES (CHUNK_K * K * sizeof(uint8_t))
|
||||
#define WARP_COPY_BYTES (WARP_SIZE * sizeof(int4))
|
||||
#define CHUNK_COPY_LINES_PER_WARP (WARP_COPY_BYTES / CHUNK_LINE_BYTES)
|
||||
#define CHUNK_COPY_LINE_LANES (WARP_SIZE / CHUNK_COPY_LINES_PER_WARP)
|
||||
#define CHUNK_COPY_LINE_LANES (WARP_SIZE / CHUNK_COPY_LINES_PER_WARP)
|
||||
|
||||
#define BLOCK_ROW_WARPS 2
|
||||
#define BLOCK_COL_WARPS 4
|
||||
@@ -166,236 +166,219 @@
|
||||
// nvcuda::wmma::load_matrix_sync.
|
||||
#define SKEW_UINT8 32
|
||||
|
||||
#define checkKernelErrors(expr) \
|
||||
do { \
|
||||
expr; \
|
||||
\
|
||||
cudaError_t __err = cudaGetLastError(); \
|
||||
if (__err != cudaSuccess) { \
|
||||
printf("Line %d: '%s' failed: %s\n", __LINE__, #expr, \
|
||||
cudaGetErrorString(__err)); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define checkKernelErrors(expr) \
|
||||
do { \
|
||||
expr; \
|
||||
\
|
||||
cudaError_t __err = cudaGetLastError(); \
|
||||
if (__err != cudaSuccess) { \
|
||||
printf("Line %d: '%s' failed: %s\n", __LINE__, #expr, cudaGetErrorString(__err)); \
|
||||
abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
using namespace nvcuda;
|
||||
|
||||
__host__ void init_host_matrices(uint8_t *a, uint8_t *b, int *c) {
|
||||
for (int i = 0; i < M_GLOBAL; i++) {
|
||||
for (int j = 0; j < K_GLOBAL; j++) {
|
||||
a[i * K_GLOBAL + j] = (uint8_t)(rand() % 3);
|
||||
__host__ void init_host_matrices(uint8_t *a, uint8_t *b, int *c)
|
||||
{
|
||||
for (int i = 0; i < M_GLOBAL; i++) {
|
||||
for (int j = 0; j < K_GLOBAL; j++) {
|
||||
a[i * K_GLOBAL + j] = (uint8_t)(rand() % 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < N_GLOBAL; i++) {
|
||||
for (int j = 0; j < K_GLOBAL; j++) {
|
||||
b[i * K_GLOBAL + j] = (uint8_t)(rand() % 3);
|
||||
for (int i = 0; i < N_GLOBAL; i++) {
|
||||
for (int j = 0; j < K_GLOBAL; j++) {
|
||||
b[i * K_GLOBAL + j] = (uint8_t)(rand() % 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int t = 0; t < M_GLOBAL * N_GLOBAL; t++) {
|
||||
c[t] = (rand() % 3);
|
||||
}
|
||||
for (int t = 0; t < M_GLOBAL * N_GLOBAL; t++) {
|
||||
c[t] = (rand() % 3);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void compute_gemm_imma(const uint8_t *A, const uint8_t *B,
|
||||
const int *C, int *D, int alpha, int beta) {
|
||||
extern __shared__ uint8_t shmem[][CHUNK_K * K + SKEW_UINT8];
|
||||
__global__ void compute_gemm_imma(const uint8_t *A, const uint8_t *B, const int *C, int *D, int alpha, int beta)
|
||||
{
|
||||
extern __shared__ uint8_t shmem[][CHUNK_K * K + SKEW_UINT8];
|
||||
|
||||
// Warp and lane identification.
|
||||
const unsigned int warpId = threadIdx.x / WARP_SIZE;
|
||||
const unsigned int laneId = threadIdx.x % WARP_SIZE;
|
||||
// Warp and lane identification.
|
||||
const unsigned int warpId = threadIdx.x / WARP_SIZE;
|
||||
const unsigned int laneId = threadIdx.x % WARP_SIZE;
|
||||
|
||||
// Offset in shared memory from which the B matrix is stored.
|
||||
const size_t shmem_idx_b_off = BLOCK_COL_TILES * M;
|
||||
// Offset in shared memory from which the B matrix is stored.
|
||||
const size_t shmem_idx_b_off = BLOCK_COL_TILES * M;
|
||||
|
||||
// This pointer is used to access the C and D matrix tiles this warp computes.
|
||||
int *shmem_warp_tile_ptr = (int *)&shmem[0][0] +
|
||||
(warpId / 2) * SHMEM_STRIDE * K * 2 +
|
||||
(warpId % 2) * SHMEM_OFFSET;
|
||||
// This pointer is used to access the C and D matrix tiles this warp computes.
|
||||
int *shmem_warp_tile_ptr = (int *)&shmem[0][0] + (warpId / 2) * SHMEM_STRIDE * K * 2 + (warpId % 2) * SHMEM_OFFSET;
|
||||
|
||||
// This pointer is used to stream the C and D matrices block-wide tile to and
|
||||
// from shared memory.
|
||||
int *shmem_warp_stream_ptr = (int *)&shmem[0][0] + warpId * SHMEM_STRIDE * K;
|
||||
// This pointer is used to stream the C and D matrices block-wide tile to and
|
||||
// from shared memory.
|
||||
int *shmem_warp_stream_ptr = (int *)&shmem[0][0] + warpId * SHMEM_STRIDE * K;
|
||||
|
||||
// Adjust the beta scaler, as it'll be multiplied by alpha at the end of
|
||||
// each tile computation. Technically this is not generally correct (may
|
||||
// result in a loss of precision). Zero still needs to be specially handled
|
||||
// though.
|
||||
beta /= alpha;
|
||||
// Adjust the beta scaler, as it'll be multiplied by alpha at the end of
|
||||
// each tile computation. Technically this is not generally correct (may
|
||||
// result in a loss of precision). Zero still needs to be specially handled
|
||||
// though.
|
||||
beta /= alpha;
|
||||
|
||||
// Each CTA slides along the 128 x 128 tiles from the top left corner of the
|
||||
// matrix to the right and down, and selects the next tile to compute. Once
|
||||
// there's no such tile, all warps in this CTA exit.
|
||||
for (unsigned int block_pos = blockIdx.x;; block_pos += gridDim.x) {
|
||||
const unsigned int block_tile_i =
|
||||
((block_pos * BLOCK_ROW_TILES) / N_TILES) * (BLOCK_COL_TILES);
|
||||
const unsigned int block_tile_j = (block_pos * BLOCK_COL_TILES) % N_TILES;
|
||||
// Each CTA slides along the 128 x 128 tiles from the top left corner of the
|
||||
// matrix to the right and down, and selects the next tile to compute. Once
|
||||
// there's no such tile, all warps in this CTA exit.
|
||||
for (unsigned int block_pos = blockIdx.x;; block_pos += gridDim.x) {
|
||||
const unsigned int block_tile_i = ((block_pos * BLOCK_ROW_TILES) / N_TILES) * (BLOCK_COL_TILES);
|
||||
const unsigned int block_tile_j = (block_pos * BLOCK_COL_TILES) % N_TILES;
|
||||
|
||||
// Stop when there are no more D matrix tiles to compute in this CTA.
|
||||
if (block_tile_i >= M_TILES) {
|
||||
break;
|
||||
}
|
||||
|
||||
// This warp's pointer to the C matrix data to copy memory from to shared
|
||||
// memory.
|
||||
const size_t gmem_idx =
|
||||
(block_tile_i + warpId) * M * GLOBAL_MEM_STRIDE + block_tile_j * N;
|
||||
const int *src_gmem_warp_stream_ptr = &C[gmem_idx];
|
||||
|
||||
// Stream multiple C tiles to shared memory.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < K; i++) {
|
||||
typedef int4 copy_t;
|
||||
|
||||
*((copy_t *)(shmem_warp_stream_ptr + SHMEM_STRIDE * i) + laneId) =
|
||||
*((copy_t *)(src_gmem_warp_stream_ptr + GLOBAL_MEM_STRIDE * i) +
|
||||
laneId);
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// These fragments will accumulate the result of A and B matrix fragment
|
||||
// multiplications along the K_GLOBAL dimension.
|
||||
wmma::fragment<wmma::accumulator, M, N, K, int> c[WARP_COL_TILES]
|
||||
[WARP_ROW_TILES];
|
||||
|
||||
// Load the C matrix tiles into fragments from shared memory.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
const int *tile_ptr =
|
||||
shmem_warp_tile_ptr + i * SHMEM_STRIDE * K + j * N;
|
||||
|
||||
wmma::load_matrix_sync(c[i][j], tile_ptr, SHMEM_STRIDE, C_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Scale the C matrix.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
#pragma unroll
|
||||
for (int t = 0; t < c[i][j].num_elements; t++) {
|
||||
c[i][j].x[t] *= beta;
|
||||
// Stop when there are no more D matrix tiles to compute in this CTA.
|
||||
if (block_tile_i >= M_TILES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select what warp copies what matrix to shared memory.
|
||||
// Warps 0-3 copy the A matrix, warps 4-7 copy the B matrix.
|
||||
const uint8_t *warp_ptr = (warpId < 4) ? (&A[block_tile_i * M * K_GLOBAL] +
|
||||
M * K_GLOBAL * (warpId % 4) * 2)
|
||||
: (&B[block_tile_j * N * K_GLOBAL] +
|
||||
N * K_GLOBAL * (warpId % 4) * 2);
|
||||
// This warp's pointer to the C matrix data to copy memory from to shared
|
||||
// memory.
|
||||
const size_t gmem_idx = (block_tile_i + warpId) * M * GLOBAL_MEM_STRIDE + block_tile_j * N;
|
||||
const int *src_gmem_warp_stream_ptr = &C[gmem_idx];
|
||||
|
||||
// Go through the global K dimension by a fixed step at a time.
|
||||
// Stream multiple C tiles to shared memory.
|
||||
#pragma unroll
|
||||
for (int tile_k = 0; tile_k < K_TILES; tile_k += CHUNK_K) {
|
||||
// Copy slices of the A and B matrices to shared memory.
|
||||
// The first half of the warps in the CTA copy the A matrix, the rest copy
|
||||
// the B matrix.
|
||||
size_t shmem_idx =
|
||||
warpId < (WARPS_PER_BLOCK / 2)
|
||||
? (M * (warpId % (WARPS_PER_BLOCK / 2)) * 2)
|
||||
: (N * (warpId % (WARPS_PER_BLOCK / 2)) * 2 + shmem_idx_b_off);
|
||||
for (int i = 0; i < K; i++) {
|
||||
typedef int4 copy_t;
|
||||
|
||||
// First half of the warp copies the first row / column of the matrix,
|
||||
// the second half of the warp copies the next.
|
||||
int4 *lane_ptr = (int4 *)(warp_ptr + tile_k * K +
|
||||
(laneId / CHUNK_COPY_LINE_LANES) * K_GLOBAL) +
|
||||
(laneId % CHUNK_COPY_LINE_LANES);
|
||||
*((copy_t *)(shmem_warp_stream_ptr + SHMEM_STRIDE * i) + laneId) =
|
||||
*((copy_t *)(src_gmem_warp_stream_ptr + GLOBAL_MEM_STRIDE * i) + laneId);
|
||||
}
|
||||
|
||||
// Shift the second half of the warp to the next row / column in the
|
||||
// shared memory.
|
||||
shmem_idx += laneId / CHUNK_COPY_LINE_LANES;
|
||||
__syncthreads();
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < ((WARP_SIZE / 2) / CHUNK_COPY_LINES_PER_WARP) * 2;
|
||||
i++) {
|
||||
// Copy 16 bytes at once in each lane.
|
||||
*((int4 *)&shmem[shmem_idx][0] + (laneId % CHUNK_COPY_LINE_LANES)) =
|
||||
*lane_ptr;
|
||||
|
||||
// Advance the global memory pointer and the shared memory index.
|
||||
lane_ptr = (int4 *)((uint8_t *)lane_ptr +
|
||||
K_GLOBAL * CHUNK_COPY_LINES_PER_WARP);
|
||||
shmem_idx += CHUNK_COPY_LINES_PER_WARP;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Compute a grid of C matrix tiles in each warp.
|
||||
#pragma unroll
|
||||
for (int k_step = 0; k_step < CHUNK_K; k_step++) {
|
||||
wmma::fragment<wmma::matrix_a, M, N, K, uint8_t, wmma::row_major>
|
||||
a[WARP_COL_TILES];
|
||||
wmma::fragment<wmma::matrix_b, M, N, K, uint8_t, wmma::col_major>
|
||||
b[WARP_ROW_TILES];
|
||||
// These fragments will accumulate the result of A and B matrix fragment
|
||||
// multiplications along the K_GLOBAL dimension.
|
||||
wmma::fragment<wmma::accumulator, M, N, K, int> c[WARP_COL_TILES][WARP_ROW_TILES];
|
||||
|
||||
// Load the C matrix tiles into fragments from shared memory.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
size_t shmem_idx_a = (warpId / 2) * M * 2 + (i * M);
|
||||
const uint8_t *tile_ptr = &shmem[shmem_idx_a][k_step * K];
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
const int *tile_ptr = shmem_warp_tile_ptr + i * SHMEM_STRIDE * K + j * N;
|
||||
|
||||
wmma::load_matrix_sync(a[i], tile_ptr, K * CHUNK_K + SKEW_UINT8);
|
||||
wmma::load_matrix_sync(c[i][j], tile_ptr, SHMEM_STRIDE, C_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Scale the C matrix.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
#pragma unroll
|
||||
for (int t = 0; t < c[i][j].num_elements; t++) {
|
||||
c[i][j].x[t] *= beta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Select what warp copies what matrix to shared memory.
|
||||
// Warps 0-3 copy the A matrix, warps 4-7 copy the B matrix.
|
||||
const uint8_t *warp_ptr = (warpId < 4) ? (&A[block_tile_i * M * K_GLOBAL] + M * K_GLOBAL * (warpId % 4) * 2)
|
||||
: (&B[block_tile_j * N * K_GLOBAL] + N * K_GLOBAL * (warpId % 4) * 2);
|
||||
|
||||
// Go through the global K dimension by a fixed step at a time.
|
||||
#pragma unroll
|
||||
for (int tile_k = 0; tile_k < K_TILES; tile_k += CHUNK_K) {
|
||||
// Copy slices of the A and B matrices to shared memory.
|
||||
// The first half of the warps in the CTA copy the A matrix, the rest copy
|
||||
// the B matrix.
|
||||
size_t shmem_idx = warpId < (WARPS_PER_BLOCK / 2)
|
||||
? (M * (warpId % (WARPS_PER_BLOCK / 2)) * 2)
|
||||
: (N * (warpId % (WARPS_PER_BLOCK / 2)) * 2 + shmem_idx_b_off);
|
||||
|
||||
// First half of the warp copies the first row / column of the matrix,
|
||||
// the second half of the warp copies the next.
|
||||
int4 *lane_ptr = (int4 *)(warp_ptr + tile_k * K + (laneId / CHUNK_COPY_LINE_LANES) * K_GLOBAL)
|
||||
+ (laneId % CHUNK_COPY_LINE_LANES);
|
||||
|
||||
// Shift the second half of the warp to the next row / column in the
|
||||
// shared memory.
|
||||
shmem_idx += laneId / CHUNK_COPY_LINE_LANES;
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
if (i == 0) {
|
||||
// Load the B matrix fragment once, because it is going to be
|
||||
// reused against the other A matrix fragments.
|
||||
size_t shmem_idx_b = shmem_idx_b_off +
|
||||
(WARP_ROW_TILES * N) * (warpId % 2) +
|
||||
(j * N);
|
||||
const uint8_t *tile_ptr = &shmem[shmem_idx_b][k_step * K];
|
||||
for (int i = 0; i < ((WARP_SIZE / 2) / CHUNK_COPY_LINES_PER_WARP) * 2; i++) {
|
||||
// Copy 16 bytes at once in each lane.
|
||||
*((int4 *)&shmem[shmem_idx][0] + (laneId % CHUNK_COPY_LINE_LANES)) = *lane_ptr;
|
||||
|
||||
wmma::load_matrix_sync(b[j], tile_ptr, K * CHUNK_K + SKEW_UINT8);
|
||||
// Advance the global memory pointer and the shared memory index.
|
||||
lane_ptr = (int4 *)((uint8_t *)lane_ptr + K_GLOBAL * CHUNK_COPY_LINES_PER_WARP);
|
||||
shmem_idx += CHUNK_COPY_LINES_PER_WARP;
|
||||
}
|
||||
|
||||
wmma::mma_sync(c[i][j], a[i], b[j], c[i][j]);
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
// Compute a grid of C matrix tiles in each warp.
|
||||
#pragma unroll
|
||||
for (int k_step = 0; k_step < CHUNK_K; k_step++) {
|
||||
wmma::fragment<wmma::matrix_a, M, N, K, uint8_t, wmma::row_major> a[WARP_COL_TILES];
|
||||
wmma::fragment<wmma::matrix_b, M, N, K, uint8_t, wmma::col_major> b[WARP_ROW_TILES];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
size_t shmem_idx_a = (warpId / 2) * M * 2 + (i * M);
|
||||
const uint8_t *tile_ptr = &shmem[shmem_idx_a][k_step * K];
|
||||
|
||||
wmma::load_matrix_sync(a[i], tile_ptr, K * CHUNK_K + SKEW_UINT8);
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
if (i == 0) {
|
||||
// Load the B matrix fragment once, because it is going to be
|
||||
// reused against the other A matrix fragments.
|
||||
size_t shmem_idx_b = shmem_idx_b_off + (WARP_ROW_TILES * N) * (warpId % 2) + (j * N);
|
||||
const uint8_t *tile_ptr = &shmem[shmem_idx_b][k_step * K];
|
||||
|
||||
wmma::load_matrix_sync(b[j], tile_ptr, K * CHUNK_K + SKEW_UINT8);
|
||||
}
|
||||
|
||||
wmma::mma_sync(c[i][j], a[i], b[j], c[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
// Store the D fragments to shared memory.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
#pragma unroll
|
||||
// Uniform, point-wise transformations of ALL fragment elements by ALL
|
||||
// threads in the warp are well-defined even though element indices
|
||||
// within fragment storage are not defined.
|
||||
for (int t = 0; t < c[i][j].num_elements; t++)
|
||||
c[i][j].x[t] *= alpha;
|
||||
|
||||
int *tile_ptr = shmem_warp_tile_ptr + i * SHMEM_STRIDE * K + j * N;
|
||||
|
||||
wmma::store_matrix_sync(tile_ptr, c[i][j], SHMEM_STRIDE, C_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Now that shared memory contains all the D tiles, stream them to global
|
||||
// memory.
|
||||
int *dst_gmem_warp_stream_ptr = &D[gmem_idx];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < K; i++) {
|
||||
*((int4 *)(dst_gmem_warp_stream_ptr + GLOBAL_MEM_STRIDE * i) + laneId) =
|
||||
*((int4 *)(shmem_warp_stream_ptr + SHMEM_STRIDE * i) + laneId);
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
// Store the D fragments to shared memory.
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_COL_TILES; i++) {
|
||||
#pragma unroll
|
||||
for (int j = 0; j < WARP_ROW_TILES; j++) {
|
||||
#pragma unroll
|
||||
// Uniform, point-wise transformations of ALL fragment elements by ALL
|
||||
// threads in the warp are well-defined even though element indices
|
||||
// within fragment storage are not defined.
|
||||
for (int t = 0; t < c[i][j].num_elements; t++) c[i][j].x[t] *= alpha;
|
||||
|
||||
int *tile_ptr = shmem_warp_tile_ptr + i * SHMEM_STRIDE * K + j * N;
|
||||
|
||||
wmma::store_matrix_sync(tile_ptr, c[i][j], SHMEM_STRIDE, C_LAYOUT);
|
||||
}
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// Now that shared memory contains all the D tiles, stream them to global
|
||||
// memory.
|
||||
int *dst_gmem_warp_stream_ptr = &D[gmem_idx];
|
||||
|
||||
#pragma unroll
|
||||
for (int i = 0; i < K; i++) {
|
||||
*((int4 *)(dst_gmem_warp_stream_ptr + GLOBAL_MEM_STRIDE * i) + laneId) =
|
||||
*((int4 *)(shmem_warp_stream_ptr + SHMEM_STRIDE * i) + laneId);
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
// Performs an MxNxK GEMM (C=alpha*A*B + beta*C) assuming:
|
||||
@@ -406,250 +389,244 @@ __global__ void compute_gemm_imma(const uint8_t *A, const uint8_t *B,
|
||||
// is designed for
|
||||
// demonstration purposes only to show the CUDA WMMA API use without
|
||||
// relying on availability of the shared memory.
|
||||
__global__ void simple_wmma_gemm_imma(const uint8_t *a, const uint8_t *b,
|
||||
const int *c, int *d, int m_ld, int n_ld,
|
||||
int k_ld, int alpha, int beta) {
|
||||
// Leading dimensions. Packed with no transpositions.
|
||||
int lda = m_ld;
|
||||
int ldb = k_ld;
|
||||
int ldc = n_ld;
|
||||
__global__ void simple_wmma_gemm_imma(const uint8_t *a,
|
||||
const uint8_t *b,
|
||||
const int *c,
|
||||
int *d,
|
||||
int m_ld,
|
||||
int n_ld,
|
||||
int k_ld,
|
||||
int alpha,
|
||||
int beta)
|
||||
{
|
||||
// Leading dimensions. Packed with no transpositions.
|
||||
int lda = m_ld;
|
||||
int ldb = k_ld;
|
||||
int ldc = n_ld;
|
||||
|
||||
// Tile using a 2D grid
|
||||
int warpM = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize;
|
||||
int warpN = (blockIdx.y * blockDim.y + threadIdx.y);
|
||||
// Tile using a 2D grid
|
||||
int warpM = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize;
|
||||
int warpN = (blockIdx.y * blockDim.y + threadIdx.y);
|
||||
|
||||
// Declare the fragments
|
||||
wmma::fragment<wmma::matrix_a, WMMA_M, WMMA_N, WMMA_K, uint8_t,
|
||||
wmma::row_major>
|
||||
a_frag;
|
||||
wmma::fragment<wmma::matrix_b, WMMA_M, WMMA_N, WMMA_K, uint8_t,
|
||||
wmma::col_major>
|
||||
b_frag;
|
||||
wmma::fragment<wmma::accumulator, WMMA_M, WMMA_N, WMMA_K, int> acc_frag;
|
||||
wmma::fragment<wmma::accumulator, WMMA_M, WMMA_N, WMMA_K, int> c_frag;
|
||||
// Declare the fragments
|
||||
wmma::fragment<wmma::matrix_a, WMMA_M, WMMA_N, WMMA_K, uint8_t, wmma::row_major> a_frag;
|
||||
wmma::fragment<wmma::matrix_b, WMMA_M, WMMA_N, WMMA_K, uint8_t, wmma::col_major> b_frag;
|
||||
wmma::fragment<wmma::accumulator, WMMA_M, WMMA_N, WMMA_K, int> acc_frag;
|
||||
wmma::fragment<wmma::accumulator, WMMA_M, WMMA_N, WMMA_K, int> c_frag;
|
||||
|
||||
wmma::fill_fragment(acc_frag, 0.0f);
|
||||
wmma::fill_fragment(acc_frag, 0.0f);
|
||||
|
||||
// Loop over k
|
||||
for (int i = 0; i < k_ld; i += WMMA_K) {
|
||||
int aCol = i;
|
||||
int aRow = warpM * WMMA_M;
|
||||
// Loop over k
|
||||
for (int i = 0; i < k_ld; i += WMMA_K) {
|
||||
int aCol = i;
|
||||
int aRow = warpM * WMMA_M;
|
||||
|
||||
int bCol = i;
|
||||
int bRow = warpN * WMMA_N;
|
||||
int bCol = i;
|
||||
int bRow = warpN * WMMA_N;
|
||||
|
||||
// Bounds checking
|
||||
if (aRow < m_ld && aCol < k_ld && bRow < k_ld && bCol < n_ld) {
|
||||
// Load the inputs
|
||||
wmma::load_matrix_sync(a_frag, a + aCol + aRow * lda, lda);
|
||||
wmma::load_matrix_sync(b_frag, b + bCol + bRow * ldb, ldb);
|
||||
// Bounds checking
|
||||
if (aRow < m_ld && aCol < k_ld && bRow < k_ld && bCol < n_ld) {
|
||||
// Load the inputs
|
||||
wmma::load_matrix_sync(a_frag, a + aCol + aRow * lda, lda);
|
||||
wmma::load_matrix_sync(b_frag, b + bCol + bRow * ldb, ldb);
|
||||
|
||||
// Perform the matrix multiplication
|
||||
wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag);
|
||||
}
|
||||
}
|
||||
|
||||
// Load in the current value of c, scale it by beta, and add this our result
|
||||
// scaled by alpha
|
||||
int cCol = warpN * WMMA_N;
|
||||
int cRow = warpM * WMMA_M;
|
||||
|
||||
if (cRow < m_ld && cCol < n_ld) {
|
||||
wmma::load_matrix_sync(c_frag, c + cCol + cRow * ldc, ldc,
|
||||
wmma::mem_row_major);
|
||||
|
||||
for (int i = 0; i < c_frag.num_elements; i++) {
|
||||
c_frag.x[i] = alpha * acc_frag.x[i] + beta * c_frag.x[i];
|
||||
// Perform the matrix multiplication
|
||||
wmma::mma_sync(acc_frag, a_frag, b_frag, acc_frag);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the output
|
||||
wmma::store_matrix_sync(d + cCol + cRow * ldc, c_frag, ldc,
|
||||
wmma::mem_row_major);
|
||||
}
|
||||
// Load in the current value of c, scale it by beta, and add this our result
|
||||
// scaled by alpha
|
||||
int cCol = warpN * WMMA_N;
|
||||
int cRow = warpM * WMMA_M;
|
||||
|
||||
if (cRow < m_ld && cCol < n_ld) {
|
||||
wmma::load_matrix_sync(c_frag, c + cCol + cRow * ldc, ldc, wmma::mem_row_major);
|
||||
|
||||
for (int i = 0; i < c_frag.num_elements; i++) {
|
||||
c_frag.x[i] = alpha * acc_frag.x[i] + beta * c_frag.x[i];
|
||||
}
|
||||
|
||||
// Store the output
|
||||
wmma::store_matrix_sync(d + cCol + cRow * ldc, c_frag, ldc, wmma::mem_row_major);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ void matMultiplyOnHost(uint8_t *A, uint8_t *B, int *C, int alpha,
|
||||
int beta, int numARows, int numAColumns,
|
||||
int numBRows, int numBColumns, int numCRows,
|
||||
int numCColumns) {
|
||||
for (int i = 0; i < numCRows; i++) {
|
||||
for (int j = 0; j < numCColumns; j++) {
|
||||
int temp = 0;
|
||||
__host__ void matMultiplyOnHost(uint8_t *A,
|
||||
uint8_t *B,
|
||||
int *C,
|
||||
int alpha,
|
||||
int beta,
|
||||
int numARows,
|
||||
int numAColumns,
|
||||
int numBRows,
|
||||
int numBColumns,
|
||||
int numCRows,
|
||||
int numCColumns)
|
||||
{
|
||||
for (int i = 0; i < numCRows; i++) {
|
||||
for (int j = 0; j < numCColumns; j++) {
|
||||
int temp = 0;
|
||||
|
||||
for (int k = 0; k < numAColumns; k++) {
|
||||
temp += A[i * numAColumns + k] * B[j * numBRows + k];
|
||||
}
|
||||
for (int k = 0; k < numAColumns; k++) {
|
||||
temp += A[i * numAColumns + k] * B[j * numBRows + k];
|
||||
}
|
||||
|
||||
C[i * numCColumns + j] = temp * alpha + beta * C[i * numCColumns + j];
|
||||
C[i * numCColumns + j] = temp * alpha + beta * C[i * numCColumns + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
printf("Initializing...\n");
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
printf("Initializing...\n");
|
||||
|
||||
int dev = findCudaDevice(argc, (const char **)argv);
|
||||
int dev = findCudaDevice(argc, (const char **)argv);
|
||||
|
||||
cudaDeviceProp deviceProp;
|
||||
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, dev));
|
||||
cudaDeviceProp deviceProp;
|
||||
checkCudaErrors(cudaGetDeviceProperties(&deviceProp, dev));
|
||||
|
||||
// Tensor cores require a GPU of Volta (SM72) architecture or higher.
|
||||
if (deviceProp.major < 7 || (deviceProp.major <= 7 && deviceProp.minor < 2)) {
|
||||
printf(
|
||||
"immaTensorCoreGemm requires SM 7.2 or higher to use Tensor Cores. "
|
||||
"Exiting...\n");
|
||||
exit(EXIT_WAIVED);
|
||||
}
|
||||
|
||||
printf("M: %d (%d x %d)\n", M_GLOBAL, M, M_TILES);
|
||||
printf("N: %d (%d x %d)\n", N_GLOBAL, N, N_TILES);
|
||||
printf("K: %d (%d x %d)\n", K_GLOBAL, K, K_TILES);
|
||||
|
||||
uint8_t *A_h = NULL;
|
||||
uint8_t *B_h = NULL;
|
||||
int *C_h = NULL;
|
||||
#if CPU_DEBUG
|
||||
int *result_hD = NULL;
|
||||
int *result_host = NULL;
|
||||
#endif
|
||||
|
||||
A_h = (uint8_t *)malloc(sizeof(uint8_t) * M_GLOBAL * K_GLOBAL);
|
||||
B_h = (uint8_t *)malloc(sizeof(uint8_t) * K_GLOBAL * N_GLOBAL);
|
||||
C_h = (int *)malloc(sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
#if CPU_DEBUG
|
||||
result_hD = (int *)malloc(sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
result_host = (int *)malloc(sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
#endif
|
||||
|
||||
uint8_t *A = NULL;
|
||||
uint8_t *B = NULL;
|
||||
int *C = NULL;
|
||||
int *D = NULL;
|
||||
|
||||
checkCudaErrors(
|
||||
cudaMalloc(reinterpret_cast<void **>(&A), sizeof(uint8_t) * M_GLOBAL * K_GLOBAL));
|
||||
checkCudaErrors(
|
||||
cudaMalloc(reinterpret_cast<void **>(&B), sizeof(uint8_t) * N_GLOBAL * K_GLOBAL));
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&C), sizeof(int) * M_GLOBAL * N_GLOBAL));
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&D), sizeof(int) * M_GLOBAL * N_GLOBAL));
|
||||
|
||||
assert(((unsigned long long)A) % 128 == 0);
|
||||
assert(((unsigned long long)B) % 128 == 0);
|
||||
assert(((unsigned long long)C) % 128 == 0);
|
||||
assert(((unsigned long long)D) % 128 == 0);
|
||||
|
||||
init_host_matrices(A_h, B_h, C_h);
|
||||
|
||||
checkCudaErrors(cudaMemcpy(A, A_h, sizeof(uint8_t) * M_GLOBAL * K_GLOBAL,
|
||||
cudaMemcpyHostToDevice));
|
||||
checkCudaErrors(cudaMemcpy(B, B_h, sizeof(uint8_t) * N_GLOBAL * K_GLOBAL,
|
||||
cudaMemcpyHostToDevice));
|
||||
checkCudaErrors(cudaMemcpy(C, C_h, sizeof(int) * M_GLOBAL * N_GLOBAL,
|
||||
cudaMemcpyHostToDevice));
|
||||
checkCudaErrors(cudaMemset(D, 0, sizeof(int) * M_GLOBAL * N_GLOBAL));
|
||||
|
||||
printf("Preparing data for GPU...\n");
|
||||
|
||||
assert(((unsigned long long)A) % 128 == 0);
|
||||
assert(((unsigned long long)B) % 128 == 0);
|
||||
assert(((unsigned long long)C) % 128 == 0);
|
||||
assert(((unsigned long long)D) % 128 == 0);
|
||||
|
||||
enum {
|
||||
// Compute the right amount of shared memory to request.
|
||||
// We need shared memory to hold per-CTA C and D matrix tiles, and to cache
|
||||
// per-CTA chunks
|
||||
// of the A and B matrices. Therefore, the right amount to request is the
|
||||
// maximum of those
|
||||
// two numbers.
|
||||
SHMEM_SZ = MAX(sizeof(uint8_t) * (BLOCK_COL_TILES * M) *
|
||||
(CHUNK_K * K + SKEW_UINT8) * 2,
|
||||
M * (BLOCK_ROW_WARPS * WARP_ROW_TILES) * N *
|
||||
(BLOCK_COL_WARPS * WARP_COL_TILES) * sizeof(int))
|
||||
};
|
||||
|
||||
printf("Required shared memory size: %lu Kb\n", SHMEM_SZ / 1024UL);
|
||||
|
||||
int alpha = 1;
|
||||
int beta = 1;
|
||||
|
||||
cudaEvent_t start, stop;
|
||||
|
||||
checkCudaErrors(cudaEventCreate(&start));
|
||||
checkCudaErrors(cudaEventCreate(&stop));
|
||||
checkCudaErrors(cudaEventRecord(start));
|
||||
|
||||
// If enough shared memory available on the GPU use high performant kernel
|
||||
if (deviceProp.sharedMemPerMultiprocessor >= SHMEM_SZ) {
|
||||
printf("Computing... using high performance kernel compute_gemm_imma \n");
|
||||
|
||||
checkCudaErrors(cudaFuncSetAttribute(
|
||||
compute_gemm_imma, cudaFuncAttributeMaxDynamicSharedMemorySize,
|
||||
SHMEM_SZ));
|
||||
checkKernelErrors(
|
||||
(compute_gemm_imma<<<deviceProp.multiProcessorCount, THREADS_PER_BLOCK,
|
||||
SHMEM_SZ>>>(A, B, C, D, alpha, beta)));
|
||||
#if CPU_DEBUG
|
||||
checkCudaErrors(cudaMemcpy(result_hD, D, sizeof(int) * M_GLOBAL * N_GLOBAL,
|
||||
cudaMemcpyDeviceToHost));
|
||||
#endif
|
||||
} else {
|
||||
dim3 gridDim;
|
||||
dim3 blockDim;
|
||||
|
||||
// blockDim.x must be a multiple of warpSize
|
||||
// 128x4 means we have 16 warps and a block computes a 64x64 output tile
|
||||
blockDim.x = 128;
|
||||
blockDim.y = 4;
|
||||
|
||||
gridDim.x = (M_GLOBAL + (WMMA_M * blockDim.x / 32 - 1)) /
|
||||
(WMMA_M * blockDim.x / 32);
|
||||
gridDim.y = (N_GLOBAL + WMMA_N * blockDim.y - 1) / (WMMA_N * blockDim.y);
|
||||
|
||||
printf("Computing... using simple_wmma_gemm_imma kernel\n");
|
||||
simple_wmma_gemm_imma<<<gridDim, blockDim>>>(A, B, C, D, M_GLOBAL, N_GLOBAL,
|
||||
K_GLOBAL, alpha, beta);
|
||||
#if CPU_DEBUG
|
||||
checkCudaErrors(cudaMemcpy(result_hD, D, sizeof(int) * M_GLOBAL * N_GLOBAL,
|
||||
cudaMemcpyDeviceToHost));
|
||||
#endif
|
||||
}
|
||||
|
||||
checkCudaErrors(cudaEventRecord(stop));
|
||||
checkCudaErrors(cudaEventSynchronize(stop));
|
||||
|
||||
#if CPU_DEBUG
|
||||
printf("Verifying correctness of the computations...\n");
|
||||
|
||||
memcpy(result_host, C_h, sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
|
||||
matMultiplyOnHost(A_h, B_h, result_host, alpha, beta, M_GLOBAL, K_GLOBAL,
|
||||
K_GLOBAL, N_GLOBAL, M_GLOBAL, N_GLOBAL);
|
||||
|
||||
for (int i = 0; i < N_GLOBAL * M_GLOBAL; i++) {
|
||||
if (abs(result_hD[i] - result_host[i]) > 0) {
|
||||
printf("mismatch i=%d result_hD=%d result_host=%d\n", i, result_hD[i],
|
||||
result_host[i]);
|
||||
// Tensor cores require a GPU of Volta (SM72) architecture or higher.
|
||||
if (deviceProp.major < 7 || (deviceProp.major <= 7 && deviceProp.minor < 2)) {
|
||||
printf("immaTensorCoreGemm requires SM 7.2 or higher to use Tensor Cores. "
|
||||
"Exiting...\n");
|
||||
exit(EXIT_WAIVED);
|
||||
}
|
||||
}
|
||||
free(result_host);
|
||||
free(result_hD);
|
||||
|
||||
printf("M: %d (%d x %d)\n", M_GLOBAL, M, M_TILES);
|
||||
printf("N: %d (%d x %d)\n", N_GLOBAL, N, N_TILES);
|
||||
printf("K: %d (%d x %d)\n", K_GLOBAL, K, K_TILES);
|
||||
|
||||
uint8_t *A_h = NULL;
|
||||
uint8_t *B_h = NULL;
|
||||
int *C_h = NULL;
|
||||
#if CPU_DEBUG
|
||||
int *result_hD = NULL;
|
||||
int *result_host = NULL;
|
||||
#endif
|
||||
|
||||
float milliseconds = 0;
|
||||
A_h = (uint8_t *)malloc(sizeof(uint8_t) * M_GLOBAL * K_GLOBAL);
|
||||
B_h = (uint8_t *)malloc(sizeof(uint8_t) * K_GLOBAL * N_GLOBAL);
|
||||
C_h = (int *)malloc(sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
#if CPU_DEBUG
|
||||
result_hD = (int *)malloc(sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
result_host = (int *)malloc(sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
#endif
|
||||
|
||||
checkCudaErrors(cudaEventElapsedTime(&milliseconds, start, stop));
|
||||
uint8_t *A = NULL;
|
||||
uint8_t *B = NULL;
|
||||
int *C = NULL;
|
||||
int *D = NULL;
|
||||
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&A), sizeof(uint8_t) * M_GLOBAL * K_GLOBAL));
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&B), sizeof(uint8_t) * N_GLOBAL * K_GLOBAL));
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&C), sizeof(int) * M_GLOBAL * N_GLOBAL));
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&D), sizeof(int) * M_GLOBAL * N_GLOBAL));
|
||||
|
||||
assert(((unsigned long long)A) % 128 == 0);
|
||||
assert(((unsigned long long)B) % 128 == 0);
|
||||
assert(((unsigned long long)C) % 128 == 0);
|
||||
assert(((unsigned long long)D) % 128 == 0);
|
||||
|
||||
init_host_matrices(A_h, B_h, C_h);
|
||||
|
||||
checkCudaErrors(cudaMemcpy(A, A_h, sizeof(uint8_t) * M_GLOBAL * K_GLOBAL, cudaMemcpyHostToDevice));
|
||||
checkCudaErrors(cudaMemcpy(B, B_h, sizeof(uint8_t) * N_GLOBAL * K_GLOBAL, cudaMemcpyHostToDevice));
|
||||
checkCudaErrors(cudaMemcpy(C, C_h, sizeof(int) * M_GLOBAL * N_GLOBAL, cudaMemcpyHostToDevice));
|
||||
checkCudaErrors(cudaMemset(D, 0, sizeof(int) * M_GLOBAL * N_GLOBAL));
|
||||
|
||||
printf("Preparing data for GPU...\n");
|
||||
|
||||
assert(((unsigned long long)A) % 128 == 0);
|
||||
assert(((unsigned long long)B) % 128 == 0);
|
||||
assert(((unsigned long long)C) % 128 == 0);
|
||||
assert(((unsigned long long)D) % 128 == 0);
|
||||
|
||||
enum {
|
||||
// Compute the right amount of shared memory to request.
|
||||
// We need shared memory to hold per-CTA C and D matrix tiles, and to cache
|
||||
// per-CTA chunks
|
||||
// of the A and B matrices. Therefore, the right amount to request is the
|
||||
// maximum of those
|
||||
// two numbers.
|
||||
SHMEM_SZ = MAX(sizeof(uint8_t) * (BLOCK_COL_TILES * M) * (CHUNK_K * K + SKEW_UINT8) * 2,
|
||||
M * (BLOCK_ROW_WARPS * WARP_ROW_TILES) * N * (BLOCK_COL_WARPS * WARP_COL_TILES) * sizeof(int))
|
||||
};
|
||||
|
||||
printf("Required shared memory size: %lu Kb\n", SHMEM_SZ / 1024UL);
|
||||
|
||||
int alpha = 1;
|
||||
int beta = 1;
|
||||
|
||||
cudaEvent_t start, stop;
|
||||
|
||||
checkCudaErrors(cudaEventCreate(&start));
|
||||
checkCudaErrors(cudaEventCreate(&stop));
|
||||
checkCudaErrors(cudaEventRecord(start));
|
||||
|
||||
// If enough shared memory available on the GPU use high performant kernel
|
||||
if (deviceProp.sharedMemPerMultiprocessor >= SHMEM_SZ) {
|
||||
printf("Computing... using high performance kernel compute_gemm_imma \n");
|
||||
|
||||
checkCudaErrors(cudaFuncSetAttribute(compute_gemm_imma, cudaFuncAttributeMaxDynamicSharedMemorySize, SHMEM_SZ));
|
||||
checkKernelErrors((compute_gemm_imma<<<deviceProp.multiProcessorCount, THREADS_PER_BLOCK, SHMEM_SZ>>>(
|
||||
A, B, C, D, alpha, beta)));
|
||||
#if CPU_DEBUG
|
||||
checkCudaErrors(cudaMemcpy(result_hD, D, sizeof(int) * M_GLOBAL * N_GLOBAL, cudaMemcpyDeviceToHost));
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
dim3 gridDim;
|
||||
dim3 blockDim;
|
||||
|
||||
// blockDim.x must be a multiple of warpSize
|
||||
// 128x4 means we have 16 warps and a block computes a 64x64 output tile
|
||||
blockDim.x = 128;
|
||||
blockDim.y = 4;
|
||||
|
||||
gridDim.x = (M_GLOBAL + (WMMA_M * blockDim.x / 32 - 1)) / (WMMA_M * blockDim.x / 32);
|
||||
gridDim.y = (N_GLOBAL + WMMA_N * blockDim.y - 1) / (WMMA_N * blockDim.y);
|
||||
|
||||
printf("Computing... using simple_wmma_gemm_imma kernel\n");
|
||||
simple_wmma_gemm_imma<<<gridDim, blockDim>>>(A, B, C, D, M_GLOBAL, N_GLOBAL, K_GLOBAL, alpha, beta);
|
||||
#if CPU_DEBUG
|
||||
checkCudaErrors(cudaMemcpy(result_hD, D, sizeof(int) * M_GLOBAL * N_GLOBAL, cudaMemcpyDeviceToHost));
|
||||
#endif
|
||||
}
|
||||
|
||||
checkCudaErrors(cudaEventRecord(stop));
|
||||
checkCudaErrors(cudaEventSynchronize(stop));
|
||||
|
||||
#if CPU_DEBUG
|
||||
printf("Verifying correctness of the computations...\n");
|
||||
|
||||
memcpy(result_host, C_h, sizeof(int) * M_GLOBAL * N_GLOBAL);
|
||||
|
||||
matMultiplyOnHost(A_h, B_h, result_host, alpha, beta, M_GLOBAL, K_GLOBAL, K_GLOBAL, N_GLOBAL, M_GLOBAL, N_GLOBAL);
|
||||
|
||||
for (int i = 0; i < N_GLOBAL * M_GLOBAL; i++) {
|
||||
if (abs(result_hD[i] - result_host[i]) > 0) {
|
||||
printf("mismatch i=%d result_hD=%d result_host=%d\n", i, result_hD[i], result_host[i]);
|
||||
}
|
||||
}
|
||||
free(result_host);
|
||||
free(result_hD);
|
||||
#endif
|
||||
|
||||
float milliseconds = 0;
|
||||
|
||||
checkCudaErrors(cudaEventElapsedTime(&milliseconds, start, stop));
|
||||
|
||||
printf("Time: %f ms\n", milliseconds);
|
||||
printf("TOPS: %.2f\n", (((double)M_GLOBAL * N_GLOBAL * K_GLOBAL * 2)/(milliseconds/1000.)) / 1e12);
|
||||
printf("TOPS: %.2f\n", (((double)M_GLOBAL * N_GLOBAL * K_GLOBAL * 2) / (milliseconds / 1000.)) / 1e12);
|
||||
|
||||
free(A_h);
|
||||
free(B_h);
|
||||
free(C_h);
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(A)));
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(B)));
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(C)));
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(D)));
|
||||
free(A_h);
|
||||
free(B_h);
|
||||
free(C_h);
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(A)));
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(B)));
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(C)));
|
||||
checkCudaErrors(cudaFree(reinterpret_cast<void *>(D)));
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user