Hyper-QでGPU使用効率をアップ

CPUからGPUにはグリッドという単位で実行を依頼するが、CUDAにはストリームという概念がある。CPUメモリからGPUメモリに入力となるデータをDMA転送し、カーネルを実行して処理を行い、結果をDMAでCPUメモリに転送するというのはGPUアクセラレータの標準的な使い方である。このような場合、「CPU→GPUメモリのDMA転送コマンド」、「カーネルの起動コマンド」、「GPU→CPUメモリのDMA転送コマンド」をストリームとして登録する。このようなストリームが複数ある場合、1つのストリーム内のコマンドは順に実行されなければならないが、異なるストリーム間のコマンドの実行順については制約はない。

GPUがグリッド内のブロックをどのSMXに割り当て、それぞれのブロックをどのようにWarpに分割するかを担当するのがワークディストリビュータ(Work Distributor)というブロックである。ワークディストリビュータは実行を依頼されたグリッドに含まれるブロックの数や各SMXの負荷状態を考えて割り当てを決める。

FermiとKeplerのグリッド管理の違い(出典:GTC2012でのNVIDIAのプレゼンテーション資料)

この図に見られるように、前世代のFermi GPUでは複数のストリームを格納するキューから、1つのストリームを選択してワークディストリビュータに送り込むという構造であったが、Keplerでは、最大32個のストリームから並列にグリッド管理ユニット(Grid Management Unit)を経由してワークディストリビュータに接続されるという構造に変わっている。

グリッド管理ユニットは、多数のストリームキューからのグリッドの実行要求や、実行を中断されているグリッドの実行要求など数千の実行要求を管理し、優先度の高いものをワークディストリビュータに送る。そしてKeplerのワークディストリビュータは最大32個のグリッドをSMXに発行することができるようになっている。

Fermiは16個のグリッドを並列実行できるが、Stream1、Stream2、Stream3の順にWork Queueに入るので、並列実行の可能性があるのは○で囲んだCとP、RとXだけになってしまう (出典:GTC2012でのNVIDIAのプレゼンテーション資料)

Fermiでは最大16個のグリッドを並列実行できるのであるが、CPUからGPUにグリッドの実行やDMAの開始を指示するコマンドを発行するワークキューが1本しか無いので、本来は並列に実行しても良いストリームが、ストリーム1のコマンドがすべて終わってからストリーム2のコマンドを処理するというようにシリアライズされてしまい、並列実行の可能性があるのは継ぎ目の部分だけになっていた。

これに対して、Keplerアーキテクチャでは、コマンドを発行するキューを32本に拡張し、32本のキューの先頭から次に実行を開始するコマンドを選択する構造となった。これがHyper-Qである。このようにすると、異なるストリームは異なるキューに対応付けられているので、ストリームAのすべてのコマンドの終了を待ってストリームBのコマンドを実行するのではなく、キュー1からストリームAの最初のコマンドを取り出して実行を開始し、次はキュー2からストリームBのコマンドを取り出して実行を開始することができるようになる。

Hyper-Qがない場合は、StreamAの各グリッドが順に実行され、それが終わるとStreamBの各グリッドが順に実行されるので、GPUの使用率が低い (出典:GTC2012でのNVIDIAのプレゼンテーション資料)

Hyper-Qを使うと、資源の許す範囲で、この例のようにStreamA、StreamB、StreamCの最初のグリッドを並列に実行できGPU使用率を大きく改善できる (出典:GTC2012でのNVIDIAのプレゼンテーション資料)

このように実行すると、1つのコマンドで実行を依頼したグリッドに含まれるブロック数が少なくSMXが空いてしまう場合でも、第2のストリーム、第3のストリームと見ていけば、空いているSMXを使うコマンドが存在する可能性が高くなり、実行資源の有効利用が可能となる。そして、実行資源の利用率が上がれば、実効性能が上がり、処理時間が短縮されることになる。

このHyper-Qの機能はハードウェアで実現されており、CUDAのソースプログラムに手を入れる必要はない。したがって、複数のストリームを使うプログラムでは、同じソースでもHyper-QをサポートするGPUで動作させれば性能が上がるという嬉しい機能である。