法改正も基礎も 基礎講座なら網羅! 講師レジュメ法改正も基礎も基礎講座なら網羅! ―リアリスティック一発合格松本基礎講座ガイダンス―
CUDA基礎 - sim.gsic.titech.ac.jp · cuda基礎2 東京工業大学 学術国際情報センター...
Transcript of CUDA基礎 - sim.gsic.titech.ac.jp · cuda基礎2 東京工業大学 学術国際情報センター...
CUDA基礎2
東京工業大学 学術国際情報センター
黄 遠雄
1 2016/6/27 第20回 GPU コンピューティング 講習会
データ競合
2016/6/27 第20回 GPU コンピューティング 講習会 2
Multi threads
多数のthreadが、あるアドレスの値を読みに行き、その値を変えて書き込みに行く場合、1つのthread が書き込みを終了しないうちに別のthreadが値を読みに行く可能性がある。
データ競合(例)
• thread2 も 0 の値をロードする
• その結果Mem[x] = 1
2016/6/27 第20回 GPU コンピューティング 講習会 3
thread1: thread2:
Old Mem[x] New Old + 1 Mem[x] New
Old Mem[x] New Old + 1 Mem[x] New
Mem[x] initialized to 0
time
Atomic Operation
• 一つのメモリアドレスに対してただ一つのread-modify-writeの操作を行う
• ハードウェアが、他のthreadのread-modify-writeの操作を排他的にAtomic Operationが終わるまで停止する – 他のthreadのAtomic OperationはQueueに
– 全てのthreadのAtomic Operationは逐次実行される
2016/6/27 第20回 GPU コンピューティング 講習会 4
Atomic Operationによる正しい結果 thread1:
thread2: Old Mem[x] New Old + 1 Mem[x] New
Old Mem[x] New Old + 1 Mem[x] New
thread1:
thread2: Old Mem[x] New Old + 1 Mem[x] New
Old Mem[x] New Old + 1 Mem[x] New
Or
2016/6/27 第20回 GPU コンピューティング 講習会 5
Thrust
Linear Algebra FFT, BLAS,
SPARSE, Matrix
Numerical & Math RAND, Statistics
Data Struct. & AI Sort, Scan, Zero Sum
Visual Processing Image & Video
NVIDIA
cuFFT,
cuBLAS,
cuSPARSE
NVIDIA
Math
Lib
NVIDIA
cuRAND
NVIDIA
NPP
NVIDIA
Video
Encode
GPU AI –
Board
Games
GPU AI –
Path
Finding
2016/6/27 第20回 GPU コンピューティング 講習会 6
Thrust:GPU版のSTL
• Standard Template Library (STL, C++)のCUDA版に相当したライブラリ。
• Header をincludeし、Host 側からAPIをCallする。
• 計算はGPU上で実行されるが、自分でDevice memoryの管理やKernelコード書く必要がない。
• vector, scan, sortなど配列によく使われる関数が提供されている。
2016/6/27 第20回 GPU コンピューティング 講習会 7
Thrustを用いたベクトル加法
// device メモリを確保
thrust::device_vector<float> deviceInput1(inputLength);
thrust::device_vector<float> deviceInput2(inputLength);
thrust::device_vector<float> deviceOutput(inputLength);
// host 上の配列を device メモリへ転送
thrust::copy(hostInput1, hostInput1 + inputLength, deviceInput1.begin());
thrust::copy(hostInput2, hostInput2 + inputLength, deviceInput2.begin());
// device上の配列 deviceInput1 と deviceInput2 に thrust::plus<float>() // の操作を行う、計算結果はdeviceOutputに出力
thrust::transform(deviceInput1.begin(), deviceInput1.end(), deviceInput2.begin(), deviceOutput.begin(),
thrust::plus<float>());
2016/6/27 第20回 GPU コンピューティング 講習会 8
変数型の Qualifier
__shared__ shared memory 上に確保される。
block単位のthread実行中のみ確保される。
block内のthreadからしかread/write不可
__constant__ constant memory 上に確保される。
プログラムが終了するまで確保される。
全てのthreadからreadでき、CPU側からは APIを通じて write 可。
constant cache が有効、 thread から読み込みが早い。
__device__ global memory の領域に確保される。
プログラムが終了するまで確保される。
全てのthreadからread/writeでき、CPU側からはAPIを通じて read/write 可。
Variable declaration Memory Scope Lifetime
int LocalVar; register thread thread
__device__ __shared__ int SharedVar; shared block block
__device__ int GlobalVar; global grid application
__device__ __constant__ int ConstantVar; constant grid application
2016/6/27 第20回 GPU コンピューティング 講習会 9
Shared メモリの確保
10
KERNEL 関数の中で
静的に確保 __shared__ double fs[1024];
実行時に確保 extern __shared__ double fs[];
<<< Dg, Db, 1024, 0 >>> の第3引数でサイズを指定。
Global Memory
Processing Unit
I/O
ALU
Processor (SM)
Shared Memory
Register File
Control Unit
PC IR
各Streaming Multiprocessor 当たり実際にあるメモリ(ハードウェア、on-chip memory) 非常に高速なアクセスが可能
block 内のthread でデータの交換が可能。
2016/6/27 第20回 GPU コンピューティング 講習会
Reduction
• 配列の中の、最大値、最小値、総和等を求めたり、Sort などを行う操作。
• CPU では容易で、様々なアルゴリズムが開発されている。GPU では data parallel に基づいたthread 並列が必須なため、
Parallel Reduction を行う必要がある。
2016/6/27 第20回 GPU コンピューティング 講習会 11
配列要素の総和
• Global memory の配列の中の、最大値、最小値、総和等を求める方法は基本的に同じであるため、ここでは総和を求める。
• Reduction は配列要素間の比較を行う必要があるが、thread 間で情報交換を行うには block 内では shared memory を使い、grid 全体では global memory を使う。
• global memory の頻繁なアクセスを行うと、GPU本来の性能が引き出せないので、可能な限り shared memory を利用する。
12 2016/6/27 第20回 GPU コンピューティング 講習会
総和計算の問題設定
• n (~1024x1024x16(double で約134.2MB))個の要素を持つ配列 A[n] に対して、総和(平均値)を求める。
• 初期に a_h[n] に 0~0.99999999 の疑似乱数を設定し、CPU での総和計算と GPU での総和計算の速度と結果を比較する。
• 初期設定:
srand(12131);
for(i = 0; i < n; i++) a_h[i] = (double) rand()/RAND_MAX;
• GPU の場合は、初期化後に cudaMemcpy で GPU にa_h[n] を転送。
13 2016/6/27 第20回 GPU コンピューティング 講習会
幾つかの総和計算の例
• CPU での総和計算
double sum = 0.0;
for(i = 0; i < n; i++) sum += a_h[i];
• Atomic operationでの総和計算
• Thrustでの総和計算
2016/6/27 第20回 GPU コンピューティング 講習会 14
Sample Code: AtomicSample/serialSum
Sample Code: thrustReduction
GPU でのParallel Sum Reduction
2016/6/27 第20回 GPU コンピューティング 講習会 15
Global memory から shared memory へのデータの読み込み
block 分割: n / (BLOCK_SIZE x 2)個のblock 例:BLOCK_SIZE = 256
512要素 512要素 512要素 a_d[i] 256 thread 256 thread 256 thread
kernel 関数の中に、shared memory の確保
__shared__ double fs[BLOCK_SIZE x 2];
各block の総和は global memory に出力
n = n / (BLOCK_SIZE x 2);
(次のイテレーションへ)
b_d[i]
Parallel Sum Reductionの一例
2016/6/27 第20回 GPU コンピューティング 講習会 16
問題点
• 半分のthreadsしか計算しなく、Warp内にControl Divergence 発生
• 毎ステップ毎にデータアクセスの距離が長くなる
2016/6/27 第20回 GPU コンピューティング 講習会 17
収集場所の変更
2016/6/27 第20回 GPU コンピューティング 講習会 18
Thread 0
3 1 7 0 6 1 4 3
7 2 13 3
20 5
25
Thread 1 Thread 2 Thread 3
改善した Kernel
2016/6/27 第20回 GPU コンピューティング 講習会 19
for (unsigned int stride = blockDim.x; stride > 0; stride /= 2)
{
__syncthreads();
if (t < stride)
partialSum[t] += partialSum[t+stride];
}
for (unsigned int stride = 1; stride <= blockDim.x; stride *= 2) { __syncthreads(); if (t % stride == 0) partialSum[2*t]+= partialSum[2*t+stride]; }
Sample Code: Reduction/reduction
各ステップで、そのステップを実行する前に前回のステップでの全てのthreadによる計
算が終わっている事を保証する為 __syncthreads() が必須
データ転送と計算
• CUDA Kernel は全部のデータがコピーし終わった後に、実行される
2016/6/27 第20回 GPU コンピューティング 講習会 20
Trans. A Trans. B Comp Trans. C
time
Only use one direction, GPU idle
PCIe Idle Only use one direction, GPU idle
パイプライン化
• データを分割して、データ転送と計算をoverlapする
2016/6/27 第20回 GPU コンピューティング 講習会 21
Trans A.0
Trans B.0
Trans C.0
Trans A.1
Comp C.0 = A.0 + B.0
Trans B.1
Comp C.1 = A.1 + B.1
Trans A.2
Trans B.2
Trans C.1
Comp C.2 = A.2 + B.2
Trans A.3
Trans B.3
CUDA Stream
2016/6/27 第20回 GPU コンピューティング 講習会 22
Device での thread の実行(タスク)は、ストリーム単位で管理されている。
■ GPU の kernel 関数の実行も 1つの stream ■ cudaMemcpy も1つの stream
Default で全ての stream の番号は 0 に設定されている。
同じ stream 上では、投入順に実行される。
GPU kernel の同時実行が可能になったのは CUDA 3.0 から。
cudaGetDeviceProperties( &prop, Device_No ); prop.deviceOverlap = 1
Utility: deviceQuery
Concurrent kernel execution: Yes
Stream の指定
cudaStream_t stream; cudaStreamCreate( &stream );
cudaStreamDestroy( stream );
cudaMemcpyAsync(void *dst, const void *src, size_t count, enum cudaMemcpyKind kind, cudaStream_t stream); kind: cudaMemcpyHostToDevice cudaMemcpyDeviceToHost
gpu_kernel_fucntion<<< grid, thread, 0, stream>>(a, b, c); grid: block_per_grid, thread: threads_per_block,
2016/6/27 第20回 GPU コンピューティング 講習会 23
Stream スケジューリング(1) ti
me
stream 0
cudaMemcpy 0 {0}
cudaMemcpy 0 {1}
GPU kernel 0 {0}
GPU kernel 0 {1}
GPU kernel 0 {2}
GPU kernel 0 {3}
cudaMemcpy 0 {2}
cudaMemcpy 0 {3}
GPU kernel 0 {1}
nonation
stream number
calling order
2016/6/27 第20回 GPU コンピューティング 講習会 24
Stream スケジューリング(2) ti
me
stream 1
cudaMemcpy 1 {0}
cudaMemcpy 1 {1}
GPU kernel 2 {0}
GPU kernel 2 {1}
GPU kernel 2 {2}
GPU kernel 2 {3}
cudaMemcpy 1 {2}
cudaMemcpy 1 {3}
stream 2
cudaDeviceSynchronize(); {1};
cudaDeviceSynchronize(); {2}
GPU kernel 2 {4}
2016/6/27 第20回 GPU コンピューティング 講習会 25
Stream スケジューリング(3) ti
me
stream 1
cudaMemcpy 1 {0}
cudaMemcpy 1 {1}
GPU kernel 2 {0}
GPU kernel 2 {1}
cudaMemcpy 1 {3}
cudaMemcpy 1 {4}
stream 2
cudaDeviceSynchronize(); {1};
cudaDeviceSynchronize(); {2}
2016/6/27 第20回 GPU コンピューティング 講習会 26
GPU kernel 1 {2}
GPU kernel 2 {2}
Multi-Streamを用いた配列計算
2016/6/27 第20回 GPU コンピューティング 講習会 27
… unsigned int partition_size = n / STREAM_N; for(i = 0; i < STREAM_N; i++) { cudaStreamCreate( &stream[i] ); cudaMalloc( (void**) &a_d[i], partition_size * sizeof(double) ); … } for(i = 0; i < STREAM_N; i++) { cudaMemcpyAsync( a_d[i], a_h + i * partition_size, partition_size * sizeof(double), cudaMemcpyHostToDevice, stream[i] ); … kernel<<< partition_size / BLOCK_SIZE, BLOCK_SIZE, 0, stream[i] >>>( c_d[i], a_d[i] ); … } for(i = 0; i < STREAM_N; i++) { cudaMemcpyAsync( c_h + i * partition_size, c_d[i], partition_size * sizeof(double), cudaMemcpyDeviceToHost, stream[i] ); }
Sample Code: MultiStream/VectorAddMS
1 Stream の結果
2016/6/27 第20回 GPU コンピューティング 講習会 28
4 Streams の結果(overlap)
2016/6/27 第20回 GPU コンピューティング 講習会 29
Overlapped with input Overlapped with output
Concurrent Stream 並列
2016/6/27 第20回 GPU コンピューティング 講習会 30
Sample Code: MultiStream/Concurrent
Unified Memory
CPU GPU
Host
Memory
Device
Memory
CPU GPU
Unified
Memory
独立メモリスペース 仮想共有メモリスペース
Unified Memoryの利点
• 手動で H2D, D2H コピーは必要なくなる
• ポインターの数は減る、管理が容易に
• コードの行数も減る
• CPU/GPU両方の関数もアクセス出来るのでデバッグは簡単になる
• 複雑なデータ構造の実装も簡単になる (例:動的多次元配列)
Example Code
Host code with device memory
int main()
{
unsigned int size = n*sizeof(double);
…
a_h = (double **) malloc(size);
b_h = (double **) malloc(size);
…
cudaMalloc( (void**) &a_d, size );
cudaMalloc( (void**) &b_d, size);
…
cudaMemcpy( a_d, a_h, size, cudaMemcpyHostToDevice );
…
}
Host code with unified memory int main()
{
unsigned int size = n*sizeof(double);
…
cudaMallocManaged( (void**) &a_h[s], size );
cudaMallocManaged( (void**) &b_h[s], size );
…
}
2016/6/27 第20回 GPU コンピューティング 講習会 33
GPUの上に動的二次元配列(1)
CPU a_h = (double **) malloc(sizeof(double *)*ny);
for(j = 0; j < ny; j++)
{
a_h[j] = (double *) malloc(sizeof(double)*nx);
}
GPU cudaMalloc( (void**) &a_d, sizeof(double *)*ny );
for(j = 0; j < ny; j++)
{
cudaMalloc( (void**) &a_d[j], sizeof(double)*nx );
}
OK Segmentation fault
2016/6/27 第20回 GPU コンピューティング 講習会 34
GPUの上に動的二次元配列(2)
cudaMalloc( (void**) &a_d, sizeof(double *)*ny );
for(j = 0; j < ny; j++)
{
cudaMalloc( (void**) &a_d[j], sizeof(double)*nx );
}
cudaMalloc() は Host memory をアクセスするが a_d[j]は device memory にいる
Sample Code: UnifiedMemory/MatrixAddDynamicError01
2016/6/27 第20回 GPU コンピューティング 講習会 35
a_d
a_d[j]
Host memory
Device memory
GPUの上に動的二次元配列(3)
a_d = (double **) malloc(sizeof(double *)*ny);
for(j = 0; j < ny; j++)
{
cudaMalloc( (void**) &a_d[j], sizeof(double)*nx );
}
….
Kernel<<<........>>>( a_d ... );
Kernel Error a_d[j]はHost memoryにいる Kernel内ではアクセス出来ない
2016/6/27 第20回 GPU コンピューティング 講習会 36
a_d
a_d[j]
Host memory
Device memory
Sample Code: UnifiedMemory/MatrixAddDynamicError02
GPUの上に動的二次元配列(4)
a_d = (double **) malloc(sizeof(double *)*ny);
for(j = 0; j < ny; j++)
{
cudaMalloc( (void**) &a_d[j], sizeof(double)*nx );
}
….
cudaMalloc( (void**) &a_dd, sizeof(double *)*ny );
cudaMemcpy( a_dd, a_d, sizeof(double *)*ny, cudaMemcpyHostToDevice );
Kernel<<<........>>>( a_dd ... );
a_d[j] を通してメモリ確保した後 a_d[j] (データ=アドレス)を (device memory上)a_dd[j] にコピーする
2016/6/27 第20回 GPU コンピューティング 講習会 37
a_d
a_d[j]
Host memory
Device memory
a_dd[j]
a_dd
Sample Code: UnifiedMemory/MatrixAddDynamic
GPUの上に動的二次元配列(5)
Unified Memoryを使えば
cudaMallocManaged( (void**) &a_h, sizeof(double *)*ny ); for(j = 0; j < ny; j++) { cudaMallocManaged( (void**) &a_h[j], sizeof(double)*nx ); } ... Kernel<<<........>>>( a_h ... );
2016/6/27 第20回 GPU コンピューティング 講習会 38
a_h = (double **) malloc(sizeof(double *)*ny); for(j = 0; j < ny; j++) { a_h[j] = (double *) malloc(sizeof(double)*nx); }
Sample Code: UnifiedMemory/MatrixAddDynamicUM
Unified MemoryとStream
• Unified MemoryはバッググラウンドでCPU/GPU間にデータ転送を行う
• Streamは非同期でデータアクセスする
• Crash(Segmentation fault)の可能性がある
–ある時点で、あるポインター(メモリ)をアクセス出来るStreamの指定が必要(Data Visibility)
– cudaDeviceSynchronize()を使う
2016/6/27 第20回 GPU コンピューティング 講習会 39
Data Visibility の指定
cudaStreamAttachMemAsync( cudaStream_t stream, void *ptr, size_t length, unsigned int flags)
stream: CUDA stream
ptr: メモリ・アドレス
length: 領域のサイズ
flags: Data Visibility のタイプを指定する定数
cudaMemAttachGlobal
cudaMemAttachSingle
cudaMemAttachHost
2016/6/27 第20回 GPU コンピューティング 講習会 40
• cudaMemAttachGlobal(default) – すべてのGPU Stream 、CPUもアクセス可能 – GPU が何かを実行する時、このメモリ(使用中ではなくでも)はアクセス出来なくなり、Concurrent streamでアクセスすると Segmentation fault の可能性がある
• cudaMemAttachSingle(Stream) – 指定したGPU streamだけがアクセス可能 – Concurrent streamはこれを使う
• cudaMemAttachHost – CPUだけがアクセス可能 – GPUの計算が影響しなくなる
Data Visibility のタイプ(flags)
Concurrent Stream 並列 (Unified Memory + CPU)
2016/6/27 第20回 GPU コンピューティング 講習会 42
Sample Code: UnifiedMemory/VectorAddUMMS
Overlapped with Data copy Overlapped GPU and CPU calculations