Commit CUDA code in src/cuda
This commit is contained in:
parent
1091a55b81
commit
77c6b84a6f
87
src/cuda/matrix.hpp
Normal file
87
src/cuda/matrix.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef MATRIX_H_
|
||||
#define MATRIX_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include <xxhash.h>
|
||||
|
||||
template <class T> struct Matrix {
|
||||
std::uint32_t rows;
|
||||
std::uint32_t cols;
|
||||
std::vector<T> mem;
|
||||
|
||||
Matrix<T>(std::uint32_t _rows, std::uint32_t _cols)
|
||||
: rows(_rows), cols(_cols) {
|
||||
mem.resize(rows * cols);
|
||||
}
|
||||
|
||||
Matrix<T>(const char *filepath) {
|
||||
std::ifstream matfs(filepath);
|
||||
|
||||
if (matfs.fail() || !matfs.is_open()) {
|
||||
throw std::runtime_error("Error opening matrix file");
|
||||
}
|
||||
|
||||
matfs >> this->rows >> this->cols;
|
||||
this->mem.resize(this->rows * this->cols);
|
||||
|
||||
for (std::uint32_t i = 0; i < rows; i++) {
|
||||
for (std::uint32_t j = 0; j < cols; j++) {
|
||||
matfs >> (*this)(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
matfs.close();
|
||||
}
|
||||
|
||||
T &operator()(std::uint32_t row_i, std::uint32_t col_j) {
|
||||
return mem[row_i * cols + col_j];
|
||||
}
|
||||
|
||||
const T &operator()(std::uint32_t row_i, std::uint32_t col_j) const {
|
||||
return mem[row_i * cols + col_j];
|
||||
}
|
||||
|
||||
Matrix<T> &operator=(const Matrix<T> &mat) {
|
||||
this->rows = mat.rows;
|
||||
this->cols = mat.cols;
|
||||
this->data = mem;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Matrix<T> t() const {
|
||||
Matrix<T> transposed = *this;
|
||||
for (std::uint32_t i = 0; i < this->rows; i++) {
|
||||
for (std::uint32_t j = 0; j < this->cols; j++) {
|
||||
transposed(j, i) = (*this)(i, j);
|
||||
}
|
||||
}
|
||||
return transposed;
|
||||
}
|
||||
|
||||
XXH32_hash_t chksum() const {
|
||||
constexpr XXH32_hash_t HASH_SEED = 42;
|
||||
return XXH32(this->mem.data(), mem.size(), HASH_SEED);
|
||||
}
|
||||
|
||||
std::size_t bytes() const { return this->mem.size() * sizeof(T); }
|
||||
};
|
||||
|
||||
template <class T> std::ostream &operator<<(std::ostream &os, Matrix<T> &mat) {
|
||||
for (std::uint32_t i = 0; i < mat.rows; i++) {
|
||||
for (std::uint32_t j = 0; j < mat.cols; j++) {
|
||||
os << mat(i, j) << "\t";
|
||||
}
|
||||
os << "\n";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
#endif // MATRIX_H_
|
||||
193
src/cuda/matrixMult.cu
Normal file
193
src/cuda/matrixMult.cu
Normal file
@ -0,0 +1,193 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cuda_runtime.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "matrix.hpp"
|
||||
#include "timer.hpp"
|
||||
|
||||
#define stream_hex(_val) std::hex << _val << std::dec
|
||||
|
||||
#define print_pair(_desc, _chksm, _time) \
|
||||
std::cout << _desc << "\n\t" \
|
||||
<< "\t-> Check: 0x" << stream_hex(_chksm) \
|
||||
<< "\tRuntime: " << _time << " us\n\n"
|
||||
|
||||
#define checkCudaErrors(call) \
|
||||
do { \
|
||||
cudaError_t err = call; \
|
||||
if (err != cudaSuccess) { \
|
||||
printf("CUDA error at %s %d: %s\n", __FILE__, __LINE__, \
|
||||
cudaGetErrorString(err)); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
enum CUDA_FUNC { CUDA_NAIVE, CUDA_TILED };
|
||||
|
||||
using data_type = int;
|
||||
|
||||
template <class T>
|
||||
__global__ void matrixMultCuda(T *matA, T *matB, std::uint32_t colsA,
|
||||
T *matRes) {
|
||||
|
||||
const auto tidx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const auto tidy = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
|
||||
T sum = 0;
|
||||
|
||||
for (std::uint32_t k = 0; k < colsA; k++) {
|
||||
sum += matA[tidy * colsA + k] * matB[k * colsA + tidx];
|
||||
}
|
||||
|
||||
matRes[tidy * colsA + tidx] = sum;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
__global__ void matrixMultCudaTiled(T *matA, T *matB, std::uint32_t colsA,
|
||||
std::uint32_t colsB, T *matRes) {
|
||||
|
||||
const auto tidx = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
const auto tidy = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
|
||||
const auto tile_size = blockDim.x;
|
||||
|
||||
std::uint32_t a = tidy * colsA + threadIdx.x;
|
||||
std::uint32_t b = tile_size * blockIdx.x + threadIdx.x + threadIdx.y * colsB;
|
||||
|
||||
const auto a_step = tile_size;
|
||||
const auto b_step = tile_size * colsB;
|
||||
|
||||
const auto a_end = a + colsA;
|
||||
|
||||
extern __shared__ T localMem[];
|
||||
|
||||
T *localA = &localMem[0];
|
||||
T *localB = &localMem[tile_size * tile_size];
|
||||
|
||||
T sum = 0;
|
||||
|
||||
for (; a < a_end; a += a_step, b += b_step) {
|
||||
localA[threadIdx.y * tile_size + threadIdx.x] = matA[a];
|
||||
localB[threadIdx.y * tile_size + threadIdx.x] = matB[b];
|
||||
|
||||
__syncthreads();
|
||||
|
||||
#pragma unroll
|
||||
for (std::uint32_t k = 0; k < tile_size; k++) {
|
||||
sum += localA[threadIdx.y * tile_size + k] *
|
||||
localB[k * tile_size + threadIdx.x];
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
matRes[tidy * colsA + tidx] = sum;
|
||||
}
|
||||
|
||||
inline int prevPowerOfTwo(int n) {
|
||||
if (n <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate the most significant bit position (MSB)
|
||||
int msbPos = 0;
|
||||
while (n > 1) {
|
||||
n >>= 1;
|
||||
msbPos++;
|
||||
}
|
||||
|
||||
// Calculate the next power of 2
|
||||
int result = 1 << msbPos;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T, CUDA_FUNC F>
|
||||
std::uint32_t matrixMult(const Matrix<T> &matA, const Matrix<T> &matB) {
|
||||
Matrix<T> matRes(matA.rows, matB.cols);
|
||||
|
||||
T *d_matA;
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&d_matA), matA.bytes()));
|
||||
checkCudaErrors(cudaMemcpy(d_matA, matA.mem.data(), matA.bytes(),
|
||||
cudaMemcpyHostToDevice));
|
||||
|
||||
T *d_matB;
|
||||
checkCudaErrors(cudaMalloc(reinterpret_cast<void **>(&d_matB), matB.bytes()));
|
||||
checkCudaErrors(cudaMemcpy(d_matB, matB.mem.data(), matB.bytes(),
|
||||
cudaMemcpyHostToDevice));
|
||||
|
||||
T *d_matRes;
|
||||
checkCudaErrors(
|
||||
cudaMalloc(reinterpret_cast<void **>(&d_matRes), matRes.bytes()));
|
||||
|
||||
// obtain the maximum work group size for the given device
|
||||
int device_id;
|
||||
checkCudaErrors(cudaGetDevice(&device_id));
|
||||
|
||||
cudaDeviceProp dev_props;
|
||||
checkCudaErrors(cudaGetDeviceProperties(&dev_props, device_id));
|
||||
|
||||
std::size_t max_block_size = dev_props.maxThreadsPerBlock;
|
||||
|
||||
// each tile should not exceed the max_group_size -> T x T <= max_group_size
|
||||
const std::uint32_t max_tile_size =
|
||||
static_cast<std::uint32_t>(prevPowerOfTwo(std::sqrt(max_block_size)));
|
||||
|
||||
// maybe the problem size of the multiplication is smaller than the maximum
|
||||
// tile size
|
||||
const std::uint32_t block_size = std::min(max_tile_size, matA.cols);
|
||||
|
||||
dim3 threads(block_size, block_size);
|
||||
dim3 grid(matB.cols / threads.x, matA.rows / threads.y);
|
||||
|
||||
cudaDeviceSynchronize();
|
||||
|
||||
if constexpr (F == CUDA_NAIVE) {
|
||||
matrixMultCuda<T><<<grid, threads>>>(d_matA, d_matB, matA.cols, d_matRes);
|
||||
} else if constexpr (F == CUDA_TILED) {
|
||||
const auto shared_mem_size = sizeof(T) * block_size * block_size * 2;
|
||||
matrixMultCudaTiled<T><<<grid, threads, shared_mem_size>>>(
|
||||
d_matA, d_matB, matA.cols, matB.cols, d_matRes);
|
||||
}
|
||||
|
||||
cudaDeviceSynchronize();
|
||||
|
||||
checkCudaErrors(cudaMemcpy(matRes.mem.data(), d_matRes, matRes.bytes(),
|
||||
cudaMemcpyDeviceToHost));
|
||||
cudaDeviceSynchronize();
|
||||
|
||||
checkCudaErrors(cudaFree(d_matA));
|
||||
checkCudaErrors(cudaFree(d_matB));
|
||||
checkCudaErrors(cudaFree(d_matRes));
|
||||
|
||||
return matRes.chksum();
|
||||
}
|
||||
|
||||
auto main(int argc, char *argv[]) -> int {
|
||||
|
||||
if (argc != 3) {
|
||||
std::cerr << "Provide 2 arguments to the program!\n"
|
||||
<< "Usage: <prog> <matA>.txt <matB>.txt\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Matrix<data_type> matA(argv[1]);
|
||||
Matrix<data_type> matB(argv[2]);
|
||||
|
||||
assert(matA.rows == matB.cols);
|
||||
|
||||
const auto naive_chk =
|
||||
measure<>::duration(matrixMult<data_type, CUDA_NAIVE>, matA, matB);
|
||||
|
||||
print_pair("CUDA naive", naive_chk.first, naive_chk.second.count());
|
||||
|
||||
const auto tiled_chk =
|
||||
measure<>::duration(matrixMult<data_type, CUDA_TILED>, matA, matB);
|
||||
|
||||
print_pair("CUDA tiled", tiled_chk.first, tiled_chk.second.count());
|
||||
|
||||
return 0;
|
||||
}
|
||||
22
src/cuda/timer.hpp
Normal file
22
src/cuda/timer.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef TIMER_H_
|
||||
#define TIMER_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
template <class TimeUnit = std::chrono::microseconds,
|
||||
class ClockType = std::chrono::steady_clock>
|
||||
struct measure {
|
||||
template <class F, class... Args>
|
||||
static auto duration(F &&func, Args &&...args) {
|
||||
auto start = ClockType::now();
|
||||
auto retVal =
|
||||
std::invoke(std::forward<F>(func), std::forward<Args>(args)...);
|
||||
auto end = ClockType::now();
|
||||
return std::make_pair(retVal,
|
||||
std::chrono::duration_cast<TimeUnit>(end - start));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TIMER_H_
|
||||
Loading…
x
Reference in New Issue
Block a user