第9回 行列計算(行列-行列積)
長岡技術科学大学 電気電子情報工学専攻 出川智啓
今回の内容
2015/06/10GPGPU実践プログラミング2
 行列-行列積
 CUDAによる行列-行列積の実装
 2次元的な並列化
 スレッド数の取り方による性能の変化
 共有メモリの利用
行列-行列積C=AB
 
k
jkkiji BAC ,,,
































NLL
N
LMM
L
NMM
N
BB
BB
AA
AA
CC
CC
,1,
,11,1
,1,
,11,1
,1,
,11,1









2015/06/10GPGPU実践プログラミング3
行列-行列積C=AB
2015/06/10GPGPU実践プログラミング4
 計算量は行列サイズの3乗に比例
 頻出しないが,出てくると確実にボトルネック化
 様々な高速化・並列化の方法が存在
 並列計算のテスト
 計算環境の評価
 アルゴリズムの評価
i
k
i
j
k
j
[A] [C]
[B]
配列アクセスのイメージ
2015/06/10GPGPU実践プログラミング5
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
C[i][j] = 0;
for(k=0;k<SIZE;k++){
C[i][j] += A[i][k]*B[k][j];
}
}
}
1次元配列での行列の表現
2015/06/10GPGPU実践プログラミング6
 1次元配列C[]を宣言し,整数型変数i,jを使って任意
の要素にアクセス
 行方向がi(第1次元),列方向がj(第2次元)
 画像処理など一般的な用途とは向きが異なる
 水平(x,行)方向がi,垂直(y,列)方向がj
 2次元配列の場合
 1次元配列の場合
C[1][2] = 9
C[_______] = 9
SIZE=6
1*6 + 2
C[i][j]  C[i*SIZE+j]
メモリアドレスが連続
i
j
1
7
2
8
3
9
4
10
5 6
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define SIZE 4096
#define Bytes (SIZE*SIZE*sizeof(float))
#define MILLISEC_PER_SEC 1000
#define FLOPS_TO_GFLOPS  1e‐9
void matmul(float *, float *, float *);
int main(void){
float *hA, *hB, *hC;
int i,j,k;
clock_t start_c, stop_c;
float time_s,time_ms;
float gflops;
hA = (float *)malloc(Bytes);
hB = (float *)malloc(Bytes);
hC = (float *)malloc(Bytes);
for(i=0;i<SIZE;i++){
for(k=0;k<SIZE;k++){
hA[i*SIZE + k]=(float)(i+1)*0.1f;
}
}
for(k=0;k<SIZE;k++){
for(j=0;j<SIZE;j++){
hB[k*SIZE + j]=(float)(j+1)*0.1f;
}
}
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
hC[i*SIZE + j] = 0.0f;
}
}
CPUプログラム
2015/06/10GPGPU実践プログラミング7
matmul.c
start_c = clock();
matmul(hA,hB,hC);
stop_c = clock();
time_s = (stop_c‐start_c)
/(float)CLOCKS_PER_SEC;
time_ms = time_s*MILLISEC_PER_SEC;
gflops = 2.0*SIZE*SIZE*SIZE/time_s
* FLOPS_TO_GFLOPS;
printf("%f ms¥n",time_ms);
printf("%f GFLOPS¥n",gflops);
return 0;
}
//行列-行列積
void matmul(float *A,float *B,float *C){
int i,j,k;
for(i=0; i<SIZE; i++){
for(j=0; j<SIZE; j++){
for(k=0; k<SIZE; k++){
C[i*SIZE+j] += 
A[i*SIZE+k]*B[k*SIZE+j];
}
}
}
}
CPUプログラム(続き)
2015/06/10GPGPU実践プログラミング8
matmul.c
行列-行列積の計算量
2015/06/10GPGPU実践プログラミング9
 計算能力FLOPS[flop/s]
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
C[i][j] = 0;
for(k=0;k<SIZE;k++){
C[i][j] += A[i][k]*B[k][j];
}
}
}
FLOPS=SIZE×SIZE×SIZE×2/実行時間
CPUプログラムの性能評価
 行列サイズ _____________2
 実行時間 _____________ ms
 実効性能 _____________ FLOPS
2015/06/10GPGPU実践プログラミング10
4096
≈600,000*
0.2G
*行列サイズを変えた時の実行時間の変化から推定
2562 30ms,1.2G
5122 350ms,0.77G
10242 8670ms,0.25G
20482 72680ms,0.24G
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
C[i*SIZE j] = 0;
for(k=0;k<SIZE;k++){
C[i*SIZE+j] 
+= A[i*SIZE+k]*B[k*SIZE+j];
}
}
}
GPUプログラム(1スレッド版)
i
k
i
j
k
j
[A] [C]
[B]
2015/06/10GPGPU実践プログラミング11
2次元的な配列アクセスの優先方向
2015/06/10GPGPU実践プログラミング12
 2次元配列の場合
in[][],out[][]
Ny
Nx
i
j
for(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)
out[i][j]=in[i][j];
for(j=0;j<Ny;j++)
for(i=0;i<Nx;i++)
out[i][j]=in[i][j];
2次元的な配列アクセスの優先方向
2015/06/10GPGPU実践プログラミング13
 2次元配列の場合
for(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)
out[i][j]=in[i][j];
in[][],out[][]
for(j=0;j<Ny;j++)
for(i=0;i<Nx;i++)
out[i][j]=in[i][j];
Ny
Nx
i
j
2次元的な配列アクセスの優先方向
2015/06/10GPGPU実践プログラミング14
 2次元配列の1次元配列的表現
for(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)
out[i][j]=in[i][j];
in[],out[]
for(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)
out[i*Ny+j]=
in[i*Ny+j];
Ny
Nx
i
j
2次元的な配列アクセスの優先方向
2015/06/10GPGPU実践プログラミング15
 CUDAで2次元的に並列化してアクセスする場合
i = blockIdx.x*blockDim.x
+ threadIdx.x;
j = blockIdx.y*blockDim.y
+ threadIdx.y;
out[j*Nx+i]=in[j*Nx+i];
in[],out[]
for(i=0;i<Nx;i++)
for(j=0;j<Ny;j++)
out[i*Ny+j]=
in[i*Ny+j];
threadIdx.y
threadIdx.x
Ny
Nx
i
j
for(j=0;j<SIZE;j++){     //ループを入れ替え
for(i=0;i<SIZE;i++){ //
C[i+SIZE*j] = 0;
for(k=0;k<SIZE;k++){
C[i+SIZE*j] 
+= A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
GPUプログラム(1スレッド版)
i
k
i
j
k
j
[A] [C]
[B]
2015/06/10GPGPU実践プログラミング16
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define SIZE 4096
#define Bytes (SIZE*SIZE*sizeof(float))
#define MILLISEC_PER_SEC 1000
#define FLOPS_TO_GFLOPS  1e‐9
void matmul(float *, float *, float *);
int main(void){
float *hA, *hB, *hC;
int i,j,k;
clock_t start_c, stop_c;
float time_s,time_ms;
float gflops;
hA = (float *)malloc(Bytes);
hB = (float *)malloc(Bytes);
hC = (float *)malloc(Bytes);
for(k=0;k<SIZE;k++){  //ループを入れ替え
for(i=0;i<SIZE;i++){//
hA[i+SIZE*k]=(float)(k+1)*0.1f;
}
}
for(j=0;j<SIZE;j++){  //ループを入れ替え
for(k=0;k<SIZE;k++){//
hB[k+SIZE*j]=(float)(k+1)*0.1f;
}
}
for(j=0;j<SIZE;j++){  //ループを入れ替え
for(i=0;i<SIZE;i++){//
hC[i+SIZE*j] = 0.0f;
}
}
CPUプログラム(GPU移植用修正版)
2015/06/10GPGPU実践プログラミング17
matmul2.c
start_c = clock();
matmul(hA,hB,hC);
stop_c = clock();
time_s = (stop_c‐start_c)
/(float)CLOCKS_PER_SEC;
time_ms = time_s*MILLISEC_PER_SEC;
gflops = 2.0*SIZE*SIZE*SIZE/time_s
* FLOPS_TO_GFLOPS;
printf("%f ms¥n",time_ms);
printf("%f GFLOPS¥n",gflops);
return 0;
}
//行列-行列積
void matmul(float *A,float *B,float *C){
int i,j,k;
for(j=0;j<SIZE;j++){  //ループを入れ替え
for(i=0;i<SIZE;i++){//
for(k=0; k<SIZE; k++){
C[i+SIZE*j] += 
A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
}
CPUプログラム(GPU移植用修正版)
2015/06/10GPGPU実践プログラミング18
matmul2.c
//#include, #defineは省略
#include "mm1.cu" //ここのファイル名(*.cu)を変更することで行列-行列積のKernelを切り替え
int main(void){
//CPU用の変数宣言などは省略
float *dA, *dB, *dC;
float *GPUresult; //GPUでの計算結果を受け取る用
cudaMalloc( (void**)&dA, Bytes );
cudaMalloc( (void**)&dB, Bytes );
cudaMalloc( (void**)&dC, Bytes );
GPUresult = (float *)malloc(Bytes);
cudaMemcpy( dA, hA, SIZE*SIZE*sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy( dB, hB, SIZE*SIZE*sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy( dC, hC, SIZE*SIZE*sizeof(float), cudaMemcpyHostToDevice);
dim3 Thread = dim3(THREADX,THREADY,1); //実行用パラメータの宣言と設定
dim3 Block  = dim3(BLOCKX ,BLOCKY, 1); //実行用パラメータの宣言と設定
matmulGPU<<<Block,Thread>>>(dA,dB,dC); //行列‐行列積の実行
cudaMemcpy(GPUresult, dC, SIZE*SIZE*sizeof(float), cudaMemcpyDeviceToHost);
//ここ以降で結果が正しいかチェック
return 0;
}
GPUプログラム(CPU処理用共通部分)
2015/06/10GPGPU実践プログラミング19
matmul.cu
2次元的な並列度の指定
2015/06/10GPGPU実践プログラミング20
 <<<ブロック数,1ブロックあたりのスレッド数>>>
 gridDim, blockDimを指定していることと同じ
 x,y,zをメンバに持つdim3構造体
 指定しないメンバは1で初期化される
 dim3型構造体変数の宣言と値の代入
 dim3 変数名 = dim3(xの値, yの値, zの値);
 dim3 変数名;でdim3型構造体変数を宣言
 変数名 = dim3(xの値, yの値, zの値);で値を代入
 dim3 Thread = dim3(THREADX,THREADY,1);
 dim3 Block  = dim3(BLOCKX ,BLOCKY, 1); 
2次元的な並列度の指定
2015/06/10GPGPU実践プログラミング21
#define THREADX ...
#define THREADY ...
#define BLOCKX  ...
#define BLOCKY  ...
int main(void){
dim3 block(2,2,1), thread(4,4,1);
matmul<<<block, thread>>>(...);
dim3 Thread = dim3(THREADX,THREADY,1);
dim3 Block  = dim3(BLOCKX ,BLOCKY, 1); 
matmul<<<Block, Thread>>>(...);
matmul<<<dim3(2,2,1), dim3(4,4,1)>>>(...);
}
dim3型変数block, 
threadを利用
・・・
あるいは直接dim3
型として記述
・・・
dim3型変数のもう
一つの宣言方法
・・・
//1スレッドが全ての要素を計算
#define THREADX 1
#define THREADY 1
#define BLOCKX  1
#define BLOCKY  1
__global__ void matmulGPU(float *A, float *B, float *C){
int i,j,k;
for(j=0; j<SIZE; j++){     //ループを入れ替え
for(i=0; i<SIZE; i++){ //
for(k=0; k<SIZE; k++){
//C[i*SIZE+j] += A[i*SIZE+k]*B[k*SIZE+j];
C[i+SIZE*j] += A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
}
GPUプログラム(行列-行列積カーネル)
2015/06/10GPGPU実践プログラミング22
mm1.cu
GPUプログラムの性能評価
 行列サイズ _____________2
 実行時間 _____________ ms
 実効性能 _____________ FLOPS
1スレッド版は時間がかかりすぎるので
時間があるときにやりましょう
2015/06/10GPGPU実践プログラミング23
4096
≈20,000,000
6M
1282 328ms,0.013G
2562 5153ms,0.0065G
5122 42149ms,0.0064G
GPUへの移植
2015/06/10GPGPU実践プログラミング24
 2次元的に並列化し,1スレッドが1点の積を計算
 行列サイズ8x8, ブロックを各方向2,ブロック内のス
レッド数を4に設定
GPUへの移植
2015/06/10GPGPU実践プログラミング25
 2次元的に並列化し,1スレッドが1成分の積を計算
blockIdx.y=0 blockIdx.y=1
blockIdx.x=0blockIdx.x=1
gridDim.y=2
gridDim.x=2
blockDim.y=4
blockDim.x=4
threadIdx.x=
threadIdx.y=
1スレッドが読み込む配列要素の決定
2015/06/10GPGPU実践プログラミング26
 i = blockIdx.x*blockDim.x + threadIdx.x
 j = blockIdx.y*blockDim.y + threadIdx.y
(0,0)(0,1)(0,2)(0,3)(0,0)
(3,3) (3,3)
(1,0)(1,1)(1,2)(1,3)
(2,0)(2,1)(2,2)(2,3)
(3,0)(3,1)(3,2)(3,3) (3,3)
(0,0) (0,0)
block(0,0) block(0,1)
block(1,0) block(1,1)
threadIdx
threadIdx.x
threadIdx.y
j= 0 1 2 3 4 5 6 7
i=01234567
1スレッドが1要素の計算を担当
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
i
k
i
j
k
j
[A] [C]
[B]
THREADX
THREADY
2015/06/10GPGPU実践プログラミング27
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
for(k=0;k<SIZE;k++){
C[i+SIZE*j] 
+= A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
1スレッドが1要素の計算を担当
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
i
k
i
j
k
j
[A] [C]
[B]
THREADX
THREADY
2015/06/10GPGPU実践プログラミング28
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
for(k=0;k<SIZE;k++){
C[i+SIZE*j] 
+= A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
1スレッドが1要素の計算を担当
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
i
k
i
j
k
j
[A] [C]
[B]
THREADX
THREADY
2015/06/10GPGPU実践プログラミング29
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
for(k=0;k<SIZE;k++){
C[i+SIZE*j] 
+= A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
1スレッドが1要素の計算を担当
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
i
k
i
j
k
j
[A] [C]
[B]
THREADX
THREADY
2015/06/10GPGPU実践プログラミング30
for(i=0;i<SIZE;i++){
for(j=0;j<SIZE;j++){
for(k=0;k<SIZE;k++){
C[i+SIZE*j] 
+= A[i+SIZE*k]*B[k+SIZE*j];
}
}
}
//1スレッド(i,j)がCijの計算を担当
#define THREADX 16
#define THREADY 16
#define BLOCKX  (SIZE/THREADX)
#define BLOCKY  (SIZE/THREADY)
__global__ void matmulGPU(float *A, float *B, float *C){
int i,j,k;
i = blockIdx.x*blockDim.x + threadIdx.x;
j = blockIdx.y*blockDim.y + threadIdx.y;
C[i*SIZE + j]=0.0f;
for(k=0; k<SIZE; k++){
C[i+SIZE*j] += A[i+SIZE*k] * B[k+SIZE*j];
}
}
GPUプログラム(行列-行列積カーネル)
2015/06/10GPGPU実践プログラミング31
mm2.cu
//1スレッド(i,j)がCijの計算を担当
#define THREADX 16
#define THREADY 16
#define BLOCKX  (SIZE/THREADX)
#define BLOCKY  (SIZE/THREADY)
__global__ void matmulGPU(float *A, float *B, float *C){
int i,j,k;
float sum = 0.0f; ・・・レジスタを使う
i = blockIdx.x*blockDim.x + threadIdx.x;
j = blockIdx.y*blockDim.y + threadIdx.y;
for(k=0; k<SIZE; k++){
sum += A[i+SIZE*k] * B[k+SIZE*j]; ・・・レジスタを使うことでグローバル
}                                        メモリへのアクセス回数を減らす
C[i+SIZE*j] = sum; ・・・レジスタからグローバルメモリへ
}                                            代入
GPUプログラム(行列-行列積カーネル)
2015/06/10GPGPU実践プログラミング32
mm2.cu
GPUプログラムの性能評価
 行列サイズ _____________2
 ブロック _____________
 スレッド _____________
 実行時間 _____________ ms
 実効性能 _____________ FLOPS
2015/06/10GPGPU実践プログラミング33
4096
2,600
53G
16×16
256×256
GPUプログラムのパラメータチューニング
2015/06/10GPGPU実践プログラミング34
 1スレッドが1点の計算を担当
 i行j列の計算を担当
 スレッドの割り当て方で性能が変化
 どのような割り当て方がいいのか?
 それはなぜか?
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロックTHREADX
THREADY
GPUプログラムのパラメータチューニング
2015/06/10GPGPU実践プログラミング35
 1スレッドが1点の計算を担当
 i行j列の計算を担当
 スレッドの割り当て方で性能が変化
 どのような割り当て方がいいのか?
 それはなぜか?
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロックTHREADX
THREADY
GPUプログラムの評価(FLOPS)
 行列サイズ _____________2
THREADY→
THREADX↓ 1 2 4 8 16 32 64 128 256 512 1024
1
2 ‐
4 ‐ ‐
8 ‐ ‐ ‐
16 ‐ ‐ ‐ ‐
32 ‐ ‐ ‐ ‐ ‐
64 ‐ ‐ ‐ ‐ ‐ ‐
128 ‐ ‐ ‐ ‐ ‐ ‐ ‐
256 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
512 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
1024 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
行列が大
きいとここ
が高速
CUDAの制限に
より実行不可能
2015/06/10GPGPU実践プログラミング36
4096
GPUプログラムの評価(FLOPS)
 行列サイズ _____________2
THREADY→
THREADX↓ 1 2 4 8 16 32 64 128 256 512 1024
1 0.5 0.9 1.8 3.7 4.8 3.8 3.2 1.9 1.6 1.5 1.5
2 0.9 1.9 3.7 7.3 9.5 7.5 6.3 4.5 4.1 5.4 ‐
4 1.8 3.7 7.4 15 19 14 12 12 13 ‐ ‐
8 3.7 7.4 15 29 31 27 28 32 ‐ ‐ ‐
16 7.3 15 29 55 53 53 54 ‐ ‐ ‐ ‐
32 15 29 57 69 60 53 ‐ ‐ ‐ ‐ ‐
64 29 57 71 67 53 ‐ ‐ ‐ ‐ ‐ ‐
128 47 79 70 54 ‐ ‐ ‐ ‐ ‐ ‐ ‐
256 49 78 58 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
512 48 57 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
1024 49 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
CUDAの制限に
より実行不可能
2015/06/10GPGPU実践プログラミング37
4096
GPUプログラムの評価(FLOPS)
(i*SIZE+jで配列へアクセス )
 行列サイズ _____________2
2015/06/10GPGPU実践プログラミング38
4096
THREADY→
THREADX↓
1 2 4 8 16 32 64 128 256 512
1 0.4 0.9 1.8 3.6 7.2 14.6 29.1 56.8 71.5 68.3
2 0.9 1.8 3.6 7.2 14.5 28.9 56.0 81.6 82.0 57.9
4 1.8 3.6 7.2 14.3 28.5 53.9 49.9 48.9 57.4 ‐
8 3.5 7.0 14.1 28.2 21.9 18.5 21.2 47.1 ‐ ‐
16 4.6 9.2 14.0 38.0 7.8 11.0 29.5 ‐ ‐ ‐
32 3.6 3.9 2.3 2.2 2.7 11.1 ‐ ‐ ‐ ‐
64 2.8 2.2 1.8 2.0 10.2 ‐ ‐ ‐ ‐ ‐
128 1.5 1.7 1.7 3.2 ‐ ‐ ‐ ‐ ‐ ‐
256 1.4 1.5 2.1 ‐ ‐ ‐ ‐ ‐ ‐ ‐
512 1.4 1.6 ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐
共有メモリによるデータ再利用
2015/06/10GPGPU実践プログラミング39
 各スレッドは異なる[A]の行(i)にアクセス
 [B]の列(j)は同じ
 列の読み込みはSIZE回(i=0,1,・・・,SIZE‐1)
 全てのスレッドが同じ列をSIZE回読込
 [B]の再利用で高速化できるのでは?
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロックTHREADX
THREADY
i
k
i
j
k
j
[A] [C]
[B]
共有メモリによるデータ再利用
2015/06/10GPGPU実践プログラミング40
 各スレッドは異なる[A]の行(i)にアクセス
 [B]の列(j)は同じ
 Bk,jだけでなくスレッド数分(Bk,j, Bk+1,j,・・・)
読み込んでどこかに保持
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロックTHREADX
THREADY
共有メモリによるデータ再利用
2015/06/10GPGPU実践プログラミング41
 各スレッドは異なる[A]の行(i)にアクセス
 [B]の列(j)は同じ
 Bk,jだけでなくスレッド数分(Bik,j, Bk+1,j,・・・)
読み込んでどこかに保持
 保持したデータから読み出し
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロックTHREADX
THREADY
L2キャッシュ
コンスタントメモリ
テクスチャメモリ
GPU
レジ
スタ
レジ
スタ
レジ
スタ
レジ
スタ
CUDA 
Core
CUDA 
Core
CUDA 
Core
CUDA 
Core
L1キャッ
シュ
共有
メモリ
SM
レジ
スタ
レジ
スタ
レジ
スタ
レジ
スタ
CUDA 
Core
CUDA 
Core
CUDA 
Core
CUDA 
Core
L1キャッ
シュ
共有
メモリ
SM
グローバルメモリ
ローカル
メモリ
ローカル
メモリ
ローカル
メモリ
ローカル
メモリ
・・・
・・・
Chip
共有(シェアード)メモリ
 ブロック内のスレッドが共通
のデータ(メモリアドレス)に
アクセス可能
 物理的にSMに近い
 遅延はグローバルメモリの
20~30分の1,帯域幅は10倍
2015/06/10GPGPU実践プログラミング42
 Fermi世代以前のGPUで
マネージドキャッシュとして
利用
 1ブロックあたり
16,32*,48kB
*Kepler世代から
ホスト
メモリ
//1スレッド(i,j)がCijの計算を担当
//共有メモリによるデータ再利用版
#define THREADX 256
#define THREADY 1
#define BLOCKX  (SIZE/THREADX)
#define BLOCKY  (SIZE/THREADY)
__global__ void matmulGPU
(float *A, float *B, float *C){
int i,j,k;
float sum=0.0f;
int tx;
__shared__ float sB[THREADX];
i = blockIdx.x*blockDim.x+threadIdx.x;
j = blockIdx.y*blockDim.y+threadIdx.y;
tx= threadIdx.x;
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j];
__syncthreads();
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum;
}
GPUプログラム(行列-行列積カーネル)
2015/06/10GPGPU実践プログラミング43
mm3.cu
共有メモリの宣言
2015/06/10GPGPU実践プログラミング44
 1ブロック内でデータを共有
 スレッド(0,0)がBk,j,スレッド(1,0)が
Bk+1,j,・・・にアクセス
 必要な要素数は行方向のスレッド分
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロックTHREADX
THREADY
THREADX
THREADX
THREADX
__shared__ float sB[THREADX];
共有メモリへ代入
2015/06/10GPGPU実践プログラミング45
i
k
i
j
k
j
[A] [C]
[B]
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=0
__syncthreads(); //同期を取る
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum;
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
tx=
0
1
2
THREADX
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=0
__syncthreads();
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum;
共有メモリから読込
2015/06/10GPGPU実践プログラミング46
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
w
w
00+0
THREADX
共有メモリから読込
2015/06/10GPGPU実践プログラミング47
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=0
__syncthreads();
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum; w
w
THREADX
10+1
共有メモリから読込
2015/06/10GPGPU実践プログラミング48
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=0
__syncthreads(); 
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();//同期を取る
}
C[i+SIZE*j] = sum; w
w
THREADX
20+2
共有メモリへ代入
2015/06/10GPGPU実践プログラミング49
i
k
i
j
k
j
[A] [C]
[B]
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=3
__syncthreads(); //同期を取る
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum;
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
tx=
0
1
2
THREADX
共有メモリから読込
2015/06/10GPGPU実践プログラミング50
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=3
__syncthreads(); 
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum;
w
w
THREADX
03+0
共有メモリから読込
2015/06/10GPGPU実践プログラミング51
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=3
__syncthreads(); 
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();
}
C[i+SIZE*j] = sum; w
w
THREADX
13+1
共有メモリから読込
2015/06/10GPGPU実践プログラミング52
i
k
i
j
k
j
[A] [C]
[B]
1スレッドの計算担当
1スレッドのメモリ
アクセス範囲
1ブロック
共有メモリ
for(k=0; k<SIZE; k+=THREADX){
sB[tx] = B[(k+tx)+SIZE*j]; //k=3
__syncthreads(); 
for(int w = 0;w<THREADX;w++){
sum += A[i+SIZE*(k+w)]*sB[w];
}
__syncthreads();//同期を取る
}
C[i+SIZE*j] = sum; w
w
THREADX
23+2
GPUプログラムの性能評価
 行列サイズ _____________2
 ブロック _____________
 スレッド _____________
 実行時間 _____________ ms
 実効性能 _____________ FLOPS
2015/06/10GPGPU実践プログラミング53
4096
2,600
49G‐>52G
256×1
16×4096
128×1 2841ms,48G
256×1 2631ms,52G
512×1 2748ms,50G
1024×1 2876ms,48G

2015年度GPGPU実践プログラミング 第9回 行列計算(行列-行列積)