先日、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