格子 QCD における GPU 計算
-
Upload
penelope-lyons -
Category
Documents
-
view
83 -
download
1
description
Transcript of 格子 QCD における GPU 計算
格子 QCD における GPU 計算
広大理 尾崎裕介共同研究者 石川健一
2
1. Introduction
格子 QCD 計算では HMC(Hybrid Monte-Carlo)法がよく用いられる
この手法において、差分方程式を解く計算が最も時間のかかる計算
この計算を GPU に計算させることによって加速solver のアルゴリズムは Bi-CGStab 法
この際に作成した CUDA のコードに対して行った工夫等について紹介
2009/6/24
3
Outline
2009/6/24
1. Introduction
2. GPU を使って倍精度の結果を得るために
3. ホッピングの計算(行列 × ベクトル)
4. 内積の計算
5. Fortran + Cuda
6. おわりに
4
2.1 GPU で倍精度
差分方程式の解は倍精度の精度が必要 しかし GPU は単精度計算が高速
単精度:約 1TFlops倍精度: 86.4GFlops
単精度演算を行いながら、得られる結果が倍精度であるような手法があれば理想的
反復改良の手法によって、実は可能
2009/6/24
5
2.2 単精度倍精度混合 Solver
差分方程式 ( 連立 1 次方程式 )
前処理付き反復法による解法(倍精度)
単精度倍精度混合 Solver (単精度)
2009/6/24
:残差ベクトル
M :前処理行列
6
2.3 単精度倍精度混合 Solver
2009/6/24
subroutine usual_solver(...) ⋮do i = 0,1,2,... ⋮p = M * r ! Preconditionq = A * px = x + αpr = r – αq ⋮enddo ⋮end subroutine
subroutine mixed_solver(...) ⋮As = A ! 倍→単 変換 ⋮do i = 0,1,2,... ⋮rs = r ! 倍→単 変換ps = (As)-1 * rs ! 単精度計算p = ps ! 単→倍 変換q = A * px = x + αpr = r – αq ⋮enddo ⋮end subroutineGPU の単精度高速計算!
7
2.4 収束の様子
2009/6/24
8
3.1 ホッピングの計算
単精度 Solver のアルゴリズムは Bi-CGStabWilson quark の場合によく用いられる。
このような反復法による計算では のような行列とベクトルの積の計算が支配的
まずはこの計算を高速化
2009/6/24
9
3.2 の計算の様子
2009/6/24
quark : 3×4 ベクトル
hopping : 12×12 行列
10
3.3 Basic strategy
Nvidia Cuda Programming Guide より、 なるべく並列度を上げる
1 thread あたりの計算を 1 格子点の計算に assign(12×12 行列 ) × (12 次元ベクトル )
メモリアクセスの最適化global memory に対するアクセス速度が遅い
400 ~ 600 clockshared memory 、 texture cache 等の有効利用
4 clock計算によって生成できるデータは GPU 上で計算
memory access が遅いので計算した方が速い場合がある2009/6/24
11
3.4 データアクセス量の削減
2009/6/24
格子の辺に対応する行列は 12×12 しかし、 12×12 の行列を計算機上に保存しておく必要
はない各 μ ごとにコーディングを行えば 3×3 行列を 4 組保存しておけ
ば十分
さらに U はリー群 SU(3) の元であり、 3×3 も必要ない厳密には 8float今回は少し楽をして 6complex
12
3.5 効率的なメモリアクセス データ量とロード回数
2009/6/24
計 8 回のロード12×2 float = 96Byte
計 2 回のロード(6×2 float = 48Byte) ×
4set block 内では shared memory を使って thread 間の memory 共有ができるshared memory : 16KByteできるだけ多くの格子点を共有した
い ロード回数の多い格子点を
shared43×2=128 の格子点 = 12.6KByte リンクは texture を用いた
13
3.6 solver の性能
その他 Bi-CGStab 法で必要な演算(複素数の内積等)をSDK 等を参考に作成
これらを組み合わせて GPU を使った単精度 solver を作成even/odd preconditioning ← 格子サイズを半分にする手法clover term の計算 ← 格子間隔による誤差を減少させる
さらに前処理付き Bi-CGStab 倍精度 solver とマージ このようにしてできた solver を実際に走らせてみた
GPU : NVIDIA GeForce GTX 280 CPU : Intel Core i7 2.67GHz
結果約 20GFlops の性能
2009/6/24
この solver を以下のようにして高速化した これらの手法を紹介
14
3.7 coalesced access
2009/6/24
これまでのデータ構造は coalesced access の条件を満たしていなかった
block 0 block 1 …
site 0 site 1 site 2 … site 128 site 0 … …
block 0 block 1 …
0 1 … 128 0 1 … 0 1 … 0 1 … …
96Byte 96Byte 96Byte 96Byte 96Byte
16B 16B 16B
このようにデータを並び替えて coalesced access の条件を達成
15
3.8 divergent branch
ある格子点 (K) を計算する際、その隣の格子点 (S) にあるデータが必要
shared memory を用いる場合
2009/6/24
if (S は K と同じブロック ) → shared memory loadelse → global memory load
divergent branch !! 代わりに texture fetch を使う
任意の格子点 → texture fetch load
multiprocessor あたりの texture cache 8KBshared memory は 16KB
No divergent branch !!
16
3.9 ここまでの結果
coalesced access and using No texture 40GFlops
coalesced access and using texture 50GFlops
coalesced access にすることで 速度は倍以上 !
texture fetch により、さらに速度 up!ただし、データは cache に乗りきらない
格子点 12.3KB + リンク 24.6KB > 8KBそれでも一部のデータは cache の恩恵を受けたた
め? 特にホッピングの計算部分の性能は 107GFlops
他の計算(内積等)が足を引っ張っている2009/6/24
17
4.1 内積の高速化
内積について高速化を行った 現在の内積は 17GFlops
bandwidthTest → 114GByte/s (GeForce GTX 280)
内積計算 : 2Byte/Flop理論性能 : 57GFlops
reduction のコードを kernel 5 → kernel 7NVIDIA_CUDA_SDK/projects/reduction/doc/
reduction.pdf要素数 2 冪以外に対応するように改造
2009/6/24
182009/6/24
template <unsigned int blockSize>__global__ void reduce6(int *g_idata, int *g_odata, unsigned int n){ extern __shared__ int sdata[]; unsigned int tid = threadIdx.x; unsigned int i = blockIdx.x* (blockSize*2) + tid; unsigned int gridSize = blockSize*2*gridDim.x; sdata[tid] = 0;
while (i < n) { sdata[tid] += g_idata[i] + g_idata[i+ blockSize]; i += gridSize; } __syncthreads();
if (blockSize >= 512) { if (tid < 256) { sdata[tid] += sdata[tid + 256]; } __syncthreads(); } if (blockSize >= 256) { if (tid < 128) { sdata[tid] += sdata[tid + 128]; } __syncthreads(); } if (blockSize >= 128) { if (tid < 64) { sdata[tid] += sdata[tid + 64]; } __syncthreads(); }
if (tid < 32) { if (blockSize >= 64) sdata[tid] += sdata[tid + 32]; if (blockSize >= 32) sdata[tid] += sdata[tid + 16]; if (blockSize >= 16) sdata[tid] += sdata[tid + 8]; if (blockSize >= 8) sdata[tid] += sdata[tid + 4]; if (blockSize >= 4) sdata[tid] += sdata[tid + 2]; if (blockSize >= 2) sdata[tid] += sdata[tid + 1]; } if (tid == 0) g_odata[blockIdx.x] = sdata[0];}
4.2 reduction : kernel 7
赤を消すと 2 冪以外にも対応可能
19
4.3 内積の高速化
reduction の部分は kernel 7 のコードを用いることにして、各成分同士の複素数 × 複素数の計算部分をコーディング
kernel 5 と kernel 7 は速度が倍違う → 倍速くなるだろう
2009/6/24
20
4.4 各成分の計算部分
2009/6/24
block 0 block 1 …
0 1 … 0 … 0 … 0 … 0 1 … …
float4 float4 float4
実部 虚部
x x
y y
z z
w w
実 虚a
x x
y y
z z
w w
実 虚b
a*b = c
x x
y y
z z
w w
実 虚c
=
float4 ar,ai,br,bi;
ar = (*a).b[blk].ri[0].c[site];ai = (*a).b[blk].ri[4].c[site];br = (*b).b[blk].ri[0].c[site];bi = (*b).b[blk].ri[4].c[site];
1thread 当りの計算
このやり方だと約20GFlops
21
4.5 各成分の計算部分の改良
2009/6/24
block 0
… …
float4 float4 float4
実部 虚部
x x
a
x x
b
a*b = c
x x
c=
float* xr,xi,yr,yi;float ar,ai,br,bi;
xr = &((*a).b[0].ri[0].c[0].x);xi = &((*a).b[0].ri[4].c[0].x);yr = &((*b).b[0].ri[0].c[0].x);yi = &((*b).b[0].ri[4].c[0].x);
ar = xr[i];ai = xi[i];br = yr[i];bi = yi[i];
1thread 当りの計算
強引に float アクセスに
y y
a
y y
b
y y
c=
1thread 当りの計算
32GFlops
22
4.6 内積の高速化
内積を計算する際の複素数 × 複素数の計算部分を float4 から float に変更見方を変えると並列度を上げたことに対応
結果、 20GFlops から 32GFlops に speed up!!元の 17GFlops から約 2 倍
全体の solver も 50GFlops → 55GFlops なぜ速くなったかはまだよくわかっていない
単純に float4 アクセスより float アクセスの方が速いのか?
並列度が上がった影響なのか?この結果は内積部分での話。他の演算ではどうか?texture経由での float4 アクセスはどうか?
2009/6/24
23
5. Fortran + Cuda
今回のプログラムは Fortan から cuda のコードを呼ぶように書いている
この書き方には標準がなく、プラットフォームやコンパイラによって異なる
今回は Linux 上の Intel コンパイラを使った時の話を紹介
2009/6/24
24
5.1 Fortran + cuda
基本的には Intel Fortran と C 間の場合と同じcuda 側の関数名の後ろにアンダースコア「_」を1つ付け加
えるcuda 側の関数宣言時、先頭に「 extern “C”」をつける引数を受け取る際、 cuda側ではポインタとして受け取る配列の順序コンパイル時に cudart 等へのリンク
等 ただし、 Fortran 上で GPU 上のメモリを扱うことが難
しかった (プログラムの先頭でメモリ確保し、後の呼び出しで引数として渡す )
今回はグローバル変数を用いて、 Fortran側にデバイスメモリが現れないようにコーディングを行った
2009/6/24
25
5.2 Fortran+cuda
2009/6/24
subroutine use_gpu_solver(...) ⋮As = A ! 倍→単 変換call new_gpu_solver(As,dA) ⋮do i = 0,1,2,... ⋮rs = r ! 倍→単 変換call s_bicgstab_gpu(rs,ps,dA,...)p = ps ! 単→倍 変換q = A * px = x + αpr = r – αq ⋮enddo
call delete_gpu_solver(dA) ⋮end subroutine
// s_bicgstab_gpu.cucuhgvfield dA;
extern “C”void new_gpu_solver_(cuhgvfield *As) { cudaMalloc((void**)&dA,...); cudaMemcpy(dA,As,...);}
extern ”C”void delete_gpu_solver_() { cudaFree((void *)dA);}
extern “C”void s_bicgstab_gpu_(hqfield *rs, hqfield *ps, .. ){ cuhqfield *dr; cudaMalloc((void**)&dr),...); ⋮ cudaMemcpy(ps,dp,...); cudaFree((void *)dr); ⋮}
26
5.3 Fortran+cuda (Makefile)
2009/6/24
Intel Fortran + nvcc
# Makefileall :: solver_main
GPULIB = GPUSolverLIB/libgpusolver.a
LIBDIR = -L$(CUDADIR)/lib \ –L$(CUDASDKDIR)/lib \ -L$(CUDASDKDIR)/common/libLIBS = $(LIBDIR) –lcudart –lcutil -lm
solver_main : $(OBJ) $(GPULIB) ifort $(LIBS) $(OBJ) $(GPULIB) \ –o $@
$(GPULIB) : (cd GPUSolverLIB; make)
# GPUSolverLIB/Makefileall :: libgpusolver.a
s_bicgstab_gpu.o : s_bicgstab_gpu.cu nvcc –c $< –o $@
libgpusolver.a : s_bicgstab_gpu.o ar cr $@ $<
cutil.h を include する場合
cuda を call する場合
27
6. おわりに
格子 QCD 計算の分野では大規模連立 1 次方程式を解く計算が大変
この計算を GPU によって加速 GPU で高速計算を実現するためにはメモリアク
セスにかなり気を配らないといけないCoalessed AccessDivergent WarpBank conflictfloat access ?
2009/6/24