このページは Cloud Translation API によって翻訳されました。
Switch to English

TensorFlowプロファイラーを使用してTensorFlowGPUパフォーマンスを最適化する

概要概要

このガイドは、GPUを使用してモデルのパフォーマンスを向上させるTensorFlowユーザーを対象としています。このガイドは、パフォーマンスに関する洞察を得るためのメインツールとしてTensorFlow Profilerを使用しており、1つ以上のGPUが十分に活用されていない場合のデバッグに役立ちます。 TensorFlowプロファイラーのクイックスタートガイドはTensorFlowプロファイラーチュートリアルにあり、プロファイルを取得する追加の方法は、プロファイラーガイドを使用したTensorFlowパフォーマンス最適化に記載されています。

計算をGPUにオフロードすることは、特に小さなモデルの場合、必ずしも有益であるとは限らないことに注意してください。ホスト(CPU)とデバイス(GPU)の間のデータ転送によるオーバーヘッドと、ホストがGPUカーネルを起動する際の遅延によるオーバーヘッドがあります。ホストが十分な作業をオフロードすることによってGPUを正常に占有し続けると、良好なパフォーマンスが達成されます。

パフォーマンス最適化ワークフロー

このガイドでは、単一のGPUから始めて、複数のGPUを備えた単一のホストに移行することでパフォーマンスの問題をデバッグする方法の概要を説明します。この順序でパフォーマンスの問題をデバッグすることをお勧めします。たとえば、TensorFlow分散戦略を使用して、複数のGPUを備えた単一のホストでモデルをトレーニングし、GPUの使用率が最適でないことに気付いた場合は、マルチGPUシステムをデバッグする前に、まず1GPUのパフォーマンスを最適化してデバッグする必要があります。推奨される順序は次のとおりです。

  1. 1GPUでのパフォーマンスの最適化とデバッグ
    1. 入力パイプラインがボトルネックであるかどうかを確認します
    2. 1GPUのデバッグパフォーマンス
    3. fp16を有効にし、オプションでXLAを有効にします
  2. マルチGPUシングルホストでのパフォーマンスの最適化とデバッグ

GPUでパフォーマンスの高いコードを取得するためのベースラインとして、このガイドでは、すでにtf.functionを使用していることを前提としています。 tf.functionコンパイル/フィットAPIは、 tf.function自動的に利用します。カスタムトレーニングループを作成するときは、 tf.functionを有効にする方法についてこのガイドを参照してください

次のセクションでは、パフォーマンスのボトルネックを特定して修正するために、上記の各シナリオで推奨されるアプローチについて説明します。

1GPUでパフォーマンスを最適化

理想的なケースでは、プログラムのGPU使用率が高く、CPU(ホスト)からGPU(デバイス)への通信が最小限であり、入力パイプラインからのオーバーヘッドがない必要があります。パフォーマンスを分析するための最初のステップは、1つのGPUで実行されているモデルのプロファイルを取得することです。

TensorFlowプロファイラーの概要ページには、プログラムが理想的なシナリオからどれだけ離れているかがわかります。

TensorFlow Profiler Overview Page

概要ページで探すべきキー番号は次のとおりです。

  1. 実際のデバイス実行からのステップ時間はどれくらいですか
  2. デバイスとホストに配置された操作の割合
  3. fp16を使用するカーネルの数

最適なパフォーマンスを達成するということは、3つのケースすべてでこれらの数値を最大化することを意味します。プログラムを深く理解するには、TensorFlowProfilerトレースビューアに精通している必要があります。以下のセクションでは、パフォーマンスのボトルネックを診断するときに探す必要のある一般的なトレースビューアのパターンをいくつか示します。

以下は、1GPUで実行されているモデルトレースビューの画像です。 Tensorflow名前スコープTensorflowオプスのセクションからは、あなたが、往路と同様に、損失関数をモデルの異なる部分を特定し、復路/勾配の計算、およびオプティマイザ重み更新することができます。また、CUDAストリームを参照する各ストリームの横にあるGPUで実行されている操作を確認できます。各ストリームは特定のタスクに使用されます。このトレースでは、 Stream#118を使用して、計算カーネルとデバイス間のコピーを起動します。 Stream#119はホストからデバイスへのコピーに使用され、 Stream#120はデバイスからホストへのコピーに使用されます。

image

このトレースは、パフォーマンスモデルの一般的な特性を示しています。たとえば、GPU計算タイムライン( Stream#118 )は、ギャップがほとんどなく、ビジーに見えます。ホストからデバイス(ストリーム#119 )およびデバイスからホスト(ストリーム#120 )へのコピーは最小限であり、ステップ間のギャップも最小限です。プログラムに対してTensorFlowプロファイラーを実行すると、トレースビューにこれらの理想的な特性が表示されない場合があります。このガイドの残りの部分では、一般的なシナリオとそれらを修正する方法について説明します。

入力パイプラインのデバッグ

GPUパフォーマンスデバッグの最初のステップは、プログラムが入力にバインドされているかどうかを判断することです。これを理解する最も簡単な方法は、TensorFlowプロファイラーの入力パイプラインアナライザーを使用することです。これにより、入力パイプラインで費やされた時間の概要がわかります。

image

入力パイプラインがステップ時間に大きく寄与する場合に実行できる可能性のあるアクションは次のとおりです。

  • 入力パイプラインをデバッグする方法については、 tf.data固有のガイドを参照してください。
  • 入力パイプラインがボトルネックであるかどうかを確認するもう1つの簡単な方法は、前処理を必要としないランダムに生成された入力データを使用することです。これは、ResNetモデルにこの手法を使用する例です。入力パイプラインが最適である場合、実際のデータと生成されたランダム/合成データで同様のパフォーマンスが見られるはずです。合成データの場合の唯一のオーバーヘッドは、入力データのコピーによるものであり、これもプリフェッチして最適化できます。

1GPUのデバッグパフォーマンス

GPU使用率の低下に寄与する可能性のあるいくつかの要因があります。以下は、トレースビューアと考えられる解決策を検討するときに一般的に観察されるいくつかのシナリオです。

ステップ間のギャップを分析する

プログラムが最適に実行されていない場合の一般的な観察は、トレーニングステップ間のギャップです。下の画像では、ステップ8と9の間に大きなギャップがあります。これは、その間、GPUがアイドル状態であることを意味します。

image

トレースビューアにステップ間に大きなギャップが表示される場合、これはプログラムが入力バウンドであることを示している可能性があります。その場合、まだ行っていない場合は、入力パイプラインのデバッグに関する前のセクションを参照する必要があります。ただし、最適化された入力パイプラインを使用しても、CPUスレッドの競合により、あるステップの終了と別のステップの開始の間にギャップが見られる場合があります。 tf.dataは、バックグラウンドスレッドを利用してパイプライン処理を並列化します。これらのスレッドは、データのコピーやGPU操作のスケジューリングなど、各ステップの開始時に発生するGPUホスト側のアクティビティに干渉する可能性があります。

GPUでこれらの操作をスケジュールするホスト側に大きなギャップがある場合は、環境変数TF_GPU_THREAD_MODE=gpu_private設定できます。これにより、GPUカーネルが独自の専用スレッドから起動され、 tf.data作業の背後でキューに入れられることがtf.dataます。

ステップ間のギャップは、メトリック計算、Kerasコールバック、またはホスト上で実行されるtf.function外のtf.functionによっても発生する可能性があります。これらの操作は、TensorFlowグラフ内の操作ほど優れたパフォーマンスを発揮しません。さらに、これらの操作の一部はCPUで実行され、GPUからテンソルを前後にコピーします。

入力パイプラインを最適化した後でも、トレースビューアでステップ間のギャップに気付いた場合は、ステップ間のモデルコードを調べて、コールバック/メトリックを無効にするとパフォーマンスが向上するかどうかを確認する必要があります。これらの操作の詳細の一部は、トレースビューアー(デバイス側とホスト側の両方)にもあります。このシナリオでの推奨事項は、すべてのステップではなく、固定数のステップの後に実行することにより、これらの操作のオーバーヘッドを償却することです。 tf.keras APIでcompileメソッドを使用する場合、 experimental_steps_per_executionフラグを設定するとこれが自動的に行われます。カスタムトレーニングループの場合は、 tf.while_loop使用しtf.while_loop

より高いデバイス使用率を実現

小さなGPUカーネルとホストカーネルの起動遅延

ホストはGPUで実行されるカーネルをキューに入れますが、カーネルが実際にGPUで実行されるまでには、待ち時間(約20〜40μs)が伴います。理想的なケースでは、ホストがGPUに十分な数のカーネルをエンキューするため、GPUは、ホストがより多くのカーネルをエンキューするのを待つのではなく、ほとんどの時間を実行に費やします。

TensorFlowプロファイラーの概要ページには、ホストがカーネルを起動するのを待っているためにGPUがアイドル状態だった時間が表示されます。下の画像では、GPUは、カーネルの起動を待機しているステップ時間の約10%の間アイドル状態です。

image

この同じプログラムのトレースビューアは、ホストがGPUでカーネルを起動するのに忙しいカーネル間の小さなギャップを示しています。

image

GPUで多くの小さな操作(たとえば、スカラー加算など)を起動すると、ホストがGPUに追いついていない可能性があります。 Tensorflow統計同じTensorFlowプロフィールショー2.77秒を取っ126224のムル操作のためのページ。したがって、各カーネルは約21.9μsであり、これは非常に小さく(起動待ち時間とほぼ同じ時間)、ホストカーネルの起動遅延が発生する可能性があります。

image

トレースビューアに、上の画像のようにGPUの操作間に多くの小さなギャップが表示される場合は、次のことができます。

  • 小さなテンソルを連結し、ベクトル化された操作を使用するか、より大きなバッチサイズを使用して、起動された各カーネルがより多くの作業を実行できるようにします。これにより、GPUがより長くビジー状態になります。
  • tf.functionを使用してTFグラフを作成し、純粋なtf.keras.Model.compileモードで操作を実行していないことを確認してください( tf.keras.Model.compileを使用tf.keras.Model.compile自動的にこれが行われます)。
  • XLAを使用してカーネルを融合します。詳細については、XLAを有効にしてパフォーマンスを向上させる方法に関する以下のセクションを参照してください。これは実験的な機能ですが、デバイスの使用率が高くなります。
TensorflowOpの配置

TensorFlowプロファイラーの概要ページには、ホストとデバイスに配置された操作の割合が表示されます(トレースビューアを確認して、特定の操作の配置を確認することもできます)。以下の画像のように、ホスト上のopsの割合をデバイスと比較して非常に小さくする必要があります。

image

理想的には、計算集約型の操作のほとんどはGPUに配置する必要があります。モデル内の操作とテンソルが割り当てられているデバイスを見つけるには、プログラムの最初のステートメントとしてtf.debugging.set_log_device_placement(True)を設定します。場合によっては、特定のデバイスに配置するopを指定した場合でも、その実装がこの条件をオーバーライドする可能性があることに注意してください(例: tf.unique )。シングルGPUトレーニングの場合でも、 tf.distribute.OneDeviceStrategyなどの配布戦略を指定すると、デバイス上でのopsの配置がより確定的になる可能性があります。

オペレーションの大部分をGPUに配置する理由の1つは、ホストとデバイス間の過剰なメモリコピーを防ぐためです(ホストとデバイス間のモデル入力/出力データのメモリコピーが必要です)。過剰なコピーの例は、GPUストリーム#167#168 、および#169の以下のトレースビューで確認できます。

image

これらのコピーは、GPUカーネルの実行をブロックすると、パフォーマンスが低下する場合があります。トレースビューアのメモリコピー操作には、これらのコピーされたテンソルのソースであるopsに関する詳細情報がありますが、memCopyをopに関連付けるのは必ずしも簡単ではない場合があります。このような場合、近くのopsを調べて、すべてのステップでメモリコピーが同じ場所で発生するかどうかを確認すると便利です。

GPU上のより効率的なカーネル

プログラムのGPU使用率が許容範囲内になったら、次のステップは、Tensorコアを利用するかopsを融合することにより、GPUカーネルの効率を高めることを検討することです。

テンソルコアの利用

最新のGPUには、適格なカーネルのパフォーマンスを大幅に向上させることができる特殊なテンソルコアがあります。 GPUカーネル統計ページは、どのGPUカーネルがTensor Coreに適格であり、どのカーネルがTensorCoreを使用しているかを示します。 fp16を有効にする(以下の「混合精度の有効化」セクションを参照)は、プログラムのGeneral Matrix Multiply(GEMM)カーネル(matmul ops)がTensorCoreを利用するようにする1つの方法です。

GPUでカーネルを効率的にする方法に関するその他の詳細な推奨事項については、 NVIDIAディープラーニングパフォーマンスガイドを参照してください。このガイドでは、NCHWとNHWCの形式を使用して入力を表す、入力の次元を次のようにするなど、さまざまな手法を試すことができます。 8の倍数。

融合操作

tf.xla.xperimental_compile機能を使用すると、TensorFlowは小さな操作を融合して大きなカーネルを形成し、パフォーマンスを大幅に向上させることができます。詳細については、以下の「XLAの有効化」セクションで説明します。

fp16とXLAを有効にする

上記の手順を実行した後、混合精度とXLAを有効にすることは、パフォーマンスをさらに向上させるために実行できる2つのオプションの手順です。推奨されるアプローチは、それらを1つずつ有効にして、パフォーマンス上の利点が期待どおりであることを確認することです。

混合精度の有効化

TensorFlow Mixed Precisionガイドは、GPUでfp16精度を有効にする方法を示しています。 fp16のパフォーマンス上の利点を実現する場合、覚えておくべきいくつかのポイントがあります。

最適なfp16カーネルの使用

fp16を有効にすると、プログラムの行列乗算(GEMM)カーネルは、Tensorコアを利用する対応するfp16バージョンを使用する必要があります。ただし、場合によっては、これが発生せず、プログラムが非効率的な実装にフォールバックするため、fp16を有効にすることで期待されるスピードアップが見られません。

image

GPUカーネル統計ページには、どのopsがTensor Coreに適格であり、どのカーネルが実際に効率的なTensorCoreを使用しているかが表示されます。 ディープラーニングのパフォーマンスに関するNVIDIAガイドには、Tensorコアを活用する方法に関する追加の提案が含まれています。さらに、fp16の利点は、以前はメモリにバインドされていたカーネルでも示されます。これは、操作に半分の時間がかかるためです。

動的損失スケーリングと静的損失スケーリング

fp16を使用する場合は、精度が低いことによるアンダーフローを防ぐために、損失スケーリングが必要です。損失スケーリングには動的と静的の2種類があり、どちらも混合精度ガイドで詳しく説明されています。パフォーマンスを最適化しようとする場合、動的損失スケーリングにより、ホスト上で実行される追加の条件付き操作が導入され、トレースビューアーのステップ間にギャップが表示される可能性があることを覚えておくことが重要です。一方、静的損失スケーリングにはそのようなオーバーヘッドがなく、正しい静的損失スケール値を指定する必要があるキャッチのパフォーマンスの観点から、より良いオプションになる可能性があります。

XLAの有効化

単一のGPUで最高のパフォーマンスを実現するための最後のステップとして、XLAを有効にして実験することができます。これにより、操作が融合され、デバイスの使用率が向上し、メモリフットプリントが削減されます。プログラムでXLAを有効にする方法の詳細については、「 XLA:機械学習用コンパイラの最適化」ガイドを参照してください。

マルチGPUシングルホストでのパフォーマンスの最適化

tf.distribute.MirroredStrategy APIを使用して、モデルトレーニングを1つのホスト上の1つのGPUから複数のGPUにスケーリングできます。 Tensorflowを使用して分散トレーニングを行う方法の詳細については、Kerasを使用した分散トレーニングガイドを参照してください。 1つのGPUから複数のGPUへの移行は、理想的には箱から出してスケーラブルである必要がありますが、パフォーマンスの問題が発生する場合があります。

単一のGPUを使用したトレーニングから同じホスト上の複数のGPUに移行する場合、理想的には、勾配通信の追加のオーバーヘッドとホストスレッドの使用率の増加のみでパフォーマンスがスケーリングするはずです。このオーバーヘッドのため、たとえば1から2 GPUに移動した場合、正確な2倍のスピードアップは見られません。以下のトレースビューは、複数のGPUでトレーニングする場合の追加の通信オーバーヘッドの例を示しています。グラデーションを連結し、レプリカ間で通信し、重みの更新を行う前に分割するためのオーバーヘッドがあります。

image

次のチェックリストは、マルチGPUシナリオでパフォーマンスを最適化するときにパフォーマンスを向上させるのに役立ちます。

  1. バッチサイズを最大化するようにしてください。これにより、デバイスの使用率が高くなり、複数のGPU間の通信コストが償却されます。メモリプロファイラーを使用すると、プログラムがメモリ使用率のピークにどれだけ近いかを把握するのに役立ちます。バッチサイズを大きくすると収束に影響を与える可能性がありますが、通常、これはパフォーマンス上の利点よりも重要であることに注意してください。
  2. 単一のGPUから複数のGPUに移行する場合、同じホストでより多くの入力データを処理する必要があります。したがって、(1)の後で、入力パイプラインのパフォーマンスを再チェックし、ボトルネックになっていないことを確認することをお勧めします。
  3. プログラムのトレースビューでGPUタイムラインをチェックして、不必要なAllReduce呼び出しがあるかどうかを確認します。これにより、すべてのデバイス間で同期が行われます。上記のトレースビューでは、AllReduceはNCCLカーネルを介して実行され、各ステップのグラデーションに対して各GPUでNCCL呼び出しが1つだけあります。
  4. 不要なD2H、H2D、およびD2Dコピー操作をチェックし、それらを最小化できるかどうかを確認します。
  5. ステップ時間をチェックして、各レプリカが同じ作業を行っていることを確認します。ホストが誤ってより多くの作業を行ってしまうために、1つのGPU(通常はGPU0)がオーバーサブスクライブされる可能性があります。
  6. 最後に、トレースビューですべてのGPUのトレーニング手順を確認して、連続して実行されている操作を確認します。これは通常、プログラムに1つのGPUから別のGPUへの制御の依存関係が含まれている場合に発生します。この状況でのデバッグパフォーマンスは、過去にケースバイケースで解決されてきました。プログラムでこの動作を観察した場合は、トレースビューの画像を使用してGithubの問題報告してください。

グラデーションAllReduceを最適化する

同期戦略でトレーニングする場合、各デバイスは入力データの一部を受け取ります。モデルを通過する順方向および逆方向のパスを計算した後、各デバイスで計算された勾配を集計して縮小する必要があります。この勾配AllReduceは、各デバイスでの勾配計算の後、オプティマイザーがモデルの重みを更新する前に発生します。各GPUは、最初にモデルレイヤー間でグラデーションを連結し、 tf.distribute.CrossDeviceOpstf.distribute.NcclAllReduceがデフォルト)を使用してGPU間でグラデーションを通信し、レイヤーごとに縮小した後にグラデーションを返します。オプティマイザーは、これらの減少した勾配を使用して、モデルの重みを更新します。理想的には、オーバーヘッドを防ぐために、このプロセスはすべてのGPUで同時に実行する必要があります。 AllReduceまでの時間は、ほぼ同じである必要があります。

(number of parameters * 4bytes)/ (communication bandwidth)

この計算は、分散トレーニングジョブの実行時に表示されるパフォーマンスが期待どおりであるかどうか、またはさらにパフォーマンスのデバッグを行う必要があるかどうかをすばやく確認するのに役立ちます。モデル内のパラメーターの数は、 tf.keras.Model.summaryからtf.keras.Model.summaryます。

Tensorflowはfp32を使用して勾配を通信するため、各モデルパラメーターは4バイトであることに注意してください。 fp16を有効にしている場合でも、NCCLAllReduceはfp32パラメーターを利用します。将来的には、Tensorflowはfp16を使用したAllReduce操作をサポートし、勾配AllReduceをパイプライン化して、勾配計算とオーバーラップするようにします。

スケーリングの利点を確認するには、これらのオーバーヘッドと比較してステップタイムをはるかに長くする必要があります。これを実現する1つの方法は、バッチサイズはステップ時間に影響しますが、通信オーバーヘッドには影響しないため、より高いバッチサイズを使用することです。

GPUホストスレッドの競合

複数のGPUを実行する場合、CPUの役割は、デバイス間でGPUカーネルを効率的に起動することにより、すべてのデバイスをビジー状態に保つことです。ただし、CPUが1つのGPUでスケジュールできる独立した操作が多数ある場合、CPUは、ホストスレッドを多数使用して、1つのGPUをビジー状態に保ち、別のGPUでカーネルを非決定的な順序で起動することを決定できます。 。これにより、スキューまたは負のスケーリングが発生し、パフォーマンスが低下する可能性があります。

以下のトレースビューアは、GPU1がアイドル状態であり、GPU2の起動後に操作を開始するため、CPUがGPUカーネルの起動を非効率的にずらした場合のオーバーヘッドを示しています。

image

ホストのトレースビューは、ホストがGPU1でカーネルを起動する前にGPU2でカーネルを起動していることを示しています(以下のtf_Compute *操作はCPUスレッドを示していないことに注意してください)。

image

プログラムのトレースビューにGPUカーネルのこの驚異的な違いが見られる場合、推奨されるアクションは次のとおりです。

  • TensorFlow環境変数TF_GPU_THREAD_MODEgpu_private設定します。この環境変数は、GPUのスレッドをプライベートに保つようにホストに指示します。
  • デフォルトでは、 TF_GPU_THREAD_MODE=gpu_privateはスレッド数を2に設定します。これは、ほとんどの場合十分です。ただし、その数は、TensorFlow環境変数TF_GPU_THREAD_COUNTを目的のスレッド数に設定することで変更できます。