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

TFプロファイラーを使用したtf.dataパフォーマンスの分析

概観

このガイドは、TensorFlow プロファイラtf.data精通していることを前提としていtf.data 。これは、ユーザーが入力パイプラインのパフォーマンスの問題を診断して修正するのに役立つ例と、段階的な指示を提供することを目的としています。

まず、TensorFlowジョブのプロファイルを収集します。その方法の説明は、 CPU / GPUCloud TPUで利用できます。

TensorFlow Trace Viewer

以下で詳しく説明する分析ワークフローは、プロファイラーのトレースビューアーツールに焦点を当てています。このツールは、TensorFlowプログラムによって実行される演算の期間を示すタイムラインを表示し、実行に最も時間がかかる演算を特定できるようにします。トレースビューアーの詳細については、TFプロファイラーガイドのこのセクションをご覧ください。通常、 tf.dataイベントはホストCPUタイムラインに表示されます。

分析ワークフロー

以下のワークフローに従ってください。改善に役立つフィードバックがある場合は、「comp:data」というラベルの付いたgithubの問題作成してください。

1. tf.dataパイプラインは十分な速度でデータを生成していますか?

まず、入力パイプラインがTensorFlowプログラムのボトルネックになっているかどうかを確認します。

これを行うには、トレースビューアーでIteratorGetNext::DoCompute opsを探します。一般に、ステップの開始時にこれらが表示されることを期待しています。これらのスライスは、入力パイプラインが要求されたときに要素のバッチを生成するのにかかる時間を表します。 kerasを使用している場合、またはtf.functionデータセットを反復処理しているtf.function 、これらはtf_data_iterator_get_nextスレッドにあります。

あなたが使用している場合ことを注意流通戦略を 、あなたが見ることがIteratorGetNextAsOptional::DoCompute代わりのイベントIteratorGetNext::DoCompute (TF 2.3のように)。

image

呼び出しがすぐに戻る場合(<= 50 us)、これは、要求されたときにデータが利用可能であることを意味します。入力パイプラインはボトルネックではありません。より一般的なパフォーマンス分析のヒントについては、 プロファイラーガイドを参照してください。

image

呼び出しの tf.data遅い場合、 tf.dataはコンシューマーの要求に対応できません。次のセクションに進みます。

2.データをプリフェッチしていますか?

入力パイプラインのパフォーマンスのベストプラクティスは、 tf.dataパイプラインの最後にtf.data.Dataset.prefetch変換を挿入することtf.data.Dataset.prefetch 。この変換は、入力パイプラインの前処理計算をモデル計算の次のステップとオーバーラップさせ、モデルのトレーニング時に入力パイプラインのパフォーマンスを最適化するために必要です。データをプリフェッチしている場合は、 IteratorGetNext::DoCompute opと同じスレッドにIterator::Prefetchスライスが表示されるはずです。

image

パイプラインの最後にprefetchがない場合は、 prefetch追加する必要がありますtf.dataパフォーマンスの推奨事項の詳細については、 tf.dataパフォーマンスガイドを参照してください

すでにデータをプリフェッチしていても、まだ入力パイプラインがボトルネックである場合は、次のセクションに進んで、パフォーマンスをさらに分析します。

3. CPU使用率が高くなっていますか?

tf.dataは、利用可能なリソースを最大限に活用しようとすることで、高スループットを実現します。一般に、GPUやTPUなどのアクセラレータでモデルを実行する場合でも、 tf.dataパイプラインはCPUで実行されます。 sarhtopなどのツールを使用して、またはGCPで実行している場合はクラウドモニタリングコンソールで使用状況を確認できます。

使用率が低い場合は 、入力パイプラインがホストCPUを十分に活用していない可能性があることを示しています。ベストプラクティスについては、 tf.dataパフォーマンスガイドを参照してください。ベストプラクティスを適用しても使用率とスループットが低いままの場合は、以下のボトルネック分析に進みます。

使用率がリソース制限近づいている場合 、パフォーマンスをさらに向上させるには、入力パイプラインの効率を向上させる(たとえば、不要な計算を回避する)か、計算をオフロードする必要があります。

tf.data不要な計算を回避することにより、入力パイプラインの効率を向上させることができます。これを行う1つの方法は、データがメモリに収まる場合、計算集約的な作業の後にtf.data.Dataset.cache変換を挿入することです。これにより、メモリ使用量が増加するという犠牲を払って計算が削減されます。さらに、 tf.dataでのtf.data内並列処理を無効にすると、効率が10%を超える可能性があり、入力パイプラインで次のオプションを設定することで実行できます。

dataset = ...
options = tf.data.Options()
options.experimental_threading.max_intra_op_parallelism = 1
dataset = dataset.with_options(options)

4.ボトルネック分析

次のセクションでは、トレースビューアーでtf.dataイベントを読み取って、ボトルネックの場所と考えられる軽減戦略を理解する方法について説明します。

プロファイラーのtf.dataイベントについて

プロファイラーの各tf.dataイベントにはIterator::<Dataset>という名前が付けられます。ここで、 <Dataset>はデータセットソースまたは変換の名前です。各イベントには、ロングネームIterator::<Dataset_1>::...::<Dataset_n>もあります。これは、 tf.dataイベントをクリックすると表示できます。長い名前では、 <Dataset_n>は(短い)名前の<Dataset> <Dataset_n>一致し、長い名前の他のデータセットはダウンストリーム変換を表します。

image

たとえば、上記のスクリーンショットは次のコードから生成されました。

dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)

ここでは、 Iterator::MapイベントのロングネームはIterator::BatchV2::FiniteRepeat::Mapです。データセット名はpython APIとは少し異なる場合があります(たとえば、RepeatではなくFiniteRepeat)。ただし、解析するには直感的である必要があります。

同期および非同期の変換

同期tf.data変換( BatchMap )の場合、同じスレッドの上流変換からのイベントが表示されます。上記の例では、使用されるすべての変換が同期であるため、すべてのイベントが同じスレッドに表示されます。

非同期変換( PrefetchParallelMapParallelInterleaveMapAndBatch )の場合、上流の変換からのイベントは別のスレッドになります。そのような場合、「ロングネーム」は、イベントがパイプラインのどの変換に対応するかを識別するのに役立ちます。

image

たとえば、上記のスクリーンショットは次のコードから生成されました。

dataset = tf.data.Dataset.range(10)
dataset = dataset.map(lambda x: x)
dataset = dataset.repeat(2)
dataset = dataset.batch(5)
dataset = dataset.prefetch(1)

ここで、 Iterator::Prefetchイベントはtf_data_iterator_get_nextスレッドにあります。 Prefetchは非同期であるため、その入力イベント( BatchV2 )は別のスレッドにあり、ロングネームIterator::Prefetch::BatchV2検索することで検索できます。この場合、それらはtf_data_iterator_resourceスレッドにあります。その長い名前から、 BatchV2Prefetch上流にあると推測できます。さらに、 BatchV2イベントのparent_idは、 PrefetchイベントのIDと一致します。

ボトルネックの特定

一般に、入力パイプラインのボトルネックを特定するには、最外部の変換からソースまで、入力パイプラインをたどります。パイプラインの最後の変換から始めて、遅い変換が見つかるか、 TFRecordなどのソースデータセットに到達するまで、上流変換に再帰します。上記の例では、 Prefetchから開始し、上流に向かってBatchV2FiniteRepeatMap 、最後にRangeます。

一般に、遅い変換は、イベントは長いものの、入力イベントは短いものに対応します。以下にいくつかの例を示します。

ほとんどのホスト入力パイプラインの最後の(最も外側の)変換はIterator::Modelイベントであることに注意してください。モデル変換はtf.dataランタイムによって自動的に導入され、入力パイプラインパフォーマンスの計測と自動tf.data使用されます。

ジョブが配布戦略を使用している場合、トレースビューアーには、デバイス入力パイプラインに対応する追加のイベントが含まれます。デバイスパイプラインの最も外側の変換( IteratorGetNextOp::DoComputeまたはIteratorGetNextAsOptionalOp::DoCompute下にネストされている)は、上流のIterator::Generatorイベントを持つIterator::Prefetchイベントです。 Iterator::Modelイベントを検索すると、対応するホストパイプラインを見つけることができます。

例1

image

上記のスクリーンショットは、次の入力パイプラインから生成されます。

dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record)
dataset = dataset.batch(32)
dataset = dataset.repeat()

スクリーンショットで、(1) Iterator::Mapイベントは長いが、(2)入力イベント( Iterator::FlatMap )はすぐに戻ることにIterator::FlatMap 。これは、順次マップ変換がボトルネックであることを示唆しています。

スクリーンショットでは、 InstantiatedCapturedFunction::Runイベントがマップ関数の実行にかかる時間に対応していることに注意してください。

例2

image

上記のスクリーンショットは、次の入力パイプラインから生成されます。

dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.map(parse_record, num_parallel_calls=2)
dataset = dataset.batch(32)
dataset = dataset.repeat()

この例は上記に似ていますが、Mapの代わりにParallelMapを使用しています。ここでは、(1) Iterator::ParallelMapイベントが長いが、(2)入力イベントIterator::FlatMap (ParallelMapは非同期であるため、別のスレッドにある)が短いことにIterator::FlatMapます。これは、ParallelMap変換がボトルネックであることを示唆しています。

ボトルネックへの対処

ソースデータセット

TFRecordファイルからの読み取りなど、データセットソースをボトルネックとして特定した場合は、データ抽出を並列化することでパフォーマンスを向上させることができます。そのためには、データが複数のファイルにtf.data.Dataset.interleaveていることをtf.data.Dataset.interleaveし、 num_parallel_callsパラメーターをtf.data.experimental.AUTOTUNE設定してtf.data.experimental.AUTOTUNEtf.data.experimental.AUTOTUNEます。決定論がプログラムにとって重要でない場合は、TF 2.2 tf.data.Dataset.interleave deterministic=Falseフラグを設定するdeterministic=Falseにより、パフォーマンスをさらに改善できます。たとえば、TFRecordsから読み取る場合は、次の操作を実行できます。

dataset = tf.data.Dataset.from_tensor_slices(filenames)
dataset = dataset.interleave(tf.data.TFRecordDataset,
  num_parallel_calls=tf.data.experimental.AUTOTUNE,
  deterministic=False)

ファイルを開く際のオーバーヘッドを分散するために、シャーディングされたファイルは適度に大きくする必要があることに注意してください。並列データ抽出の詳細については、 tf.dataパフォーマンスガイドのこのセクションを参照してください。

変換データセット

中間のtf.data変換をボトルネックとして識別した場合は、変換を並列化するか、データがメモリに収まり適切な場合は計算をキャッシュすることで、これに対処できます。 Mapなどの一部の変換には、対応する変換があります。 tf.dataパフォーマンスガイドは 、これらを並列化する方法を示しています。 FilterUnbatchBatchなどの他の変換は、本質的に順次です。 「外部並列処理」を導入することで、それらを並列化できます。たとえば、入力パイプラインが最初は次のようになっていて、 Batchがボトルネックになっているとします。

filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)
dataset = filenames_to_dataset(filenames)
dataset = dataset.batch(batch_size)

分割された入力に対して入力パイプラインの複数のコピーを実行し、結果を組み合わせることで、「外部並列処理」を導入できます。

filenames = tf.data.Dataset.list_files(file_path, shuffle=is_training)

def make_dataset(shard_index):
  filenames = filenames.shard(NUM_SHARDS, shard_index)
  dataset = filenames_to_dataset(filenames)
  Return dataset.batch(batch_size)

indices = tf.data.Dataset.range(NUM_SHARDS)
dataset = indices.interleave(make_dataset,
                             num_parallel_calls=tf.data.experimental.AUTOTUNE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

追加のリソース