CUDA學習--矩陣乘法的並行運算
1. CUDA學習步驟
- CPU實現
a*b = c
的矩陣乘法(矩陣尺寸是n*m的,n和m大於1000) - 將cpu程式碼移植到cuda。將CPU值傳入GPU,使用cuda計算,與cpu結果對比。
- 優化思路1:將矩陣分塊進行計算
- 優化思路2:使用share memory進行優化
- 優化思路3:將資料繫結在texture上
2. CPU實現的矩陣乘法
廢話不多說,直接上原始碼
/* CPUMatMultiply:CPU下矩陣乘法
* a:第一個矩陣指標,表示a[M][N]
* b:第二個矩陣指標,表示b[N][S]
* result:結果矩陣,表示為result[M][S]
*/
void CPUMatMultiply(const int * a,const int * b, int *result,const int M,const int N,const int S)
{
for (int i = 0; i < M; i++)
{
for (int j = 0; j < S; j++)
{
int index = i * S + j;
result[index] = 0;
//迴圈計算每一個元素的結果
for (int k = 0; k < N; k++)
{
result[index] += a[i * N + k] * b[k * S + j];
}
}
}
}
3. CUDA實現的矩陣乘法
下面直接進入正題,矩陣乘法的移植。
從CPU上直接移植矩陣乘法到GPU上是非常簡單的,不需要for迴圈,直接通過CUDA執行緒的id號,即threadIdx.x
和threadIdx.y
即可操作相應的資料。
gpu矩陣乘法核函式-原始碼:
/* gpuMatMultKernel:GPU下矩陣乘法核函式
* a:第一個矩陣指標,表示a[M][N]
* b:第二個矩陣指標,表示b[N][S]
* result:結果矩陣,表示result[M][S]
*/
__global__ void gpuMatMultKernel(const int *a, const int * b, int *result, const int M, const int N, const int S)
{
int threadId = (blockIdx.y * blockDim.y + threadIdx.y) * gridDim.x * blockDim.x
+ blockIdx.x * blockDim.x + threadIdx.x;
if (threadId < M * S)
{
int row = threadId / S;
int column = threadId % S;
result[threadId] = 0;
for (int i = 0; i < N; i++)
{
result[threadId] += a[row * N + i] * b[i * S + column];
}
}
}
其中,blockIdx
、blockDim
、threadIdx
、gridDim
都是CUDA的內建變數。
blockIdx
、threadIdx
:分別CUDA中執行緒塊的ID、執行緒的ID。
blockDim
、gridDim
:分別是CUDA中執行緒塊的維度,執行緒網格的維度。
if
語句是為了判斷是否是對矩陣中的資料進行操作,防止在當執行緒threadId
超過了M*S時,使用result[threadId]
出現越界行為。
後續的操作則是矩陣乘法的簡單實現。
4. 程式優化1:共享記憶體分塊運算
CUDA C支援共享記憶體。關鍵字__shared__新增到宣告中,這將使這個變數駐留在共享記憶體中。當宣告變數為shared變數時,執行緒塊中的每一個執行緒都共享這塊記憶體,使得一個執行緒塊中的多個執行緒能夠在計算上進行通訊和協作。而且,共享記憶體緩衝區駐留在物理GPU上,而不是駐留在GPU之外的系統記憶體中。因此,在訪問共享記憶體時的延遲要遠遠低於訪問普通緩衝區的延遲,提高了效率。
使用共享記憶體的核函式:
/* gpuMatMultWithSharedKernel:GPU下使用shared記憶體的矩陣乘法
* a:第一個矩陣指標,表示a[height_A][width_A]
* b:第二個矩陣指標,表示b[width_A][width_B]
* result:結果矩陣,表示result[height_A][width_B]
*/
template<int BLOCK_SIZE>
__global__ void gpuMatMultWithSharedKernel(const int *a, const int *b, int *result, const int height_A, const int width_A, const int width_B)
{
int block_x = blockIdx.x;
int block_y = blockIdx.y;
int thread_x = threadIdx.x;
int thread_y = threadIdx.y;
if ((thread_y + block_y * blockDim.y) * width_B + block_x * blockDim.x + thread_x >= height_A * width_B)
{
return;
}
const int begin_a = block_y * blockDim.y * width_A;
const int end_a = begin_a + width_A - 1;
const int step_a = blockDim.x;
const int begin_b = block_x * blockDim.x;
const int step_b = blockDim.y * width_B;
int result_temp = 0;
for (int index_a = begin_a, int index_b = begin_b;
index_a < end_a; index_a += step_a, index_b += step_b)
{
__shared__ int SubMat_A[BLOCK_SIZE][BLOCK_SIZE];
__shared__ int SubMat_B[BLOCK_SIZE][BLOCK_SIZE];
SubMat_A[thread_y][thread_x] = a[index_a + thread_y * width_A + thread_x];
SubMat_B[thread_y][thread_x] = b[index_b + thread_y * width_B + thread_x];
__syncthreads();
for (int i = 0; i < BLOCK_SIZE; i++)
{
result_temp += SubMat_A[thread_y][i] * SubMat_B[i][thread_x];
}
__syncthreads();
}
int begin_result = block_y * blockDim.y * width_B + begin_b;
result[begin_result + thread_y * width_B + thread_x] = result_temp;
}
矩陣乘法的並行運算,每次計算矩陣的一塊資料。利用共享記憶體的共享功能,每次將一塊資料儲存到共享記憶體中使得一個執行緒塊同時呼叫資料進行計算當前塊相對應得矩陣乘法結果值。
- 程式碼
__shared__ int SubMat_A
中的__shared__
宣告變數SubMat_A
為共享記憶體中儲存的變數。然後將陣列中的資料提取到變數SubMat_A
中儲存在共享記憶體。 __syncthreads()
對執行緒塊中的執行緒進行同步,確保對__shared__進行下面的操作時上面的操作已經完成。- 兩個
for
迴圈完成了當前執行緒塊對應矩陣子塊的乘法結果計算。
注意:CUDA架構將確保,除非執行緒塊中的每個執行緒都執行了__syncthreads()
,否則沒有任何執行緒能執行__syncthreads()
之後的指令。例如:
if (cacheIndex < i){
cache[cacheIndex] += cache[i];
__syncthreads();
}
此時,如果有執行緒中的cacheIndex>i
則會出現一直等待的情況。
5. 程式優化2:紋理記憶體的運用
紋理記憶體,CUDA C程式中的另一種只讀記憶體。紋理記憶體是專門為那些在記憶體訪問模式中存在大量空間區域性性的圖形應用程式而設計的。在某個計算應用程式中,這意味著一個執行緒讀取的位置可能與鄰近執行緒讀取的位置“非常接近”,如下圖所示。
從數學的角度看,這四個地址並非連續的,在一般的CPU快取模式中,這些地址將不會快取。但由於GPU紋理快取是專門為了加速這種訪問模式而設計的,因此如果在這種情況中使用紋理記憶體而不是全域性記憶體,那麼將會獲得性能提升。
使用紋理記憶體的核函式-原始碼:
/* gpuMatMultWithTextureKernel:GPU下使用texture記憶體的矩陣乘法
* result:結果矩陣,表示為result[M][S];
* M:表示為矩陣A與矩陣result的行數
* N:表示矩陣A的列數,矩陣B的行數
* S:表示矩陣B和矩陣result的列數
*/
__global__ void gpuMatMultWithTextureKernel(int * result, const int M, const int N, const int S)
{
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
int offset = x + y * blockDim.x * gridDim.x;
if (offset < M * S)
{
int a = 0, b = 0;
int temp_result = 0;
for (int i = 0; i < N; i++)
{
a = tex1Dfetch(texA, y * N + i);
b = tex1Dfetch(texB, i * S + x);
temp_result += a * b;
}
result[offset] = temp_result;
}
}
紋理記憶體的運用與普通記憶體運用時的演算法大致相同,只不過資料是在核函式中呼叫tex1Dfetch
從紋理中提取。
在使用紋理記憶體時,主要注意的是紋理記憶體的使用。
首先,需要將輸入的資料宣告為texture型別的引用。
注意,輸入的資料是什麼型別,相應的紋理也應該與之一致。並且紋理引用必須宣告為檔案作用域內的全域性變數。
//這些變數將位於GPU上
texture<int> texA;
//二維紋理引用,增加了代表維數的引數2
texture<float, 2> texB;
在為這兩個緩衝區分配了GPU記憶體後,需要通過cudaBindTexture
將這些變數繫結到記憶體緩衝區。這相當於告訴CUDA執行時兩件事:
- 我們希望將指定的緩衝區作為紋理來使用。
- 我們希望將紋理引用作為紋理的“名字”。
cudaBindTexture(NULL, texA, dev_a, desc, M * N * sizeof(int));
cudaBindTexture(NULL, texB, dev_b, desc, N * S * sizeof(int));
在繫結紋理時,CUDA執行時要求提供一個cudaChannelFormatDesc。此時,需要呼叫cudaCreateChannelDesc<int>()
。
最後,通過cudaUnbindTexture()
函式來取消紋理的繫結。
6.總結
相關推薦
CUDA學習--矩陣乘法的並行運算
1. CUDA學習步驟 CPU實現 a*b = c 的矩陣乘法(矩陣尺寸是n*m的,n和m大於1000) 將cpu程式碼移植到cuda。將CPU值傳入GPU,使用cuda計算,與cpu結果對比。 優化思路1:將矩陣分塊進行計算 優化思路2:使用share
CUDA之矩陣乘法
我們已經知道了threads/blocks在CUDA端的組織方式,接下來我們學學多維度空間下的多執行緒模型,下面以矩陣乘法為例。 1. 行優先 儲存方式 二維矩陣在記憶體中的儲存方式受到程式語言的影響,主要可以分為兩種:行優先和列優先。對於程式語言諸如C/C++/CUD
CUDA之矩陣乘法——非方陣計算
說明 A矩陣為M * N,B矩陣為N * M 程式碼 #include "device_functions.h" #include "cuda_runtime.h" #include "device_launch_parameters.h" #incl
CUDA之矩陣乘法——複數
做好矩陣乘法和轉置之後本來開心得不行的! 準備上手做個最基本的波束形成了! 突然發現希爾伯特變換完以後需要進行各種複數的運算…所以臨時補寫了一個複數乘法… 學著學著好像有點感覺了~!還是蠻有意思的。當然前提是能除錯成功。 用一句傅小姐的名言鼓勵一下“只要
機器學習 矩陣的基本運算
矩陣的基本概念 假設 aij∈R, 其中 i=1,2,...,m; j=1,2,...,n. 我們定義如下的行列式: A=⎡⎣⎢⎢⎢⎢a11a21⋮am1a12a22⋮am2⋯⋯⋯a1na2n⋮amn⎤⎦⎥⎥⎥⎥ 是一個維數為 m×n 的實數矩陣。有時候我
CUDA之矩陣乘法——globalmemory
CUDA 矩陣乘法 使用global memory 報錯 錯誤 17 error : no instance of overloaded function “cudaMalloc”
CUDA程式設計--實現並行矩陣乘法【80行程式碼】
簡述 這裡只寫了方陣之間的乘法,但是本質上都是一樣的。 我測試過100規模的方陣之間的乘法,沒有問題。 程式碼 讀取檔案data.txt 資料格式就是一個數值N,然後來連續的兩個N*N的矩陣。用空格隔開。 #include "cuda
基於Cuda的幾種並行稀疏矩陣乘法方法(一)
最近由於研究需要和興趣看了很多稀疏矩陣乘法的演算法,這方面的研究千奇百怪,研究人員真的是十八般武藝全都用上了,好吧,就讓我來說說這個東西吧,由於這個東西實在方法太多,所以請容許我一節一節地去完善。 1、儲存方式 稀疏矩陣的儲存方式真的非常多,也各
學習筆記:矩陣的基本運算的實現
for int size data stdin mat 轉置 span font 2017-09-05 21:33:33 writer:pprp 昨天開始就上課了,沒有整天整天的時間去編代碼了,充分抓住每天晚上的時間吧, 今天下午預習了一下線性代數中矩陣最基本的運算,今晚就
cuda編程-矩陣乘法(1)
return mac cpu ims iostream oba 簡單的 oid memory 本方法采用簡單的單線程計算每組行和列乘加運算 代碼如下: #include <stdio.h> #include <stdlib.h> #include
機器學習之數學系列(一)矩陣與矩陣乘法
1.對於矩陣的認識應當把它看成是多個向量的排列表或把矩陣看成行向量,該行向量中的每個元素都是一個列向量,即矩陣是複合行向量。如下圖所示。 2.對於下面這個矩陣的乘法有兩種看法: (1)矩陣將向量[b1,b2,b3].T進行了運動變換,這種變換可以是同空間內變換,也可以是不同空間間的變換;
神經網路高效能運算 卷積計算優化 openblas GEMM 矩陣乘法優化 ncnn mobileNet-ssd shueezeNet-ssd
HighPerformanceComputing 高效能運算(High performance computing, 縮寫HPC) 指通常使用很多處理器(作為單個機器的一部分) 或者某一叢集中組織的幾臺計算機(作為單個計 算資源操作)的計算系統和環境。 有許多型別的HP
CUDA學習筆記(2)- 執行緒並行和塊並行
1. 獲取顯示卡裝置資訊 有些顯示卡支援CUDA有些不支援,那麼如何確定主機的顯示卡裝置是否支援CUDA呢。可以使用下面的函式獲取顯示卡的相關資訊。 cudaError_t cudaGetDeviceCount(int *count) 獲取支援CUD
CUDA學習筆記(LESSON7)——常用優化策略&動態並行化
常用優化策略 下面讓我們來看看一些常用的優化策略,這些策略我們之前已經談過,現在只是對它進行一個總結。 資料佈局變換(Data layout transformation) 第一部分就是我們之前所說的coalescing存取模式,當相鄰執行緒訪問記憶體的相鄰位置的時
CUDA 學習筆記 (二) 【Chapter4 CUDA並行程式設計】
Capter4 CUDA並行程式設計 前面我們看到將一個標準C函式放到GPU裝置上執行是很容易的。只需要在函式定義前面加上 __globle__ 修飾符,並通過一種特殊的尖括號語法來呼叫它,就可以在GPU上執行這個函式。然而,前面的示例只調用了一個和函式,並且該函式在GPU上以序列方
機器學習中的數學系列(一)矩陣與矩陣乘法
1.對於矩陣的認識應當把它看成是多個向量的排列表或把矩陣看成行向量,該行向量中的每個元素都是一個列向量,即矩陣是複合行向量。如下圖所示。 2.對於下面這個矩陣的乘法有兩種看法: (1)矩陣將向量[b1,b2,b3].T進行了運動變換,這種變換可以是同空間內變
AI學習之路(19)TensorFlow裡的矩陣乘法
如果對矩陣的知識有點遺忘,有點陌生,有點想不起來,請先看看這個網頁:基礎知識已經補過了,就直接來使用TF的矩陣乘法了。tf.matmul(a, b, transpose_a=False, transpose_b=False, adjoint_a=False, adjoint_b
python中矩陣的基本運算學習記錄
矩陣運算: NumPy系統是Python的一種開源的數值計算擴充套件。這種工具可用來儲存和處理大型矩陣,比Python自身的巢狀列表(nested list structure)結構要高效的多(該
【學習筆記】快速冪+矩陣+矩陣乘法+矩陣快速冪
今天晚上我學習了矩陣 1、快速冪 通常,我們要算bpmodkbpmodk是這麼算的: ans := 1; for i := 1 to p do ans := ans * b mod k;
大矩陣乘法運算map reduce實現思路
實現思路: 儲存: 大矩陣很多都是稀疏矩陣,並且有可能有上百萬的行和上百萬的列。 那麼矩陣可以存在類似HBase面向列的分散式資料庫中。 假設HTable中有兩個表A和表B分別儲存兩個巨型矩陣a和b。表A和表B都是隻有一個列族。列名都是1開始計數。 那麼表A和表B所儲存的矩