CUBLAS中的矩陣乘法函式詳解
關於cuBLAS庫中矩陣乘法相關的函式及其輸入輸出進行詳細討論。
▶ 漲姿勢:
● cuBLAS中能用於運算矩陣乘法的函式有4個,分別是 cublasSgemm(單精度實數)、cublasDgemm(雙精度實數)、cublasCgemm(單精度複數)、cublasZgemm(雙精度複數),它們的定義(在 cublas_v2.h 和 cublas_api.h 中)如下。
1 #define cublasSgemm cublasSgemm_v2 2 CUBLASAPI cublasStatus_t CUBLASWINAPI cublasSgemm_v2 3 ( 4 cublasHandle_t handle,5 cublasOperation_t transa, cublasOperation_t transb, 6 int m, int n, int k, 7 const float *alpha, 8 const float *A, int lda, 9 const float *B, int ldb, 10 const float *beta, 11 float *C, int ldc 12 ); 13 14 #define cublasDgemm cublasDgemm_v2 15 CUBLASAPI cublasStatus_t CUBLASWINAPI cublasDgemm_v216 ( 17 cublasHandle_t handle, 18 cublasOperation_t transa, cublasOperation_t transb, 19 int m, int n, int k, 20 const double *alpha, 21 const double *A, int lda, 22 const double *B, int ldb, 23 const double *beta, 24 double *C, int ldc 25 ); 26 27 #define cublasCgemm cublasCgemm_v2 28CUBLASAPI cublasStatus_t CUBLASWINAPI cublasCgemm_v2 29 ( 30 cublasHandle_t handle, 31 cublasOperation_t transa, cublasOperation_t transb, 32 int m, int n, int k, 33 const cuComplex *alpha, 34 const cuComplex *A, int lda, 35 const cuComplex *B, int ldb, 36 const cuComplex *beta, 37 cuComplex *C, int ldc 38 ); 39 40 #define cublasZgemm cublasZgemm_v2 41 CUBLASAPI cublasStatus_t CUBLASWINAPI cublasZgemm_v2 42 ( 43 cublasHandle_t handle, 44 cublasOperation_t transa, cublasOperation_t transb, 45 int m, int n, int k, 46 const cuDoubleComplex *alpha, 47 const cuDoubleComplex *A, int lda, 48 const cuDoubleComplex *B, int ldb, 49 const cuDoubleComplex *beta, 50 cuDoubleComplex *C, 51 int ldc 52 );
● 四個函式形式相似,均輸入了14個引數。該函式實際上是用於計算 C = α A B +β C 的,其中 Am×k 、Bk×n 、Cm×n 為矩陣,α 、β 為標量。這裡先籠統的描述每個引數的意義,再通過後面的分點逐一詳細說明。
cublasHandle_t handle 呼叫cuBLAS庫時的控制代碼。
cublasOperation_t transa 是否轉置矩陣A
cublasOperation_t transb 是否轉置矩陣B
int m 矩陣A的行數,等於矩陣C的行數;不再單獨說明
int n 矩陣B的列數,等於矩陣C的列數;不再單獨說明
int k 矩陣A的列數,等於矩陣B的行數;不再單獨說明
const float *alpha 標量 α 的指標,可以是主機指標或裝置指標,只需要計算矩陣乘法時命 α = 1.0f;不再單獨說明
const float *A 矩陣(陣列)A 的指標,必須是裝置指標;不再單獨說明
int lda 矩陣 A 的主維(leading dimension)
const float *B 矩陣(陣列)B 的指標,必須是裝置指標;不再單獨說明
int ldb 矩陣 B 的主維
const float *beta 標量 β 的指標,可以是主機指標或裝置指標,只需要計算矩陣乘法時命 β = 0.0f;不再單獨說明
float *C 矩陣(陣列)C 的指標,必須是裝置指標;不再單獨說明
int ldc 矩陣 C 的主維
這裡我們採用測試程式碼(完整的程式碼在本文末尾)中的例子加以說明。其中維度常數 m = 2, n = 4, k = 3。
① cublasDataType_t handle
一個有關cuBLAS庫的上下文的控制代碼,之後需要傳遞給API函式,即計算乘法的函式, 簡單說就是以下過程。
1 ...// 準備 A, B, C 以及使用的執行緒網格、執行緒塊的尺寸 2 3 // 建立控制代碼 4 cublasHandle_t handle; 5 cublasCreate(&handle); 6 7 // 呼叫計算函式 8 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, *B, n, *A, k, &beta, *C, n); 9 10 // 銷燬控制代碼 11 cublasDestroy(handle); 12 13 ...// 回收計算結果,順序可以和銷燬控制代碼交換
另外,建立控制代碼的函式 cublasCreate() 會返回一個 cublasStatus_t 型別的值,用來判斷控制代碼是否建立成功,該值在 cublas_api.h 中定義如下,可見只有其等於0的時候才能認為建立成功。
1 typedef enum{ 2 CUBLAS_STATUS_SUCCESS =0, 3 CUBLAS_STATUS_NOT_INITIALIZED =1, 4 CUBLAS_STATUS_ALLOC_FAILED =3, 5 CUBLAS_STATUS_INVALID_VALUE =7, 6 CUBLAS_STATUS_ARCH_MISMATCH =8, 7 CUBLAS_STATUS_MAPPING_ERROR =11, 8 CUBLAS_STATUS_EXECUTION_FAILED=13, 9 CUBLAS_STATUS_INTERNAL_ERROR =14, 10 CUBLAS_STATUS_NOT_SUPPORTED =15, 11 CUBLAS_STATUS_LICENSE_ERROR =16 12 } cublasStatus_t;
② cublasOperation_t transa, cublasOperation_t transb
兩個“是否需要對輸入矩陣 A、B 進行轉置”的引數,這是 cuBLAS 庫難點之一。簡單地說,cuBLAS 中關於矩陣的儲存方式與 fortran、MATLAB類似,採用的是列優先,而非 C / C++ 中的行優先。
所以,當我們將 C / C++ 中行優先形式儲存的陣列 A 輸入到cuBLAS中時,會被cuBLAS理解為列優先儲存。這時如果保持 A 的行、列數不變,則矩陣 A 會發生重排(過程類似 MATLAB 中的 reshape(A, [size(A,2), size(A, 1)])),除非同時交換 A 的行、列數,此時結果才恰好等於 A 的轉置,在一般的呼叫過程中正是利用了這一條性質。於是 cuBLAS 事先利用這個引數詢問,是否需要將矩陣 A、B 進行轉置。在這裡,我嘗試了大量的例子,結合圖形來說明cuBLAS中對陣列的操作。
該引數轉置有以下三種,CUBLAS_OP_N 表示不轉置,CUBLAS_OP_T 表示需要普通轉置,CUBLAS_OP_C 表示需要共軛轉置。例子中僅涉及單精度實數運算,採用 CUBLAS_OP_N 或者 CUBLAS_OP_T 。
1 typedef enum { 2 CUBLAS_OP_N = 0, 3 CUBLAS_OP_T = 1, 4 CUBLAS_OP_C = 2 5 } cublasOperation_t;
1° (錯誤示範)直接將A、B放入函式中,轉置引數均取為 CUBLAS_OP_N(lda、ldb、ldc 正常地取作各矩陣的行數,後面會解釋其作用,到時候再回過頭來看就好)。
1 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, *A, m, *B, k, &beta, *C, m);
過程和結果如下。cuBLAS 自動將輸入的 A、B 理解為列優先儲存,而且保持 A、B 的行、列數保持不變,即將其當作 A1 和 B1。注意這裡的 A1、B1 並不等於 A、B 的轉置。計算乘積 C1 作為輸出,最後我們在主機中將輸出C1看作是行優先的矩陣,就成了C2的模樣。顯然這樣計算的結果顯然不是我們期望的 C 。
2° (正確過程)這一般教程上的呼叫過程。利用了性質 A B = (BT AT)T 來計算矩陣乘積。如前文所述,我們把一個行優先的矩陣看作列優先的同時,交換其行、列數,其結果等價於得到該矩陣的轉置,反之列優先轉行優先的原理相同。所以我們可以在呼叫該函式的時候,先放入 B 並交換引數 k 和 n 的位置,再放入 A 並交換引數 m 和 k 的位置,這樣就順理成章得到了結果的 C (所有轉換由cuBLAS完成,不需要手工調整陣列),注意以下呼叫語句中紅色的部分。
1 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, n, m, k, &alpha, *B, n, *A, k, &beta, *C, n);
過程和結果如下。cuBLAS讀取 B 和 A 的時候雖然進行了重排,但是
2.5° (嘗試手工轉置,為 3° 作鋪墊)如果我們並不想運用 2° 中的奇技淫巧,而是希望按順序將 A、B 輸入 cuBLAS 中運算。我們可以先嚐試手工將 A、B 轉化為列優先存,儲然後再呼叫該函式,最後將輸出的 C 轉化回行優先即可。轉化過程非常簡單,如以下程式所示。
1 // 行優先轉列優先,只改變儲存方式,不改變矩陣尺寸 2 void rToC(float *a, int ha, int wa)// 輸入陣列及其行數、列數 3 { 4 int i; 5 float *temp = (float *)malloc(sizeof(float) * ha * wa);// 存放列優先儲存的臨時陣列 6 7 for (i = 0; i < ha * wa; i++) // 找出列優先的第 i 個位置對應行優先座標進行賦值 8 temp[i] = a[i / ha + i % ha * wa]; 9 for (i = 0; i < ha * wa; i++) // 覆蓋原陣列 10 a[i] = temp[i]; 11 free(temp); 12 return; 13 } 14 15 // 列優先轉行優先 16 void cToR(float *a, int ha, int wa) 17 { 18 int i; 19 float *temp = (float *)malloc(sizeof(float) * ha * wa); 20 21 for (i = 0; i < ha * wa; i++) // 找出行優先的第 i 個位置對應列優先座標進行賦值 22 temp[i] = a[i / wa + i % wa * ha]; 23 for (i = 0; i < ha * wa; i++) 24 a[i] = temp[i]; 25 free(temp); 26 return; 27 }
這樣一來,我們的呼叫過程如下(跟 1° 中引數位置一模一樣)注意上面的轉換函式中我沒有交換矩陣 a 的行數、列數,所以在下面的呼叫中仍然有 m = 2, n = 4, k = 3。
1 rToC(h_A, m, k); 2 rToC(h_B, k, n); 3 4 ...// 準備工作 5 6 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, d_A, m, d_B, k, &beta, d_C, m); 7 8 ...// 回收工作 9 10 cToR(h_CUBLAS, m, n);
過程和結果如下。注意經過 rToC處理得到的矩陣,以 A1為例,它相當於用列優先的方式(豎著走)把 A 遍歷得到的軌跡按行優先的方式存放(橫著放)。可見矩陣 A、B、C 一波三折,但是最終獲得了我們期望的結果(圖中的“列優先”和“行優先”分別代表進入和退出 cuBLAS 時發生的強制轉化,不要理解為其他意思)。
3° (使用 cuBLAS 自動轉置)有了2.5 ° 的基礎,我們就很好理解引數 cublasOperation_t transa, cublasOperation_t transb 的作用了。這兩個引數就是在問,是否要在 cuBLAS 內部完成上面的 rToC過程。如果我們選擇引數 CUBLAS_OP_T,那麼不再需要手動地將 A、B 進行轉換(但是仍需要對輸出 C 進行轉換),我們直接上程式碼。
1 cublasSgemm(handle, CUBLAS_OP_T, CUBLAS_OP_T, m, n, k, &alpha, d_A, k, d_B, n, &beta, d_C, m); 2 3 ...// 回收工作 4 5 cToR(h_CUBLAS, matrix_size.uiHC, matrix_size.uiWC);
過程和結果如下。我們發現主維數 lda、ldb 已經用上了,它們分別等於 A2、B2 的行數(在列優先儲存中矩陣的行數是維度的第一個分量,故稱主維)。其實圖中可以省略 “→ A2” 這一中間步驟,但是為了突出“轉置”的意味,我們把它畫出來,再經過CUBLAS_OP_T 處理,方便理解。最終我們獲得了期望的結果 C3 ,我們發現這種方式省去了對 A、B 的 rToC 操作,但是 C2 的 cToR 操作還是逃不掉,沒有 2° 來的巧妙。
4° 有了以上的說明,我們就可以對A、B的不同情況再加以利用,得到下面兩種組合,他們都能得到正確的結果。過程圖示就是 2.5° 和 3° 的圖進行組合,其餘不變。
1 // A轉B不轉 2 //rToC(h_A, matrix_size.uiHA,matrix_size.uiWA); 3 rToC(h_B, matrix_size.uiHB, matrix_size.uiWB); 4 5 ... 6 7 cublasSgemm(handle, CUBLAS_OP_T, CUBLAS_OP_N, m, n, k, &alpha, d_A, k, d_B, k, &beta, d_C, m); 8 9 ... 10 11 cToR(h_CUBLAS, matrix_size.uiHC, matrix_size.uiWC);
1 // B轉A不轉 2 rToC(h_A, matrix_size.uiHA,matrix_size.uiWA); 3 //rToC(h_B, matrix_size.uiHB, matrix_size.uiWB); 4 5 ... 6 7 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_T, m, n, k, &alpha, d_A, m, d_B, n, &beta, d_C, m); 8 9 ... 10 11 cToR(h_CUBLAS, matrix_size.uiHC, matrix_size.uiWC);
③ int lda, int ldb, int ldc
主維(leading dimension)。如果我們想要計算 Am×k Bk×n = Cm×n,那 m、n、k 三個引數已經固定了所有尺寸,為什麼還要一組主維引數呢?看完了上面部分我們發現,輸入的矩陣 A、B 在整個計算過程中會發生變形,包括行列優先變換和轉置變換,所以需要一組引數告訴該函式,矩陣變形後應該具有什麼樣的尺寸。參考 CUDA 的教程 CUDA Toolkit Documentation v9.0.176,對這幾個引數的說明比較到位。
當引數 transa 為 CUBLAS_OP_N 時,矩陣 A 在 cuBLAS 中的尺寸實際為 lda × k,此時要求 lda ≥ max(1, m)(否則由該函式直接報錯,輸出全零的結果);當引數 transa 為 CUBLAS_OP_T 或 CUBLAS_OP_C 時,矩陣 A 在 cuBLAS 中的尺寸實際為 lda × m,此時要求 lda ≥ max(1, k) 。
transb 為 CUBLAS_OP_N 時,B 尺寸為 ldb × n,要求 ldb ≥ max(1, k); transb 為 CUBLAS_OP_T 或 CUBLAS_OP_C 時,B尺寸為 ldb × k,此時要求 ldb ≥ max(1, n) 。
C 尺寸為 ldc × n,要求 ldc ≥ max(1, m)。
可見,是否需要該函式幫我們轉置矩陣 A 會影響 A 在函式中的儲存。而主維正是在這一過程中起作用。特別地,如果按照一般教程中的方法來呼叫該函式,三個主維引數是不用特別處理的。
上面的教程中只要求了三個主維引數的下界,為此,我嘗試調高三個引數,獲得了一些有啟發性的結果。
Ⅰ.ldb + 1
1 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, n, m, k, &alpha, d_B, n + 1, d_A, k, &beta, d_C, n);
結果和過程。注意 B1 中多出來的部分被用零來填充了,計算 C1 的時候是依其尺寸來進行的,若 C1 的尺寸上沒有加 1,則最後一行不進行計算。
Ⅱ. lda + 1
1 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, n, m, k, &alpha, d_B, n, d_A, k + 1, &beta, d_C, n);
結果和過程。注意 A1 在參與乘法時去掉了多出的行(其實應該是計算乘法時行程長 = min (B1 列數,A1 行數) = 3)。
Ⅲ. ldc + 1
1 cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, n, m, k, &alpha, d_B, n, d_A, k, &beta, d_C, n + 1);
結果和過程。注意計算出來的 C1 自動補充了零行,但是在輸出的時候連著零一起輸出,並且把最後的兩個元素擠掉了。
▶ 測試用原始碼:
1 #include <assert.h> 2 #include <helper_string.h> 3 #include <cuda_runtime.h> 4 #include <cublas_v2.h> 5 #include <helper_functions.h> 6 #include <helper_cuda.h> 7 8 // 存放各矩陣維數的結構體 9 typedef struct 10 { 11 unsigned int wa, ha, wb, hb, wc, hc; 12 } matrixSize; 13 14 // 行優先轉列優先,只改變儲存方式,不改變矩陣尺寸 15 void rToC(float *a, int ha, int wa) 16 { 17 int i; 18 float *temp = (float *)malloc(sizeof(float) * ha * wa);// 存放列優先儲存的臨時陣列 19 20 for (i = 0; i < ha * wa; i++) // 找出列優先的第 i 個位置對應行優先位置進行賦值 21 temp[i] = a[i / ha + i % ha * wa]; 22 for (i = 0; i < ha * wa; i++) // 覆蓋原陣列 23 a[i] = temp[i]; 24 free(temp); 25 return; 26 } 27 28 // 列優先轉行優先 29 void cToR(float *a, int ha, int wa) 30 { 31 int i; 32 float *temp = (float *)malloc(sizeof(float) * ha * wa); 33 34 for (i = 0; i < ha * wa; i++) // 找出列優先的第 i 個位置對應行優先位置進行賦值 35 temp[i] = a[i / wa + i % wa * ha]; 36 for (i = 0; i < ha * wa; i++) 37 a[i] = temp[i]; 38 free(temp); 39 return; 40 } 41 42 // 計算矩陣乘法部分 43 int matrixMultiply() 44 { 45 matrixSize ms; 46 ms.wa = 3; 47 ms.ha = 2; 48 ms.wb = 4;相關推薦
CUBLAS中的矩陣乘法函式詳解
關於cuBLAS庫中矩陣乘法相關的函式及其輸入輸出進行詳細討論。▶ 漲姿勢:● cuBLAS中能用於運算矩陣乘法的函式有4個,分別是 cublasSgemm(單精度實數)、cublasDgemm(雙精度實數)、cublasCgemm(單精度複數)、cublasZgemm(雙精
Oracle中的substr()函式 詳解及應用
1)substr函式格式 (俗稱:字元擷取函式) 格式1: substr(string string, int a, int b); 格式2:substr(string string, int a) ; 解釋: 格式1:  
Matlab中的stretchlim函式詳解
imadjust函式是MATLAB的一個工具箱函式,一般的語法呼叫格式為: f1=imadjust(f,[low_in high_in],[low_out high_out],gamma) (注:本文所述影象資料均為Uint8,對於Matlab,矩陣中的一個元素即是一
檔案操作中的lseek函式詳解
所有開啟的檔案都有一個當前檔案偏移量(current file offset),以下簡稱為 cfo。cfo 通常是一個非負整數,用於表明檔案開始處到檔案當前位置的位元組數。讀寫操作通常開始於 cfo,並且使 cfo 增大,增量為讀寫的位元組數。檔案被開啟時,cfo
Oracle中的instr()函式 詳解及應用
1)instr()函式的格式 (俗稱:字元查詢函式) 格式一:instr( string1, string2 ) / instr(源字串, 目標字串) 格式二:instr( string1, string2 [, start_position [, nth_appearance ] ] ) /
ES2017 中的非同步函式詳解(async function)
非同步函式中有兩個新的關鍵字async和await async 就是非同步的意思 await 就是等的意思. 暫停函式的執行, 等待非同步任務完成. 宣告非同步函式 /*使用關鍵字 asy
C++類中拷貝建構函式詳解
a. C++標準中提到“The default constructor, copy constructor and copy assignment operator, and destructor are special member functions.[Note: T
C++排序函式中cmp()比較函式詳解
整型資料比較 bool cmp(int a,int b){ return a < b; } int a[10]; sort(a,a+10,cmp); 實型資料比較
matlab中的unique函式詳解
C = unique(A):返回的是和A中一樣的值,但是沒有重複元素。產生的結果向量按升序排序。 示例: 1.篩除向量中的重複值,產生的結果按升序排列 Define a vector with a repeated value. A = [9 2 9
關於opengl中的三維矩陣平移,矩陣旋轉,推導過程理解 OpenGL計算機圖形學的一些必要矩陣運算知識 glTranslatef(x,y,z)glRotatef(angle,x,y,z)函式詳解
原文作者:aircraft 原文連結:https://www.cnblogs.com/DOMLX/p/12166896.html 為什麼引入齊次座標的變換矩陣可以表示平移呢? - Yu Mao的回答 - 知乎 https://www.zhihu.com/
linux中alarm函式和pause函式詳解例項
轉載原文:https://www.cnblogs.com/yxk529188712/p/4982401.html alarm(time);執行之後告訴核心,讓核心在time秒時間之後向該程序傳送一個定時訊號,然後該程序捕獲該訊號並處理; pause()函式使該程序暫停讓出CPU,但是該函式的暫停
關於js中的 escape() 、 encodeURI() 、 encodeURIComponent()函式詳解
首先看一下這三個函式在js版本中出現的時間 escape()  
機器學習中Logistic損失函式以及神經網路損失函式詳解
機器學習中最重要的三個部分為網路結構、損失函式、優化策略。 而其中以損失函式最難以理解,主要原因是需要較強的數學知識,其中用的最多的就是引數估計。 所謂引數估計就是:對未知引數θ進行估計時,在引數可能的取值範圍內選取,使“樣本獲得此觀測值”的概率最大的引數作為θ的估計,這樣選定的有利於”
linux中fork() 函式詳解
fork入門知識 一個程序,包括程式碼、資料和分配給程序的資源。fork()函式通過系統呼叫建立一個與原來程序幾乎完全相同的程序,也就是兩個程序可以做完全相同的事,但如果初始引數或者傳入的變數不同,兩個程序也可以做不同的事。 一個程序呼叫fork()函式後,系統先給新的程序分配資源,例如儲存資料和程式碼的
MATLAB中accumarray函式詳解
原文連結:https://blog.csdn.net/liuhuicsu/article/details/70739459?utm_source=blogxgwz0 先看看subs和val的具體內容 subs = [1 1 1; 2 1 2; 2 3 2; 2 1 2; 2
類和物件-中(6個預設函式詳解)
本文主要是對類的6個預設函式進行講解 類的預設成員函式有6個:建構函式 解構函式
關於js中的 escape() 、 encodeURI() 、 encodeURIComponent()函式詳解
首先看一下這三個函式在js版本中出現的時間 escape() javascript 1.0 encodeURI()
softmax + cross-entropy交叉熵損失函式詳解及反向傳播中的梯度求導
相關 正文 在大多數教程中, softmax 和 cross-entropy 總是一起出現, 求梯度的時候也是一起考慮. 我們來看看為什麼. 關於 softmax 和 cross-entropy 的梯度的求導過程, 已經在上面的兩篇文章中分別給出, 這裡
stl中sort函式詳解
1. sort 函式的形式: sort(first_pointer,first_pointer+n,cmp) 函式的第一個引數是陣列的首地址,一般寫上陣列名就可以,因為陣列名是一個指標
linux中fork()函式詳解(原創!!例項講解)
所以打印出結果: 0 parent 2043 3224 3225 0 child 3224 3225 0 第二步:假設父程序p3224先執行,當進入下一個迴圈時,i=1,接著執行fork,系統中又新增一個程序p3226,對於此時的父程序,p2043->p3224(當前程