More Related Content Similar to GPGPU Seminar (GPU Accelerated Libraries, 1 of 3, cuBLAS) (20) GPGPU Seminar (GPU Accelerated Libraries, 1 of 3, cuBLAS) 17. 共役勾配法のアルゴリズム
2015/10/14GPGPU講習会17
連立一次方程式Ax=bに対する共役勾配法
Ap 係数行列Aと
ベクトルpの積
( , ) ベクトル同士の
内積
Compute r(0)=b−Ax(0). Set p(0)=0,c2
(0)=0.
For k=1,…, until ||r||/||b|| < , Do
p(k) = r(k)+c2
(k−1)p(k−1)
c1
(k) = (r(k), r(k))/(p(k), Ap(k))
x(k+1) = x(k)+c1
(k)p(k)
r(k+1) = r(k)−c1
(k)Ap(k)
c2
(k) = (r(k+1), r(k+1))/{c1
(k)(p(k), Ap(k))}
EndDo
A 係数行列
x 解ベクトル
b 右辺ベクトル
r 残差ベクトル
p 補助ベクトル
||・|| l2−ノルム
19. 共役勾配法のバリエーション
2015/10/14GPGPU講習会19
安定化双共役勾配法(Bi‐CGSTAB法)
非対称行列に対応
Compute r(0)=b−Ax(0). Set p(0)=0,c2
(0)=0, r*=r(0).
For k=1,…, until ||r||/||b|| < , Do
p(k) = r(k)+c2
(k−1)(p(k−1)−c3
(k−1)Ap(k−1))
c1
(k) = (r*, r(k))/(r*, Ap(k))
t(k) = r(k)−c1
(k)Ap(k)
c3
(k) = (At(k), t(k))/(At(k), At(k))
x(k+1) = x(k)+c1
(k)p(k)+c3
(k)t(k)
r(k+1) = r(k)−c3
(k)At(k)
c2
(k) = (r*, r(k+1))/{c3
(k)(r*, Ap(k))}
EndDo
r* 疑似残差
t 補助ベクトル
21. CPUプログラム(制御部分)
2015/10/14GPGPU講習会21
#include<stdlib.h>
#include<stdio.h>
#include<math.h>
int main(void){
int N = 1 << 10; //未知数の数210
const double err_tol = 1e‐9; //許容誤差
const int max_ite = 1<<20;//反復回数の上限
double *x; //近似解ベクトル
double *b; //右辺ベクトル
double *A; //係数行列
double *sol; //厳密解
double *r, rr; //残差ベクトル, 残差の内積
double *p, *Ax; //補助ベクトル,行列ベクトル積
double c1, c2, dot; //計算に使う係数
int i, k;
//メモリの確保
A = (double *)malloc(sizeof(double)*N*N);
x = (double *)malloc(sizeof(double)*N);
b = (double *)malloc(sizeof(double)*N);
sol= (double *)malloc(sizeof(double)*N);
r = (double *)malloc(sizeof(double)*N);
p = (double *)malloc(sizeof(double)*N);
Ax = (double *)malloc(sizeof(double)*N);
for (i = 0; i < N; i++){
sol[i] = (double)i; //厳密解を設定
x[i] = 0.0; //近似解を0で初期化
}
//係数行列Aの生成
setTridiagonalMatrix(A, N);
//右辺ベクトルbの生成
setRightHandSideVector(b, A, sol, N);
// :
// ここで共役勾配法を実行
// :
//確保したメモリを解放
free(x);
free(b);
free(A);
free(sol);
free(r);
free(p);
free(Ax);
} cg_cpu.c
24. CPUプログラム(共役勾配法部分)
2015/10/14GPGPU講習会24
//残差ベクトルの計算 r(0)=b−Ax(0)
computeResidual(r, b, A, x, N);
//残差ベクトルの内積を計算
rr = innerProduct(r, r, N);
k = 1;
while(rr>err_tol*err_tol && k<=max_ite)
{
if (k == 1){
//p(k) = r(k)+c2
(k−1)p(k−1) c2とpが0のためp(k) = r(k)
copy(p, r, N);
}
else{
c2 = rr / (c1*dot);
//p(k) = r(k)+c2
(k−1)p(k−1)
computeVectorAdd(p, c2, r, 1.0, N);
}
//(p(k), Ap(k))を計算
//行列ベクトル積Apを実行し,結果とpの内積
computeMxV(Ax, A, p, N);
dot = innerProduct(p, Ax, N);
c1 = rr / dot;
//x(k+1) = x(k)+c1
(k)p(k)
//r(k+1) = r(k)−c1
(k)Ap(k)
computeVectorAdd(x, 1.0, p, c1, N);
computeVectorAdd(r, 1.0, Ax,‐c1, N);
//残差ベクトルの内積を計算
rr = innerProduct(r, r, N);
k++;
}
/*
Compute r(0)=b−Ax(0). Set p(0)=0,c2
(0)=0.
For k=1,…, until ||r||/||b|| < , Do
p(k) = r(k)+c2
(k−1)p(k−1)
c1
(k) = (r(k), r(k))/(p(k), Ap(k))
x(k+1) = x(k)+c1
(k)p(k)
r(k+1) = r(k)−c1
(k)Ap(k)
c2
(k) = (r(k+1), r(k+1))/{c1
(k)(p(k), Ap(k))}
EndDo
*/ cg_cpu.c
26. CPUプログラム(共役勾配法内の関数)
2015/10/14GPGPU講習会26
//値のコピー(単純代入)p(k) = r(k)
void copy(double *lhs, double *rhs, int N){
int i;
for (i = 0; i < N; i++){
lhs[i] = rhs[i];
}
}
//ベクトル和y(k) = ax(k) + by(k)の計算
void computeVectorAdd(
double *y, const double b,
double *x, const double a,
int N){
int i;
for (i = 0; i < N; i++){
y[i] = a*x[i] + b*y[i];
}
}
//行列-ベクトル積Axの計算
void computeMxV(double *Ax,
double *A, double *x, int N){
int i,j;
for (i = 0; i < N; i++){
Ax[i] = 0.0;
for (j = 0; j < N; j++){
Ax[i] += A[i + N*j] * x[j];
}
}
}
cg_cpu.c
34. cublasHandle_t handle;
cublasCreate(&handle);
cublasSetMatrix
(N, N, sizeof(float), A, N, d_A, N);
cublasSetVector
(N, sizeof(float), B, 1, d_B, 1);
cublasSetVector
(N, sizeof(float), C, 1, d_C, 1);
cublasSgemv
(handle, CUBLAS_OP_N, N, N, &alpha,
d_A, N, d_B, 1, &beta, d_C, 1);
cublasGetVector
(N, sizeof(float), d_C, 1, C, 1);
for(i=0;i<N;i++){
printf("C[%3d] = %f ¥n",j, C[j]);
}
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
cublasDestroy(handle);
return 0;
}
cuBLASによる行列-ベクトル積
GPGPU講習会34 2015/10/14
cublas_gemv.cu
36. cuBLASによる行列-ベクトル積
2015/10/14GPGPU講習会36
データ転送
cublasSetMatrix(N,N,sizeof(float),A,N,d_A,N);
cublasSetVector(N,sizeof(float),B,1,d_B,1);
cublasSetVector(N,sizeof(float),C,1,d_C,1);
関数内部でcudaMemcpyが呼ばれる それぞれB,d_B,
C, d_Cにアクセス
する際のストライド
行列(A, d_A)の第1次元の要素数
37. cuBLASによる行列-ベクトル積
2015/10/14GPGPU講習会37
演算を行う関数の呼び出し
cublasSgemv(handle, CUBLAS_OP_N, N, N, &alpha,
d_A, N, d_B, 1, &beta, d_C, 1);
CUBLAS_OP_N 行列Aに対する操作
cublasOperation_t型
CUBLAS_OP_N OP(A)=A 処理しない
CUBLAS_OP_T OP(A)=AT 転置
CUBLAS_OP_C OP(A)=AH 共役転置
C[N] = OP(A[N][N])B[N] + C[N]
d_B, d_C
にアクセス
する際のス
トライド
行列d_Aの第1次元の要素数
40. cuBLASを使う際の注意点
2015/10/14GPGPU講習会40
C言語におけるA[i][j]のメモリ上の配置
0/256,0/256,0/256・・・1/256,1/256,1/256・・・2/256,2/256,2/256 ・・・
FortranにおけるA(i,j)のメモリ上の配置
0/256,1/256,2/256・・・0/256,1/256,2/256・・・0/256,1/256,2/256・・・
256/2256/2256/2
256/1256/1256/1
256/0256/0256/0
j
i
41. cuBLASを使う際の注意点
2015/10/14GPGPU講習会41
C言語におけるA[i][j]のメモリ上の配置
0/256,0/256,0/256・・・1/256,1/256,1/256・・・2/256,2/256,2/256 ・・・
FortranにおけるA(i,j)のメモリ上の配置
0/256,1/256,2/256・・・0/256,1/256,2/256・・・0/256,1/256,2/256・・・
256/2256/2256/2
256/1256/1256/1
256/0256/0256/0
j
i
42. cuBLASを使う際の注意点
2015/10/14GPGPU講習会42
C言語におけるA[i][j]のメモリ上の配置
0/256,0/256,0/256・・・1/256,1/256,1/256・・・2/256,2/256,2/256 ・・・
FortranにおけるA(i,j)のメモリ上の配置 ←cuBLAS
0/256,1/256,2/256・・・0/256,1/256,2/256・・・0/256,1/256,2/256・・・
256/2256/2256/2
256/1256/1256/1
256/0256/0256/0
j
i
46. CPUプログラム(共役勾配法)(再掲)
2015/10/14GPGPU講習会46
//残差ベクトルの計算 r(0)=b−Ax(0)
computeResidual(r, b, A, x, N);
//残差ベクトルの内積を計算
rr = innerProduct(r, r, N);
k = 1;
while(rr>err_tol*err_tol && k<=max_ite)
{
if (k == 1){
//p(k) = r(k)+c2
(k−1)p(k−1) c2とpが0のためp(k) = r(k)
copy(p, r, N);
}
else{
c2 = rr / (c1*dot);
//p(k) = r(k)+c2
(k−1)p(k−1)
computeVectorAdd(p, c2, r, 1.0, N);
}
//(p(k), Ap(k))を計算
//行列ベクトル積Apを実行し,結果とpの内積
computeMxV(Ax, A, p, N);
dot = innerProduct(p, Ax, N);
c1 = rr / dot;
//x(k+1) = x(k)+c1
(k)p(k)
//r(k+1) = r(k)−c1
(k)Ap(k)
computeVectorAdd(x, 1.0, p, c1, N);
computeVectorAdd(r, 1.0, Ax,‐c1, N);
//残差ベクトルの内積を計算
rr = innerProduct(r, r, N);
k++;
}
/*
Compute r(0)=b−Ax(0). Set p(0)=0,c2
(0)=0.
For k=1,…, until ||r||/||b|| < , Do
p(k) = r(k)+c2
(k−1)p(k−1)
c1
(k) = (r(k), r(k))/(p(k), Ap(k))
x(k+1) = x(k)+c1
(k)p(k)
r(k+1) = r(k)−c1
(k)Ap(k)
c2
(k) = (r(k+1), r(k+1))/{c1
(k)(p(k), Ap(k))}
EndDo
*/ cg_cpu.c
49. r(0)=b−Ax(0)の置き換え
2015/10/14GPGPU講習会49
cublasDgemv
cublasDgemv(handle, CUBLAS_OP_N, N, N, &one,
d_A, N, d_x, 1, &zero, d_Ax, 1);
Ax[N] = one*A[N][N]x[N] + zero*Ax[N]
cublasDcopy
cublasDcopy(handle, N, d_b, 1, d_r, 1);
r[N] = b[N]
cublasDaxpy
cublasDaxpy(handle, N, &minusOne,d_Ax,1 ,d_r,1);
r[N] = r[N] + minusOne*Ax[N]
one =1.0;
zero=0.0;
minusOne =‐1.0;
必ず変数のアドレスを指定
51. p(k) = r(k)+c2
(k−1)p(k−1)の置き換え
2015/10/14GPGPU講習会51
k=1
c2=0のため単純な代入 p(k) = r(m)
cublasDcopy
k>1
ベクトル和を計算 p(k) = r(k)+c2
(k−1)p(k−1)
またもや難題
cublasDaxpyでは足されるベクトル(p)への操作はできない
2段階に分けて処理を実行
pの値をc2倍 p(k)=c2×p(k−1)
cublasDscal
ベクトル和を計算 p(k) = p(k)+1×r(k)
cublasDaxpy
52. p(k) = r(k)+c2
(k−1)p(k−1)の置き換え
2015/10/14GPGPU講習会52
cublasDscal
cublasDscal(handle, N, &c2, d_p, 1);
p[N] = c2*p[N]
cublasDaxpy
cublasDaxpy(handle, N, &one,d_r,1 ,d_p,1);
p[N] = one*r[N] + p[N]
54. 係数c1の計算
2015/10/14GPGPU講習会54
cublasDgemv
cublasDgemv(handle, CUBLAS_OP_N, N, N, &one,
d_A, N, d_p, 1, &zero, d_Ax, 1);
Ax[N] = one*OP(A[N][N])p[N] + zero*Ax[N]
cublasDdot
cublasDdot(handle, N, d_p, 1, d_Ax, 1, &dot);
dot = (p[N], Ax[N])
c1 = rr/dot
55. 解ベクトルx(k)と残差ベクトルr(k)を更新
2015/10/14GPGPU講習会55
ベクトル和を計算 x(k+1) = x(k)+c1
(k)×p(k)
cublasDaxpy(handle, N, &c1,d_p,1 ,d_x,1);
x[N] = c1*p[N] + x[N]
ベクトル和を計算 r(k+1) = r(k)−c1
(k)×Ap(k)
既に計算した行列-ベクトル積(Ax)を利用
cublasDaxpy(handle, N, &minusC1, d_Ax,1 ,d_r,1);
r[N] = minusC1*Ax[N] + r[N]
minusC1 =‐c1;
57. GPUプログラム(制御部分)
2015/10/14GPGPU講習会57
#include <stdlib.h>
#include <stdio.h>
#include <cublas_v2.h>
int main(void){
int N = 1 << 10; //未知数の数210
const double err_tol = 1e‐9; //許容誤差
const int max_ite = 1<<20;//反復回数の上限
double *x; //近似解ベクトル
double *b; //右辺ベクトル
double *A; //係数行列
double *sol; //厳密解
//GPU用変数
double *d_x;
double *d_b;
double *d_A;
double *d_r, rr;
double *d_p, *d_Ax;
double c1, c2, minusC1, dot;
//cublasで利用する定数
const double one = 1.0;
const double zero = 0.0;
const double minusOne = ‐1.0;
int i, k;
A = (double *)malloc(sizeof(double)*N*N);
x = (double *)malloc(sizeof(double)*N);
b = (double *)malloc(sizeof(double)*N);
sol= (double *)malloc(sizeof(double)*N);
for (i = 0; i < N; i++){
sol[i] = (double)i; //厳密解を設定
x[i] = 0.0; //近似解を0で初期化
}
//係数行列Aの生成
setTridiagonalMatrix(A, N);
//右辺ベクトルbの生成
setRightHandSideVector(b, A, sol, N);
cg_cublas.cu
58. GPUプログラム(制御部分)
2015/10/14GPGPU講習会58
//cuBLASハンドルの生成
cublasHandle_t cublasHandle = 0;
cublasStatus_t cublasStatus;
cublasStatus=cublasCreate(&cublasHandle);
cudaMalloc
((void **)&d_A, N*N*sizeof(double));
cudaMalloc
((void **)&d_x, N *sizeof(double));
cudaMalloc
((void **)&d_b, N *sizeof(double));
cudaMalloc
((void **)&d_r, N *sizeof(double));
cudaMalloc
((void **)&d_p, N *sizeof(double));
cudaMalloc
((void **)&d_Ax,N *sizeof(double));
cublasSetMatrix
(N, N, sizeof(double), A, N, d_A, N);
cublasSetVector
(N, sizeof(double), x, 1, d_x, 1);
cublasSetVector
(N, sizeof(double), b, 1, d_b, 1);
// :
// ここで共役勾配法を実行
// :
//ハンドルの破棄
cublasDestroy(cublasHandle);
//確保したメモリを解放
free(x);
free(b);
free(A);
free(sol);
cudaFree(d_x);
cudaFree(d_b);
cudaFree(d_A);
cudaFree(d_r);
cudaFree(d_p);
cudaFree(d_Ax);
} cg_cublas.cu
59. GPUプログラム(共役勾配法部分)
2015/10/14GPGPU講習会59
//残差ベクトルの計算 r(0)=b−Ax(0)
cublasDgemv(cublasHandle, CUBLAS_OP_N, N, N, &one, d_A, N, d_x, 1, &zero, d_Ax, 1);
cublasDcopy(cublasHandle, N, d_b, 1, d_r, 1);
cublasDaxpy(cublasHandle, N, &minusOne, d_Ax, 1, d_r, 1);
//残差ベクトルの内積を計算
cublasDdot(cublasHandle, N, d_r, 1, d_r, 1, &rr);
k = 1;
while(rr>err_tol*err_tol && k<=max_ite)
{
if (k == 1){
//p(k) = r(k)+c2
(k−1)p(k−1) c2とpが0のためp(k) = r(k)
cublasDcopy(cublasHandle, N, d_r, 1, d_p, 1);
}
else{
c2 = rr / (c1*dot);
//p(k) = r(k)+c2
(k−1)p(k−1)
cublasDscal(cublasHandle, N, &c2, d_p, 1);
cublasDaxpy(cublasHandle, N, &one, d_r, 1, d_p, 1);
}
cg_cublas.cu
60. GPUプログラム(共役勾配法部分)
2015/10/14GPGPU講習会60
//(p(k), Ap(k))を計算
//行列ベクトル積Apを実行し,結果とpの内積
cublasDgemv(cublasHandle, CUBLAS_OP_N, N, N, &one, d_A, N, d_p, 1, &zero, d_Ax, 1);
cublasDdot(cublasHandle, N, d_p, 1, d_Ax, 1, &dot);
c1 = rr / dot;
//x(k+1) = x(k)+c1
(k)p(k)
//r(k+1) = r(k)−c1
(k)Ap(k)
cublasDaxpy(cublasHandle, N, &c1, d_p, 1, d_x, 1);
cublasDaxpy(cublasHandle, N, &minusC1, d_Ax, 1, d_r, 1);
//残差ベクトルの内積を計算
cublasDdot(cublasHandle, N, d_r, 1, d_r, 1, &rr);
cudaDeviceSynchronize();
k++;
}
//計算結果をGPUからCPUへコピー
cublasGetVector(N, sizeof(double), d_x, 1, x, 1);
cg_cublas.cu
63. 改善点
2015/10/14GPGPU講習会63
ベクトルの次元214は多いか?
少なすぎて話にならない
現在のシミュレーションでは220~230(数千万から数億)
行列の格納形式
無駄が多い
配列要素数に対する非ゼロ要素の割合
(2+(N‐2)*3+2) / N2
N=214のとき非ゼロ要素の割合は0.02%
改善点
行列の非ゼロ要素のみを格納してメモリ利用効率を上げる*
cuSPARSEを利用
41
141
141
14
0
0
*cuBLASにも帯行列‐ベクトル積を計算する関数があるので,
それを使えば0要素を保持しなくてもよくなる.調査してみよう.
71. A. cuBLAS関数のリファレンス
2015/10/14GPGPU講習会71
cublasDdot
result <‐ (x[n]·y[n])
cublasStatus_t
cublasDdot (cublasHandle_t handle,
int n,
const double *x,
int incx,
const double *y,
int incy,
double *result)
ハンドル
ベクトルの要素数
一つ目のベクトルのアドレス
一つ目のベクトルのストライド
二つ目のベクトルのアドレス
二つ目のベクトルのストライド
内積の結果を格納する変数
のアドレス
73. A. cuBLAS関数のリファレンス
2015/10/14GPGPU講習会73
cublasDaxpy
y[n]<‐alpha*x[n] + y[n]
cublasStatus_t
cublasDaxpy(cublasHandle_t handle,
int n,
const double *alpha,
const double *x,
int incx,
double *y,
int incy)
ハンドル
ベクトルの要素数
かける数を代入した変数の
アドレス
加算するベクトルのアドレス
加算するベクトルのストライド
加算されるベクトルのアドレス
加算されるベクトルのストライド
74. A. cuBLAS関数のリファレンス
2015/10/14GPGPU講習会74
cublasDgemv
y[m] = alpha*OP(A[m][n])x[n] + beta*y[m]
cublasStatus_t
cublasDgemv(cublasHandle_t handle,
cublasOperation_t trans,
int m, int n,
const double *alpha,
const double *A, int lda,
const double *x, int incx,
const double *beta,
double *y, int incy)
ハンドル
行列に対する操作
行列の行と列の要素数
かける数を代入した
変数のアドレス
行列と1次元の要素数
行列にかけるベクトル
のアドレスとストライド
計算結果のベクトルにかける
数を代入した変数のアドレス
計算結果のベクトルの
アドレスとストライド
76. B. 実行時間の測定
CPUで時間計測はできない
clock関数を使うと実行時間を正しく測定できない
CUDAで用意されている関数を使って時間を計測
2015/10/14GPGPU講習会76
初期化の指示
初期化
カーネルの実行指示
カーネルを実行
結果の取得
実行結果をコピー
time
CPUとGPUは非同期
CPUは別の処理を実行可能
必要なデータのコピー
メモリに書込
78. D. CUDA Eventで時間測定*
GPUへの指示をEventと呼び,そのEventが発生した時間の差
から時間を測定
準備
どのEventを取り扱うかを宣言
kernelの呼び出し(startイベント)と実行終了(stopイベント)
イベントを取り扱う変数の宣言と利用準備
*http://gpu.fixstars.com/index.php/
CUDA_Eventで時間計測
2015/10/14GPGPU講習会78
cudaEvent_t start,stop; ・・・イベントを取り扱う変数
float elapsed_time_ms = 0.0f; ・・・経過時間保存用
cudaEventCreate(&start); ・・・イベントのクリエイト
cudaEventCreate(&stop);
79. D. CUDA Eventで時間測定
Event発生時間を記録
Eventの同期
startとstopの時間を正しく記録し終わっていることを保証
2015/10/14GPGPU講習会79
cudaEventRecord(start, 0);
//ここでkernelを実行
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
80. D. CUDA Eventで時間測定
Eventが発生した時間差を計算(ミリ秒単位)
Eventの時間を記録した変数を破棄
2015/10/14GPGPU講習会80
cudaEventElapsedTime(&elapsed_time_ms, start,stop);
cudaEventDestroy(start);
cudaEventDestroy(stop);
81. D. CUDA Eventで時間測定
int main(void){
float *a,*b,*c;
//イベント記録の準備
cudaMalloc((void **)&a, Nbytes);
cudaMalloc((void **)&b, Nbytes);
cudaMalloc((void **)&c, Nbytes);
init<<< , >>>(a,b,c);
//イベント発生時間の記録
add<<< , >>>(a,b,c);
//イベント発生時間の記録
//イベントの同期
//イベント間の時間差を計算
printf("%f¥n", elapsed_time_ms);
//イベントの破棄
return 0;
}
2015/10/14GPGPU講習会81