キューを使った処理分担
複数のGPUが処理を分担する場合、新たな仕事が出てくるとキューに入れ、空きができたGPUはキューの先頭から次の処理の指令を取り出して処理をしていくというやり方が使われる。
新たな仕事がでてくると、キューのHeadポインタを進めて、新しい仕事を書き込むエリアを作る。そして、新しい仕事のデータを書き込む。これが新しい仕事をキューに入れるPush動作である。
キューから仕事を取り出すには、Tailポインタを1つ進めて、その場所から仕事の指令を取り出す。これがPop操作である。
通常はこれで良いのであるが、空のキューへのPushとPopが同時に起こった場合はうまく行かない。
アトミックアクセスを使って、排他処理行えば正しく動作するが、排他処理のオーバヘッドが大きい。
この問題を解決するには、2つのHeadポインタ(あるいは2つのTailポインタ)を使う。Pushの方は、Outer Headポインタを進めて、それが指す場所に新しい仕事のデータを書き込む。そして、Inner Headポインタを進める。
一方、Pop側では"tail"=="Inner Head"であれば、何もしない。そうでなければtailポインタを進める。そして、新しい仕事のデータを読み出す。つまり、キューが空であれば、Popは無効になり、空のキューからロ見出しをおこなうことはない。そしてPushが行なわれてinner Headが進められると、Popができるようになる。
複数のプロデューサーと複数のコンスーマーがある場合は、innerとouterのheadを使いアンダーフローを避ける。また、innerとouterのtailを使いオーバフローを避ける。ポインタの操作はアトミックに行い複数のプロデューサー、コンスーマーが同時にアクセスしようとしても正しく動作するようにする。
NVLinkはアトミックなアクセスができるので、このようなポインタ操作は簡単であるが、アトミックなメモリアクセスをサポートしていないPCIeでこれを実現するのは容易ではない。
NVLinkの場合、コンスーマーの数を増やすのは容易である。性能的に制約になるのはキューとコンスーマーの間のメモリバンド幅である。
次のグラフは横軸がGPUの数で、縦軸が使われたメモリバンド幅である。
次のグラフはキューアクセスの競合による制約を示すもので、横軸はコンスーマー数である。最初はコンスーマーが増えるとスループットは上がっていくが、60~70コンスーマーがベストで、それ以上になるとキューアクセスの競合のためにスループットが下がってしまう。多数のGPUがキューのheadをアクセスするのでメモリが飽和してしまうからである。
この問題は、キューアクセスができなかったときに、ただちにキューアクセスを繰り返すのではなく、バックオフ時間を入れて、しばらく待ってから次回のアクセスを試みるという方法を取ることで改善できる。次のグラフはパケットの処理時間はゼロで、キューが取れなかった場合は66μsのバックオフを入れた場合のスループットを示すものである。前のグラフと比較すると、100コンスーマー以上のところではスループットの低下が抑えられている。しかし、不必要にバックオフ時間を大きくすると処理のレーテンシが大きくなってしまうという問題がある。
他のGPUのメモリのアクセスがローカルのアクセスのように見なせるのはNVSwitchのお蔭である。しかし、NVSwitchの総バンド幅は2TB/sであるし、もし、全部のGPUが1つのGPUと通信しようとする場合は137GB/sのバンド幅を全部のGPUで分け合うことになってしまう。
また、NVLinkのバンド幅の問題ではなく、1つのメモリアドレスへのアクセスの競合が問題を引き起こす場合もある。何しろ、260万スレッドあるのだから、メモリアクセスの競合がひどくなることは十分にあり得る。
高い性能を実現するためにはスレッドやデータをすべてのGPUに分散することが重要である。データを1つのストレージGPUだけに格納するのではなく、全部のGPUに分散して格納すればメモリアクセスの競合が減少する。また、スレッドを全GPUに分散すれば一部の計算GPUにトラフィックが集中してしまうことも無い。
DGX-2のユニファイド仮想メモリは、複数GPUに跨る単純なメモリモデルを実現してくれる。しかし、リモートメモリのトラフィックはL1キャッシュを使用する。VoltaはSMごとに128KBのL1キャッシュを持っているが、このキャッシュはコヒーレントではなく、書き込みの後、一貫性を保つためにはfence命令を使う必要がある。
ユニファイドメモリは便利であるが、ローカルやリモートのメモリを明示的に管理することにより、NVSwitchに頼るよりも性能を改善できる場合もある。
結論
NVLinkのファブリックはDGX-2を単なる16GPUのマシン以上のものにしている。これが単に16個のGPUを並べただけのマイニング用のマシンとDGX-2の違いである。DGX-2は優れたストロングスケーリングができるマシンである。
また、マルチGPUのプログラミングのオーバヘッドが小さく、ナイーブなコードでもうまく動く。その点で少ない努力でも報酬が大きいマシンであると言える。
ユニファイド仮想メモリは全GPUのメモリを1つのリニアなメモリとして見せてくれて使いやすい。
NVIDIAのDGX-2は16個のVolta GPUを搭載し、強力な演算能力を持っている点に目が行きやすいが、この発表で強調したように、NVLinkとNVSwitchによる16GPU共通のリニアなメモリモデルの提供は、プログラムの作り易さの点で画期的な改善である。また、ストロングスケールがやり易く、ナイーブなコードでも性能が出る点もプログラミングのやり易さに貢献している。