C++矩陣乘法計算 || GPU && CPU 實現
前言
矩陣乘法運算是機器學習的基礎。比如,卷積神經網路通過矩陣化輸入資料,然後通過矩陣乘法計算獲得結果。而效能對於演算法是至關重要的事情,所以本文主要介紹c++呼叫普通的矩陣乘法庫進行計算,以及通過cuda計算矩陣乘法。C++常用cblas庫加速cpu上的矩陣乘法運算。為了將速度提高更高,GPU版本矩陣乘法運算則通過cublas庫進行操作,在cublas庫中,使用cublasSgemv()和cublasSgemm()分別進行矩陣向量間的乘法運算與矩陣矩陣間的乘法運算。本文將具體的解釋上述兩個函式的引數以及具體的應用例子。參照官方解釋
矩陣乘法函式解釋
CPU版本矩陣矩陣乘法
cblas有兩個函式實現矩陣乘法,一個是cblas_sgemm(),另一個是cblas_dgemm()兩者的不同點在於傳入引數一個是float型,一個是double型。
void cblas_sgemm(
OPENBLAS_CONST enum CBLAS_ORDER Order, // 矩陣儲存形式,行優先或者列優先
OPENBLAS_CONST enum CBLAS_TRANSPOSE TransA, // 進行矩陣乘運算前,A是否轉置
OPENBLAS_CONST enum CBLAS_TRANSPOSE TransB, // 進行矩陣運算前,B是否轉置
OPENBLAS_CONST blasint M, // A的行數
OPENBLAS_CONST blasint N, // B的列數
OPENBLAS_CONST blasint K, // A的列數 <==> B的行數
OPENBLAS_CONST double alpha, // 比例因子
OPENBLAS_CONST double *A, // A的首地址
OPENBLAS_CONST blasint lda, // A的列數,與是否轉置無關
OPENBLAS_CONST double *B, // B的首地址
OPENBLAS_CONST blasint ldb, // B的列數,與轉置無關
OPENBLAS_CONST double beta, // 比例因子
double *C, // C的首地址
OPENBLAS_CONST blasint ldc); // C的列數
- 計算結果: C = alpha * TransA(A) x TransB(B) + beta * C
其中, A為M x K矩陣, B為 K x N矩陣,C 為 M x N 矩陣
CPU版本矩陣向量乘法
void cblas_sgemv(
OPENBLAS_CONST enum CBLAS_ORDER order, // A的資料儲存型式
OPENBLAS_CONST enum CBLAS_TRANSPOSE trans, // A進行矩陣運算前,是否轉置
OPENBLAS_CONST blasint M, // 轉置後, A的行維度
OPENBLAS_CONST blasint N, // 轉置後, A的列維度
OPENBLAS_CONST float alpha, // 比例因子
OPENBLAS_CONST float *A, // A的首地址
OPENBLAS_CONST blasint lda, // A的列數,與轉置無關
OPENBLAS_CONST float *X, // X的首地址
OPENBLAS_CONST blasint incx, // X的首地址大小
OPENBLAS_CONST float beta, // 比例因子
float *Y, // Y的首地址
OPENBLAS_CONST blasint incy); // Y的列數,與轉置無關
運算式子:Y = alpha*trans(A) x X + beta * C
其中,A為M x N的矩陣,而X為N x 1向量,C為M x 1 的向量
- 可以看出,函式需要傳入的引數比較多,第一個引數是矩陣的儲存型式(可選行優先或者列優先),
GPU版本矩陣乘法
僅僅使用單精度(float)進行解釋,若想嘗試雙精度(double),可以進入官網檢視相應函式.
cublasStatus_t cublasSgemv(cublasHandle_t handle, //傳入的cublas控制代碼
cublasOperation_t trans,
int m, int n,
const float *alpha,
const float *A, int lda,
const float *x, int incx,
const float *beta,
float *y, int incy)
這個函式實現的是矩陣向量乘法運算:
y = alpha*op(A) x + beta *y
A是一個mxn維的矩陣,以列優先的形式儲存在記憶體中,x,y分別是向量,alpha,beta分別規模因子,也就是倍數關係,而對於矩陣A來說 op(A) = A 如果第二個引數 transa == CUBLAS_OP_N,如果第二個引數為transa == CUBLAS_OP_T,那麼op(A)結果是A的轉置,而如果transa==cublas_op_H,那麼op(A)的結果是A的共軛轉置.
而lda是A矩陣主軸的維度,而incx與incy分別是x,y各個元素之間的間隔。
cublasStatus_t cublasSgemm(cublasHandle_t handle, //cublas控制代碼
cublasOperation_t transa,
cublasOperation_t transb,
int m, int n, int k,
const float *alpha,
const float *A, int lda,
const float *B, int ldb,
const float *beta,
float *C, int ldc)
C = alpha * op(A)op(B) + beta*C,其中A,B,C都為矩陣,而A維度為mxk,B維度為kxn,而C的維度為mxk,lda,ldb,ldc分別是矩陣A,B,C主軸上的維度。
#例項
#include "./common/common.h"
#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
#include "cublas_v2.h"
#include <iostream>
using namespace std;
/*
* A simple example of performing matrix-vector multiplication using the cuBLAS
* library and some randomly generated inputs.
*/
/*
* M = # of rows
* N = # of columns
*/
int M = 5;
int N = 2;
/*
* Generate a vector of length N with random single-precision floating-point
* values between 0 and 100.
*/
void generate_random_vector(int N, float **outX)
{
int i;
double rMax = (double)RAND_MAX;
float *X = (float *)malloc(sizeof(float) * N);
for (i = 0; i < N; i++)
{
int r = rand();
double dr = (double)r;
X[i] = (dr / rMax) * 100.0;
}
*outX = X;
}
/*
* Generate a matrix with M rows and N columns in column-major order. The matrix
* will be filled with random single-precision floating-point values between 0
* and 100.
*/
void generate_random_dense_matrix(int M, int N, float **outA)
{
int i, j;
double rMax = (double)RAND_MAX;
float *A = (float *)malloc(sizeof(float) * M * N);
// For each column
for (j = 0; j < N; j++)
{
// For each row
for (i = 0; i < M; i++)
{
double dr = (double)rand();
A[j * M + i] = (dr / rMax) * 100.0;
}
}
*outA = A;
}
int main(int argc, char **argv)
{
int i;
float *A, *dA;
float *X, *dX;
float *Y, *dY;
float beta;
float alpha;
cublasHandle_t handle = 0;
alpha = 3.0f;
beta = 4.0f;
// Generate inputs
srand(9384);
generate_random_dense_matrix(M, N, &A);
cout<<"A: "<<endl;
for(int i = 0; i < M; i ++){
for(int j = 0; j < N; j ++){
cout << A[M * j + i] <<" ";
}
cout<< endl;
}
generate_random_vector(N, &X);
cout<<"X: "<<endl;
for(int i = 0; i < N; i ++){
cout<<X[i]<<" ";
}
cout<< endl;
generate_random_vector(M, &Y);
cout<<"Y: "<<endl;
for(int i = 0; i < M; i++){
cout<<Y[i]<<" ";
}
cout<<endl;
// Create the cuBLAS handle
CHECK_CUBLAS(cublasCreate(&handle));
// Allocate device memory
CHECK(cudaMalloc((void **)&dA, sizeof(float) * M * N));
CHECK(cudaMalloc((void **)&dX, sizeof(float) * N));
CHECK(cudaMalloc((void **)&dY, sizeof(float) * M));
// Transfer inputs to the device
CHECK_CUBLAS(cublasSetVector(N, sizeof(float), X, 1, dX, 1));
CHECK_CUBLAS(cublasSetVector(M, sizeof(float), Y, 1, dY, 1));
CHECK_CUBLAS(cublasSetMatrix(M, N, sizeof(float), A, M, dA, M));
// Execute the matrix-vector multiplication
CHECK_CUBLAS(cublasSgemv(handle, CUBLAS_OP_N, M, N, &alpha, dA, M, dX, 1,
&beta, dY, 1));
cout<< "aplha: "<<alpha<< " beta: "<<beta<<endl;
// Retrieve the output vector from the device
CHECK_CUBLAS(cublasGetVector(M, sizeof(float), dY, 1, Y, 1));
cout<<"result: "<<endl;
for (i = 0; i < M; i++)
{
printf("%2.2f\n", Y[i]);
}
printf("...\n");
free(A);
free(X);
free(Y);
CHECK(cudaFree(dA));
CHECK(cudaFree(dY));
CHECK_CUBLAS(cublasDestroy(handle));
return 0;
}
最終的輸出結果為:
A:
51.3409 68.9939
27.9576 78.9213
52.4111 97.6222
36.8833 74.3454
39.9056 4.77248
B:
82.3203 91.5174 84.3425
30.7926 37.0584 81.2048
C:
34.009 36.5475 79.0337
7.12583 76.2508 76.6652
71.0715 39.7865 79.2842
3.62919 88.8859 93.6551
78.67 60.1165 20.3579
X:
30.6251 21.6128
72.769 67.5084 61.5184 41.7629 46.4297
Y:
9481.49 7955.78 11391 8376.15 4161.5
C:
19188.7 21912.4 30114.7
14223.5 16754.9 26607.1
22245.9 25401.9 37360.8
15991.1 18747.3 27818.7
10610.7 11727.2 11341.3