1. 程式人生 > >CUBLAS中的矩陣乘法函式詳解

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_v2
16 ( 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 28
CUBLASAPI 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、B並不等於 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/

linuxalarm函式和pause函式例項

轉載原文:https://www.cnblogs.com/yxk529188712/p/4982401.html alarm(time);執行之後告訴核心,讓核心在time秒時間之後向該程序傳送一個定時訊號,然後該程序捕獲該訊號並處理; pause()函式使該程序暫停讓出CPU,但是該函式的暫停

關於js的 escape() 、 encodeURI() 、 encodeURIComponent()函式

首先看一下這三個函式在js版本中出現的時間  escape()                          

機器學習Logistic損失函式以及神經網路損失函式

機器學習中最重要的三個部分為網路結構、損失函式、優化策略。 而其中以損失函式最難以理解,主要原因是需要較強的數學知識,其中用的最多的就是引數估計。 所謂引數估計就是:對未知引數θ進行估計時,在引數可能的取值範圍內選取,使“樣本獲得此觀測值”的概率最大的引數作為θ的估計,這樣選定的有利於”

linuxfork() 函式

fork入門知識 一個程序,包括程式碼、資料和分配給程序的資源。fork()函式通過系統呼叫建立一個與原來程序幾乎完全相同的程序,也就是兩個程序可以做完全相同的事,但如果初始引數或者傳入的變數不同,兩個程序也可以做不同的事。 一個程序呼叫fork()函式後,系統先給新的程序分配資源,例如儲存資料和程式碼的

MATLABaccumarray函式

  原文連結: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 的梯度的求導過程, 已經在上面的兩篇文章中分別給出, 這裡

stlsort函式

1. sort 函式的形式: sort(first_pointer,first_pointer+n,cmp)         函式的第一個引數是陣列的首地址,一般寫上陣列名就可以,因為陣列名是一個指標

linuxfork()函式(原創!!例項講解)

    所以打印出結果:    0 parent 2043 3224 3225    0 child  3224 3225    0    第二步:假設父程序p3224先執行,當進入下一個迴圈時,i=1,接著執行fork,系統中又新增一個程序p3226,對於此時的父程序,p2043->p3224(當前程