행렬 연산의 프레임워크화
매번 행렬의 계산이 있을 때마다 복잡한 kernel을 호출하게 되면, 코드가 간결하지 못하게되고 가독성이 떨어진다. 따라서, 아래와 같이 공통화할 수 있는 부분들을 모아서 행렬 연산 프레임워크 설계가 가능하다. 프레임워크 설계의 아주 간단한 예시이다.
class cuMAt{
float *mDevice = NULL;
float *mhost = NULL;
int rows = 0;
int cols = 0;
cublasHandle_t cudaHandle;
cuMAt(int rows, int cols){
cublasCreate(&cudaHandle);
cudaThreadSynchronize();
new_matrix(rows, cols);
}
cuMat(const cuMat &a) {
cublasCreate(&cudaHandle);
cudaThreadSynchronize();
new_matrix(a.row, a.cols);
cudaError_t error = cudaMemcpy(mDevice, a.mDevice, rows*cols*sizeof(mDevice), cudaMemcpyDeviceToDevice);
if (error != cudaSuccess);
}
~cuMat() {
del_matrix();
cublasDestroy(cudaHandle);
}
}
- kernel은 기본적으로 비동기로 동작한다. 따라서, gpu내에서 처리가 완료되지 않아도 kernel에서 프로그램으로 되돌아 올 수 있다. 만약 gpu의 결과를 이용해야 한다면, CUDA의 thread가 완료될 때까지 대기해야 한다.
- cudaThreadSynchronize()
- 실행 중인 모든 CUDA 스레드의 처리가 완료될 때까지 대기하는 함수이다.
- 사용하는 함수에 따라서, 동기화가 필요한지를 확인해야한다. -> 사용 함수마다 동기/비동기인지 확인
cuBLAS
위의 예시처럼, 행렬의 연산과 관련한 것들을 CUDA 커널로 구현이 가능하다. 하지만 CUDA toolkit에는 이미 cuBLAS라는 행렬 연산에 특화된 라이브러리가 포함되어있다. 다음은 행렬의 덧셈을 cuBLAS로 구현한 코드이다.
void plus (const cuMat &b, cuMAT &r){
float alpha = 1;
float beta = 1;
cublasStatus_t stat = cublasSgream(
r.cudaHandle,
CUBLAS_OP_N,
CUBLAS_OP_N,
rows,
cols,
&alpha,
mDevice,
rows,
&beta,
b.mDevice,
rows,
r.mDevice,
r.rows);
if (stat !=CUBLAS_STATUS_SUCCESS)
cudaThreadSynchronize();
}
cublasStatus_t cublasSgream(cublasHandle_t handle,
cublasOperation_t transa,
cublasOperation_t transb,
int m, int n,
const float *alpha,
const float *A, int lda,
const float *beta,
const float *B, int ldb,
float *C, int ldc)
- 인수로서 더하고 싶은 행렬 b와 결과를 저장할 r를 받아, plus를 호출한 자기 자신의 행렬과 b를 더해 r에 결과를 저장한다.
- cublasSgream은 cuBLAS의 함수이다.
- C언어에서 일괄적으로 확보된 메모리는 물리적으로 연속된 메모리 영역에 확보된다. 논리 배열로 확보한 영역에 물리적 배치의 구별은 없다.
- cuBLAS의 논리 배열의 메모리 배치 순서는 column-major이다.
- C언어 및 기타 주요 프로그래밍 언어에서는 row-major이다.