TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

CUDAマルチストリーム対応

先日、CUDAのメモリの非同期転送に対応したことを記事にした。
その際に、マルチストリームに対応することで、転送だけではなく演算処理も並列化できることを書いた。

その後、マルチストリームの実装を行い、2GPUでは、初期局面でNPSが36%向上することを確認したが、8GPUではほとんど変わらないかかえって遅くなることがわかった。

原因を推測すると、8GPUのサーバは、CPUとGPU間は、PCI Expressで直結でなく、PCIeスイッチを介して接続されており*1、PCIeスイッチの帯域がボトルネックになっていのではないかと考えた。
PCIe4.0x16の帯域(256Gbps)の4倍までに制限されるため、1,2GPUだと速くなり、8GPUだと遅くなる理由としては納得できる。

対策

帯域がボトルネックとすると、以下の対策が有効である。
(1) GPUごとのストリーム数を、探索スレッド数と同じにするのではなく、2つに制限する
(2) CPUからGPUに転送する際のデータ型をFP16にする

測定

それぞれ対策した場合の8GPUでのNPSを測定した。
モデルは第2回電竜戦のdlshogiの15ブロックのモデルを使用した。

floodgateからサンプリングした100局面を1秒思考した際のNPSを3回測定して平均を算出した。
対策(1)のみは結果が良くなかったので1回のみ測定した。

シングルストリーム 対策(1) 対策(1)+(2)
平均値 290579 295309(101.5%) 344963(118.4%)
中央値 293918 294581(100.7%) 345824(117.5%)
最小値 207550 220396(85.7%) 240279(102.6%)
最大値 341644 361907(113.9%) 434519(135.2%)

※()内の%は、シングルストリームに対する比である。

対策(1)のみでは、平均、中央値ではNPSは変わっておらずマルチストリームの効果はでていない。
GPUあたりのストリームが2でも、帯域がボトルネックになっているようだ。

対策(1)+(2)では、平均で18.4%、中央値で17.5%NPSが向上した。
対策(2)を行うことで、CPUからGPUに転送前にFP16に変換して、また結果をFP16で転送してCPUでFP32にすることで、転送のデータ量が半分になる。
NPSが向上したことから、やはりCPU<->GPU間の帯域がボトルネックとなっていたと考えられる。

まとめ

マルチストリームに対応し、CPU<->GPU間をFP16で転送することで、NPSが平均で18.4%向上した。
レーティングがどれくらい向上するかは別途計測予定である。

マルチストリーム対応版のソース:
GitHub - TadaoYamaoka/DeepLearningShogi at feature/multi_stream
※実験用のためFP16決め打ちになっている

2つのストリームを複数スレッドで共有するため、セマフォ排他制御した。
C++セマフォは標準で対応しているのは、C++20からであったので、mutexとcondition_variableを使って実装した。
参考:c++ - C++0x has no semaphores? How to synchronize threads? - Stack Overflow