CUDA C プロフェッショナル第1章から第3章までのメモ。
この本はとても良かった。丁寧に詳細まで解説してある。中級〜上級者向け。Impressのサイトにソースコードがおいてあるので、落として手を動かしながら進めていくのが吉。
インプレス
売り上げランキング: 42,483
第1章
CC(Compute Capability) 3.5 以降 (Kepler 世代以降) に最適なバイナリを生成する。それ以前では動かない。
gencode オプションを使うと複数世代に最適なコードを生成し、ワンバイナリに含めることができる
第2章
- 1つのカーネル呼び出しによって生成されたすべてのスレッドを、まとめてグリッドと呼ぶ
- ブロックとは、互いに協調して動作できるスレッドのグループであり、以下の機能を利用できる
- ブロックに属するスレッド間の同期 (ブロックをまたいだ同期は難しい)
- ブロック内で共有されるシェアドメモリ
kernel<<nBlocks, nThreadsPerBlock>>
のように呼び出す- nBlocks 1グリッドあたりのブロック数
- nThreadsPerBlock 1ブロックあたりのスレッド数
- CUDAでは32本のスレッド(1 Warp)に対して1つのインストラクションを1度に実行する。
- nThreadsPerBlock は 32の倍数にしておかないと、損をする
- インストラクションは Warp ごとだが、レジスタとシェアドメモリと同期はブロックごと
Fermi 以降なら、カーネルで printf が使える
CUDA API の実行失敗をチェックするマクロ CHECK()
。例えばCHECK(cudaDeviceSynchronize());
とすればそこまでの処理全部に対して失敗しているものがいないか確認できる。
kernel を送り込むところで CPU で時間がかかる。なので、非同期に送り込みたくなるし、実際 kernel 実行は非同期になっている
Fermi の場合、
グリッドあたりの各次元(x,y,z,w)ごとの最大ブロック数は65536
ブロックあたりのスレッドの数は最大 1024
なお、現行の chainer(cupy) の場合は以下のように x しか使っていない。
スレッド数 128、ブロック数 = 要素数 // スレッド数(128)。
(65536 を超えるようだったら、y を使うようにしても良いかもしれないが、ソフトウェア的にブロック数を増やした所で、ハードウェアのスペックをすでに超えてるから意味ないだろうなぁ)
第3章
SIMDとSIMT
- SIMD: 複数データにたいして1つのインストラクションを1度に実行する。全体で1つのインストラクション。命令ユニットが少なくてすむ。
- SIMT: NVIDIA独自(というか提案?)のアーキテクチャ。SIMDの改善。32本のスレッド(1 Warp)に対して1つのインストラクションを1度に実行する。全体としては同時に複数のインストラクションを発行可能(別Streamを指定して発行)。
ワープダイバージェンス
- 同じワープ内のスレッドが異なる命令を実行することをワープダイバージェンスと呼ぶ
- ワープ内のスレッドが異なる分岐パスを選択した場合、ワープは各分岐パスを逐次的に実行する(分岐 true を実行してから、分岐 false を実行する)
- SIMDの場合、そもそも分岐処理できないので、それの改良ではある。
- ただし、遅くはなる
ダイナミックパラレリズム
- kepler 以降
- kernel から kernel 呼び出しができる
nvidia-smi -a -q -d CLOCK | fgrep -A 3 "Max Clocks" | fgrep "Memory
- メモリ転送速度を帯域幅じゃなくて周波数でみることができる
- 帯域幅 / 周波数 = bytes / サイクル
3.3 並列性の確保
nvprof --metrics arhieved_occupancy
- 占有率 = アクティブワープの数 / ワープの最大数 を見る。GPUを有効に使えているかどうかの指標
- ワープの最大数: SMあたりの最大スレッド数(M2070ではmaxThreadsPerMultiProcessor == 1,536) / 32
- アクティブワープの数: 有効に使っているワープの数
- 占有率をあげるには、基本的には nBlocks の数をあげれば良い
- といって、nThreadsPerBlock を 128 より小さくして nBlocks を大きくしようとすると占有率はあがるものの、1ブロック(ハードウェア的にはSM)あたりのスレッド数が減って並列処理できなくなるので、遅くなる。<= Q. そこもあわせて見る指標はないのか?
nvprof --metrics gld_throughput
- メモリ読み取りスループットのチェック
- 補足: cudaMalloc とか cudaMemcpy とかではなくて、GPUプロセッサとGPUメモリ間の転送
- (CPUでこれを見ようと思うと難しいので簡単に見れるのは嬉しい)
- Q. ブロック数とスレッド数の変更で、メモリ読み取りスループットが変わる理屈がよくわからない。そもそも説明が書いてない。
nvprof --metrics gld_efficiency
- メモリ読み取り効率のチェック
- (さらに%で見れるとは嬉しい)
グリッドのサイズ(nBlock)とブロックのサイズ(nThreadsPerBlock)の決定に関するガイドライン
- nThreadsPerBlock をワープサイズ(32)の倍数に保つ
- nThreadsPerBlock は 128 または 256 からチューニングを始める
- カーネルのリソース要件に従ってブロックサイズを調整する
- nBlock がSMの数をはるかに上回るようにする (k80では13)
- 元々のデータサイズが小さいと、SMの数を上回れなくなるので、データをまとめて一気に処理するようにするとか?
3.4, 3.5. リダクション
(日本語) http://gpu-computing.gsic.titech.ac.jp/Japanese/Lecture/2010-06-28/reduction.pdf
うーむ、難しい。あとでまた読む