Multi-GPU system with ExpEther


Multi-GPU system with ExpEther/注意事項

とりあえずここで言いたいことは、実機評価@NECでは油断するな、ということ。

どのGPUが外付けなの?

Express system@NECのホストのPCIeスロットには、 ExpEther NICの他にディスプレイ出力用のNVIDIA製GPUが1つ挿さっている。 ユーザからは外付けGPUもホストマシンに内蔵しているように見えてしまうため、 ユーザには外付けGPUsと内臓GPUが見えることになる。

試しにnvidia-smiを実行すると以下のようになる。 この環境では、Tesla C2050がホストに直挿しされていて、他のTesla K20はすべて外付けされている。

$ nvidia-smi

よく見ると、GPU名の左横になにやらIDのようなものが振ってあることがわかる。 これを見て、「Tesla C2050は使わないから、GPU 0以外を使うようにしよう!」と考えてしまいがちだが、これは罠である。

GPU IDはdeviceQueryで確認しなければならない。

$ /usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery

deviceQueryにより、実際は次のようにGPU IDが振られていることが確認できる。

  • GPU 0: Tesla K20
  • GPU 1: Tesla C2050
  • GPU 2: Tesla K20
  • GPU 3: Tesla K20
  • GPU 4: Tesla K20

どうやらNVIDIAは、シングルGPU実行時では自動的に最新(もしくは最高性能)のGPUを優先的に使うようにしているらしい。

以上により、外付けのGPUは0, 2, 3, 4番であるとわかった。

どうやってGPUを指定するの?

"どのGPUが外付けなの?"の環境を仮定して、サンプルを提示する。

基本的に以下のような関数をプログラムの冒頭で1回だけ行えば良い。 ここで行われるCUDA runtime関数は意外と重たいので注意。

static int id_table[] = { 0, 2, 3, 4 }; // 使いたいGPU IDを列挙する
void InitializeDevices(int num_gpus) {
  for (int i = 0; i < num_gpus; ++i) {
    for (int j = 0; j < num_gpus; ++j) {
      int gpu_id = id_table[i];
      int peer_id = id_table[j];
      if (gpu_id == peer_id) continue;
      cudaSetDevice(gpu_id);
      int can_access_peer = 0;
      cudaDeviceCanAccessPeer(&can_access_peer, gpu_id, peer_id);
      if (can_access_peer == 0) continue;
      printf("Enabling peer access to GPU %d from GPU %d\n", peer_id, gpu_id);
      cudaDeviceEnablePeerAccess(peer_id, 0);
    }
  }
}
  • id_table
    • この配列に使いたいGPUのIDを列挙する
  • cudaSetDevice
    • 「今からgpu_id番のGPUを使いますよ」という合図
  • cudaDeviceCanAccessPeer
    • gpu_id番のGPUとpeer_id番のGPUがpeer accessできるかどうか調べ、結果をcan_access_peerに書き込む
  • cudaDeviceEnablePeerAccess
    • セットされているGPUとpeer_id番のGPUのpeer accessを許可する
    • お互いのGPUから行う必要がある

上記のやり方だけでなく、設定ファイルみたいのを作ってそれをプログラムに食わせたりすると楽かも。

カーネル内で他GPUのglobal memoryへ直接アクセスしないほうが良い

  • アクセスがwarp単位でコマ目に行われ、そのたびにEthernetフレームのヘッダが付加される
    • warp単位のアクセス量 = 32 x sizeof(Type) = 最大256Byte
    • Ethernetフレームには46〜1500Byteのデータが格納可能(46Byte以下のデータはパディング)
  • 計算と通信のオーバラップが難しい

pinned memoryは使わないほうが良い

  • pinned memory: GPUからも直接見ることができるホストメモリ領域
    • pinned memoryへのアクセス = ホストとデバイスとの通信
  • 帯域の狭いネットワークを通ってアクセスするのでスループットがでない
  • 複数のGPUが同時に1つのホストにアクセスしようとすると衝突が起こる

実機@NECでプログラムが正しく動かない時

  • 基本的に同期 cudaDeviceSynchronize または cudaStreamSynchronize がうまくとれていないことが考えられる
    • common systemで開発を行っていると起こりがちな問題
    • Express systemはcommon systemの約5倍の通信レイテンシであることに注意
    • 通信後はちゃんと各GPUで同期 cudaDeviceSynchronize, cudaStreamSynchronize をやるべし

プログラム実行中にフリーズする

  • ExpEtherは熱に弱いらしく、熱くなると固まる
  • なんだか様子がおかしいときは手遅れにならないうちに電源ボタン長押し

外付けGPUを使うプログラムの実行がやたらと遅い場合がある

  • nvidia-smiやdeviceQueryも遅くなる
  • 小さな処理でも約13秒くらい時間がかかったりする
  • プログラムのwakeupが遅い?
  • このことを見越して評価をとらないとタイムオーバーになりかねない

written by mits(2015-02-14)


添付ファイル: filenvidia-smi.txt 142件 [詳細] filedeviceQuery.txt 141件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2019-08-29 (木) 00:03:47