使用 CUBLAS 庫給矩陣運算提速
阿新 • • 發佈:2019-02-06
前言
編寫 CUDA 程式真心不是個簡單的事兒,除錯也不方便,很費時。那麼有沒有一些現成的 CUDA 庫來呼叫呢?
答案是有的,如 CUBLAS 就是 CUDA 專門用來解決線性代數運算的庫。
本文將大致介紹如何使用 CUBLAS 庫,同時演示一個使用 CUBLAS 庫進行矩陣乘法的例子。
CUBLAS 內容
CUBLAS 是 CUDA 專門用來解決線性代數運算的庫,它分為三個級別:
Lev1. 向量相乘
Lev2. 矩陣乘向量
Lev3. 矩陣乘矩陣
同時該庫還包含狀態結構和一些功能函式。
CUBLAS 用法
大體分成以下幾個步驟:
1. 定義 CUBLAS 庫物件
2. 在視訊記憶體中為待運算的資料以及需要存放結果的變數開闢視訊記憶體空間。( cudaMalloc 函式實現 )
3. 將待運算的資料傳輸進視訊記憶體。( cudaMemcpy,cublasSetVector 等函式實現 )
3. 呼叫 CUBLAS 庫函式 ( 根據 CUBLAS 手冊呼叫需要的函式 )
4. 從視訊記憶體中獲取結果變數。( cudaMemcpy,cublasGetVector 等函式實現 )
5. 釋放申請的視訊記憶體空間以及 CUBLAS 庫物件。( cudaFree 及 cublasDestroy 函式實現 )
程式碼示例
如下程式使用 CUBLAS 庫進行矩陣乘法運算,請仔細閱讀註釋,尤其是 API 的引數說明:
// CUDA runtime 庫 + CUBLAS 庫
#include "cuda_runtime.h"
#include "cublas_v2.h"
#include <time.h>
#include <iostream>
using namespace std;
// 定義測試矩陣的維度
int const M = 5;
int const N = 10;
int main()
{
// 定義狀態變數
cublasStatus_t status;
// 在 記憶體 中為將要計算的矩陣開闢空間
float *h_A = (float *)malloc (N*M*sizeof(float));
float *h_B = (float*)malloc (N*M*sizeof(float));
// 在 記憶體 中為將要存放運算結果的矩陣開闢空間
float *h_C = (float*)malloc (M*M*sizeof(float));
// 為待運算矩陣的元素賦予 0-10 範圍內的隨機數
for (int i=0; i<N*M; i++) {
h_A[i] = (float)(rand()%10+1);
h_B[i] = (float)(rand()%10+1);
}
// 列印待測試的矩陣
cout << "矩陣 A :" << endl;
for (int i=0; i<N*M; i++){
cout << h_A[i] << " ";
if ((i+1)%N == 0) cout << endl;
}
cout << endl;
cout << "矩陣 B :" << endl;
for (int i=0; i<N*M; i++){
cout << h_B[i] << " ";
if ((i+1)%M == 0) cout << endl;
}
cout << endl;
/*
** GPU 計算矩陣相乘
*/
// 建立並初始化 CUBLAS 庫物件
cublasHandle_t handle;
status = cublasCreate(&handle);
if (status != CUBLAS_STATUS_SUCCESS)
{
if (status == CUBLAS_STATUS_NOT_INITIALIZED) {
cout << "CUBLAS 物件例項化出錯" << endl;
}
getchar ();
return EXIT_FAILURE;
}
float *d_A, *d_B, *d_C;
// 在 視訊記憶體 中為將要計算的矩陣開闢空間
cudaMalloc (
(void**)&d_A, // 指向開闢的空間的指標
N*M * sizeof(float) // 需要開闢空間的位元組數
);
cudaMalloc (
(void**)&d_B,
N*M * sizeof(float)
);
// 在 視訊記憶體 中為將要存放運算結果的矩陣開闢空間
cudaMalloc (
(void**)&d_C,
M*M * sizeof(float)
);
// 將矩陣資料傳遞進 視訊記憶體 中已經開闢好了的空間
cublasSetVector (
N*M, // 要存入視訊記憶體的元素個數
sizeof(float), // 每個元素大小
h_A, // 主機端起始地址
1, // 連續元素之間的儲存間隔
d_A, // GPU 端起始地址
1 // 連續元素之間的儲存間隔
);
cublasSetVector (
N*M,
sizeof(float),
h_B,
1,
d_B,
1
);
// 同步函式
cudaThreadSynchronize();
// 傳遞進矩陣相乘函式中的引數,具體含義請參考函式手冊。
float a=1; float b=0;
// 矩陣相乘。該函式必然將陣列解析成列優先陣列
cublasSgemm (
handle, // blas 庫物件
CUBLAS_OP_T, // 矩陣 A 屬性引數
CUBLAS_OP_T, // 矩陣 B 屬性引數
M, // A, C 的行數
M, // B, C 的列數
N, // A 的列數和 B 的行數
&a, // 運算式的 α 值
d_A, // A 在視訊記憶體中的地址
N, // lda
d_B, // B 在視訊記憶體中的地址
M, // ldb
&b, // 運算式的 β 值
d_C, // C 在視訊記憶體中的地址(結果矩陣)
M // ldc
);
// 同步函式
cudaThreadSynchronize();
// 從 視訊記憶體 中取出運算結果至 記憶體中去
cublasGetVector (
M*M, // 要取出元素的個數
sizeof(float), // 每個元素大小
d_C, // GPU 端起始地址
1, // 連續元素之間的儲存間隔
h_C, // 主機端起始地址
1 // 連續元素之間的儲存間隔
);
// 列印運算結果
cout << "計算結果的轉置 ( (A*B)的轉置 ):" << endl;
for (int i=0;i<M*M; i++){
cout << h_C[i] << " ";
if ((i+1)%M == 0) cout << endl;
}
// 清理掉使用過的記憶體
free (h_A);
free (h_B);
free (h_C);
cudaFree (d_A);
cudaFree (d_B);
cudaFree (d_C);
// 釋放 CUBLAS 庫物件
cublasDestroy (handle);
getchar();
return 0;
}
PS:矩陣元素是隨機生成的
小結
1. 使用 CUDA 庫固然方便,但也要仔細的參閱函式手冊,其中每個引數的含義都要很清晰才不容易出錯。
2. 如果程式僅使用 CUDA 庫的話,用 .cpp 原始碼檔案即可 (不用 .cu)