SIMT実行モデルの改善
PascalまでのWarpの実行モデルは次の図のようになっており、条件分岐がある場合、まず、if側のパス1の命令が実行され、その後にthen側のパス2の命令が実行される。パス1とパス2の処理が無関係であれば、この実行で問題ない。
しかし、パス1の中にパス2のスレッドの処理結果を待つようなコードがある場合は、永久に待ってしまうデッドロックになってしまう。つまり、PascalまでのGPUでは、ワープ内のスレッドの間で通信を行う場合は、デッドロックにならないよう注意してプログラムを作る必要があった。
PascalまでのGPUではPCはWarpごとに設けられており、すべてのスレッドが同じPCを使うという構造であった。これをVoltaでは、スレッドごとにPCを設け、各スレッドを個別にスケジューリングすることができる構造に変わった。
ただし、スレッドごとにPCがあるといっても、MIMDのようにスレッドごとに別の命令を同時並列的に実行できるという訳ではなく、分岐した各スレッドがどこまで命令を実行し、その時のアーキテクチャ状態をレジスタSに記憶し、次に実行すべき命令がどのアドレスであるかを示すPCを設けたのである。
この構造変更により、if側のパス1の処理Aと処理Bの間、then側のパス2の処理Xと処理Yの間に__syncwarp();を入れた場合、パス1のAの処理が終わった時点で、パス2側の処理Xの実行に切り替わる。そして、処理Xが終わると、パス1のBの処理を行う。さらに処理Bが終わると、パス2の処理Yを行う。結果として、分岐したパス1のスレッドとパス2のスレッドが__syncwarp();を入れたところで同期することができるようになる。
Pascalの場合は、ワープ内のスレッド間で通信する場合は、デッドロックに陥らないLock-Freeのアルゴリズムを使ったものしか扱えなかったが、VoltaではStarvation-Freeのアルゴリズムであれば、他スレッドを待つようなコードも使えるようになった。
次の図は、よく例に使われる双方向リンクリストのAとCのノードの間に、ノードBを挿入する処理である。この例ではlock(a)とlock(a->next)で2つの資源をロックして挿入処理を行っている。そして、ノードCの挿入処理が終わるとロックした資源を解放している。
この処理を並列に実行される複数のスレッドで実行すると、ロックの競合が起こってしまうので、Pascalではうまく働かない。このため、Pascalでこの処理を行う場合は、リンクの付け替えをアトミックなCompare-Swap命令で行うようなLock-Freeなアルゴリズムに変更する必要がある。
しかし、Voltaでは、使いたい資源が他のスレッドからロックされていても、待っていればロックが解除されるので、この例のコードを正しく実行することができる。 科学技術計算では資源のロックを必要とする処理はあまり多くないと思われるが、構造が複雑なニューラルネットの処理などでは、ワープを分割して異なる処理を実行させ、それらのスレッドの間で通信を行うというケースが出てくると思われる。
(次回は2月22日に掲載します)