高性能な PyTorch はどのように実現されるのでしょうか?経験豊富な専門家がまとめた落とし穴を避ける10のヒント

高性能な PyTorch はどのように実現されるのでしょうか?経験豊富な専門家がまとめた落とし穴を避ける10のヒント

最小限の労力で最も効率的な PyTorch トレーニングを完了するにはどうすればよいでしょうか? PyTorch の使用経験が 2 年ある Medium ブロガーが最近、この点に関して 10 の誠実な提案を共有しました。

「効率的な PyTorch」のセクションでは、著者は I/O と CPU のボトルネックを特定して排除するためのヒントをいくつか提供しています。 2 番目の部分では、効率的なテンソル演算のためのいくつかのテクニックについて説明し、3 番目の部分では、効率的なモデルのデバッグ テクニックについて説明します。

この記事を読む前に、PyTorch についてある程度の知識が必要です。

[[330988]]

さて、最も明白なものから始めましょう:

ヒント0: コードのボトルネックがどこにあるかを理解する

nvidia-smi、htop、iotop、nvtop、py-spy、strace などのコマンドライン ツールは、あなたの親友になるはずです。トレーニング パイプラインは CPU にバインドされていますか? IO にバインドされていますか? GPU にバインドされていますか? これらのツールは答えを見つけるのに役立ちます。

これらのツールについて聞いたことがないかもしれませんし、聞いたことがあっても使用したことがないかもしれません。それは問題ではありません。すぐに使わなくても大丈夫。ただし、他の誰かがそれらを使用して、あなたよりも 5、10、15% 速くモデルをトレーニングしている可能性があることを覚えておいてください。これは、市場投入時や就職の機会に最終的に異なる結果をもたらす可能性があります。

データ前処理

ほぼすべてのトレーニング パイプラインは Dataset クラスから始まります。データサンプルを提供する役割を担います。必要なデータ変換と拡張はここで行われる可能性があります。つまり、データセットはそのサイズを報告し、指定されたインデックスのデータのサンプルを提供します。

画像のようなデータ (2D、3D スキャン) を処理する場合、ディスク I/O がボトルネックになる可能性があります。生のピクセル データを取得するには、コードでディスクからデータを読み取り、画像をメモリにデコードする必要があります。各タスクは短時間で完了しますが、数百または数千のタスクをできるだけ早く処理する必要がある場合は、困難になる可能性があります。 NVidia などのライブラリは、GPU による高速 JPEG デコードを提供します。データ処理パイプラインで IO ボトルネックが発生している場合は、このアプローチを試してみる価値は間違いなくあります。

代替として、SSD ディスクのアクセス時間は約 0.08 ~ 0.16 ミリ秒です。 RAM アクセス時間はナノ秒単位です。データをメモリに直接保存できます。

推奨事項 1: 可能であれば、データのすべてまたは一部を RAM に移動します。

トレーニング データを読み込んで保存するのに十分な RAM がメモリ内にある場合、これがパイプラインから最も遅いデータ取得ステップを排除する最も簡単な方法です。

このアドバイスは、Amazon の p3.8xlarge などのクラウド インスタンスに特に役立つ可能性があります。このインスタンスには EBS ディスクがあり、デフォルト設定ではパフォーマンスが非常に制限されます。ただし、このインスタンスには驚異的な 248Gb の RAM が搭載されています。これは、ImageNet データセット全体をメモリに収めるのに十分です。これを実現するには、次の操作を実行します。

  1. クラス RAMDataset(データセット):
  2. def __init__(image_fnames, ターゲット):
  3. self.targets = ターゲット
  4. 自己画像= []
  5. for fname in tqdm(image_fnames, desc = "RAMにファイルをロードしています" ):
  6. open(fname, "rb") を f として実行します:
  7. 自己画像を追加します(f.read())
  8.  
  9. __len__(自分)を定義します:
  10. len(self.targets) を返す
  11.  
  12. def __getitem__(self, インデックス):
  13. ターゲット= self .targets[インデックス]
  14. 画像、 retval = cv2.imdecode (self.images[index], cv2.IMREAD_COLOR)
  15. イメージを返す、ターゲット

私は個人的にこのボトルネックの問題に直面しました。私は 4x1080Ti GPU を搭載した家庭用 PC を持っています。ある時点で、4 つの NVidia Tesla V100 を搭載した p3.8xlarge インスタンスを取得し、そこにトレーニング コードを移動しました。 V100 は私の古い 1080Ti よりも新しくて高速なので、トレーニングが 15~30% 速くなると予想していました。予想外に、エポックあたりのトレーニング時間が増加しました。これにより、CPU と GPU の速度だけでなく、インフラストラクチャと環境の違いにも注意を払う必要があることがわかりました。

シナリオに応じて、各ファイルのバイナリ コンテンツをそのままにして RAM 内でオンザフライでデコードするか、画像を圧縮せずにデコードして元のピクセルを保持することができます。しかし、どのようなアプローチを取るにしても、2 つ目のヒントがあります。

ヒント 2: 分析、測定、比較を行います。パイプラインの変更を提案するたびに、その全体的な影響を徹底的に評価します。

モデル、ハイパーパラメータ、データセットなどに変更を加えていないと仮定すると、このアドバイスはトレーニング速度のみに焦点を当てています。マジック コマンド ライン パラメータ (マジック スイッチ) を設定することができ、そのパラメータが指定されると、いくつかの適切なデータ例に対してトレーニングが実行されます。この機能を使用すると、パイプラインをすばやく解析できます。

  1. # CPUボトルネックのプロファイル
  2. python -m cProfile トレーニングスクリプト.py --プロファイリング
  3.  
  4. # GPU ボトルネックのプロファイル
  5. nvprof --print-gpu-trace python train_mnist.py
  6.  
  7. # プロファイルシステムコールのボトルネック
  8. strace -fcT python training_script.py -eトレース=開く、閉じる、読む
  9.  
  10. アドバイス3: *すべてをオフラインで前処理する*

ヒント3: すべてをオフラインで前処理する

複数の 2048x2048 画像から作成された 512x512 サイズの画像でトレーニングする場合は、事前にサイズを変更してください。モデルへの入力としてグレースケール画像を使用する場合は、オフラインで色を調整します。自然言語処理 (NLP) を行う場合は、事前にトークン化を行ってディスクに保存してください。トレーニング中に同じ動作を何度も繰り返しても意味がありません。プログレッシブ学習を行う場合、トレーニング データを複数の解像度で保存できます。これは、オンラインでターゲット解像度に再スケーリングするよりも高速です。

表形式のデータの場合、データセットを作成するときに、pd.DataFrame オブジェクトを PyTorch テンソルに変換することを検討してください。

ヒント4: DataLoaderのワークフローを調整する

PyTorch は DataLoader クラスを使用して、トレーニング モデルのバッチ処理を簡素化します。速度を上げるには、Python のマルチプロセスを使用して並列実行します。ほとんどの場合、そのまま使用できます。覚えておくべきポイントがあといくつかあります:

各プロセスはデータのバッチを生成し、これらのバッチはミューテックス同期を介してメインプロセスで利用できるようになります。 N 個のワーカーがある場合、スクリプトではそれらのデータ バッチをシステム メモリに保存するために N 倍の RAM が必要になります。どれくらいの RAM が必要ですか?

計算してみましょう:

  • バッチ サイズが 32、サイズが 512x512x3 (高さ、幅、チャネル) の RGB 画像を使用して、Cityscapes の画像セグメンテーション モデルをトレーニングするとします。 CPU 側で画像を正規化します (これがなぜ重要なのかは後で説明します)。この場合、最終的な画像テンソルは 512 * 512 * 3 * sizeof(float32) = 3,145,728 バイトになります。これをバッチ サイズで乗算すると、結果は 100,663,296 バイト、つまり約 100 MB になります。
  • 画像に加えて、グラウンドトゥルースマスクも提供する必要があります。それぞれのサイズは (デフォルトでは、マスク タイプは long、8 バイト) 512 * 512 * 1 * 8 * 32 = 67,108,864、つまり約 67 Mb です。
  • したがって、データのバッチに必要な合計メモリは 167 MB です。ワーカーが 8 台あると仮定すると、合計メモリ要件は 167 Mb * 8 = 1,336 Mb になります。

悪く聞こえないでしょうか? 問題は、ハードウェア設定が 8 人以上のワーカーによって提供される大きなバッチを処理できるようになったときに発生します。単純に 64 個のワーカーを配置することもできますが、その場合少なくとも 11 GB 近くの RAM が消費されます。

データが 3D スキャンの場合、状況はさらに悪くなります。この場合、512x512x512 の単一チャネル ボリュームは 134 MB を占有し、バッチ サイズが 32 の場合、8 つのワーカーは 4.2 GB を占有し、中間データをメモリに保持するためだけに 32 GB の RAM が必要になります。

この問題には部分的な解決策があります。入力データのチャネル深度を可能な限り減らすことができます。

  • RGB 画像をチャネル深度あたり 8 ビットに保ちます。画像は簡単に浮動小数点に変換したり、GPU 上で正規化したりできます。
  • データセットでは long ではなく uint8 または uint16 データ型を使用します。
  1. クラス MySegmentationDataset(データセット):
  2. ...
  3. def __getitem__(self, インデックス):
  4. 画像= cv2.imread (self.images[インデックス])
  5. ターゲット= cv2.imread (self.masks[インデックス])
  6.  
  7. # ここではデータの正規化と型キャストは行われません
  8. torch.from_numpy(image).permute(2,0,1).contiguous() を返します。
  9. torch.from_numpy(ターゲット).permute(2,0,1).contiguous()
  10.  
  11. クラス Normalize(nn.Module):
  12. # https://github.com/BloodAxe/pytorch-toolbelt/blob/develop/pytorch_toolbelt/modules/normalize.py
  13. def __init__(self, 平均, std):
  14. スーパー().__init__()
  15. self.register_buffer("mean", torch.tensor(mean).float().reshape(1, len(mean), 1, 1).contiguous())
  16. self.register_buffer("std", torch.tensor(std).float().reshape(1, len(std), 1, 1).reciprocal().contiguous())
  17.  
  18. def forward(self, 入力: torch.Tensor) - > torch.Tensor:
  19. 戻り値 (input.to(self.mean.type) - self.mean) * self.std
  20.  
  21. クラス MySegmentationModel(nn.Module):
  22. __init__(self)を定義します。
  23. self.normalize =正規化([0.221 * 255], [0.242 * 255])
  24. 自己損失= nn.CrossEntropyLoss ()
  25.  
  26. def forward(自分、イメージ、ターゲット):
  27. 画像= self .normalize(画像)
  28. 出力= self .backbone(画像)
  29.  
  30. ターゲットがNoneでない場合:
  31. 損失=自己.loss(出力、ターゲット.long())
  32. リターンロス
  33.  
  34. 出力を返す

これを行うことで、RAM 要件が大幅に削減されます。上記の例の場合。データ表現を効率的に保存するためのメモリ使用量は、バッチあたり 33 MB となり、以前の 167 MB から 5 分の 1 に減少します。もちろん、そのためには、データを標準化したり、データを適切なデータ型に変換したりするための手順をモデルに追加する必要があります。ただし、テンソルが小さいほど、CPU から GPU への転送は高速になります。

DataLoader ワーカーの数は慎重に選択する必要があります。 CPU と IO システムの速度、メモリの量、GPU がデータを処理できる速度を確認する必要があります。

マルチGPUトレーニングと推論

[[330989]]

ニューラル ネットワーク モデルはますます大きくなっています。現在では、トレーニング時間を長くするために複数の GPU を使用する傾向があります。幸いなことに、多くの場合、より大きなバッチ サイズに拡張するとモデルのパフォーマンスが向上します。 PyTorch には、わずか数行のコードで複数の GPU を実行するすべての機能が備わっています。ただし、一見しただけでは明らかではない考慮事項がいくつかあります。

  1. model = nn.DataParallel (model) # 利用可能なすべてのGPUでモデルを実行します

複数の GPU で実行する最も簡単な方法は、モデルを nn.DataParallel クラスでラップすることです。画像セグメンテーション モデル (または出力として大きなテンソルを生成するその他のモデル) をトレーニングしていない限り、ほとんどの場合、これで問題なく動作します。フォワード パスの終了時に、nn.DataParallel はマスター GPU 上のすべての GPU 出力を収集し、出力を逆方向に実行して勾配の更新を完了します。

さて、2つの質問があります。

  • GPU 負荷の不均衡;
  • プライマリGPUでの集約には追加のビデオメモリが必要

まず、マスター GPU のみが損失計算、逆推論、勾配ステップを実行でき、他の GPU は 60 度以下に冷却され、次のデータセットを待機します。

2 番目に、マスター GPU 上のすべての出力を集約するために必要な追加メモリにより、通常はバッチ サイズを縮小する動機が生まれます。 nn.DataParallel はバッチを複数の GPU に均等に分散します。 GPU が 4 つあり、バッチ サイズの合計が 32 であるとします。その場合、各 GPU は 8 個のサンプルを含むブロックを取得します。問題は、すべてのマスター GPU がこれらのバッチをそれぞれの VRAM に簡単に収めることができる一方で、マスター GPU は他のカードの出力用に 32 のバッチ サイズに対応するために追加のスペースを割り当てる必要があることです。

この不均一な GPU 使用率には 2 つの解決策があります。

  • トレーニング中、損失はフォワードパスで nn.DataParallel を使用して計算され続けます。この場合。 za はメイン GPU に密な予測マスクを返すのではなく、単一のスカラー損失のみを返します。
  • nn.DistributedDataParallel とも呼ばれる分散トレーニングを使用します。分散トレーニングを使用するもう 1 つの利点は、GPU が 100% の負荷を達成するのを確認できることです。

さらに詳しく知りたい場合は、次の 3 つの記事をご覧ください。

  • https://medium.com/huggingface/training-larger-batches-practical-tips-on-1-gpu-multi-gpu-distributed-setups-ec88c3e51255
  • https://medium.com/@theaccelerators/learn-pytorch-multi-gpu-properly-3eb976c030ee
  • https://towardsdatascience.com/how-to-scale-training-on-multiple-gpus-dae1041f49d2

ヒント5: 2つ以上のGPUがある場合

どれだけの時間を節約できるかはシナリオによって大きく異なりますが、4x1080Ti で画像分類パイプラインをトレーニングすると、約 20% の時間節約が見られました。推論には nn.DataParallel と nn.DistributedDataParallel も使用できることにも言及する価値があります。

カスタム損失関数について

カスタム損失関数を書くことは楽しい練習なので、時々試してみることをお勧めします。このような論理的に複雑な損失関数に関しては、1 つのことを念頭に置く必要があります。それらはすべて CUDA 上で実行され、「CUDA 効率の良い」コードを記述できるはずです。 「CUDA 効率」とは、「Python 制御フローがない」ことを意味します。 CPU と GPU を切り替えて GPU テンソルの個々の値にアクセスすることでもこれらのタスクを実行できますが、パフォーマンスは低下します。

以前、私は論文「コサイン埋め込みとリカレント砂時計ネットワークによる細胞インスタンスのセグメント化と追跡」からカスタム コサイン埋め込み損失関数を実装しました。テキスト形式では非常に単純に見えますが、実装するのは少し複雑です。

私が書いた最初の単純な実装では(バグは別として)、1 つのバッチの損失を計算するのに数分かかりました。 CUDA のボトルネックを分析するために、PyTorch は非常に便利な組み込みプロファイラーを提供します。これは非常に使いやすく、コード内のボトルネックを解決するためのすべての情報を提供します。

  1. テスト損失プロファイリング()を定義します:
  2. 損失= nn.BCEWithLogitsLoss ()
  3. torch.autograd.profiler.profile( use_cuda = True ) を prof として使用します。
  4. 入力= torch.randn ((8, 1, 128, 128)).cuda()
  5. input.requires_grad = True  
  6.  
  7. ターゲット= torch.randint (1, (8, 1, 128, 128)).cuda().float()
  8.  
  9. iが範囲(10)内にある場合:
  10. l =損失(入力、ターゲット)
  11. l.後方()
  12. print(prof.key_averages().table( sort_by = "self_cpu_time_total" ))を印刷します。

ヒント9: カスタムモジュールと損失を設計する場合は、それらを構成してテストします

初期実装をプロファイリングした後、100 倍の高速化を達成することができました。 PyTorch で効率的なテンソル式を記述する方法の詳細については、「効率的な PyTorch — パート 2」で説明されています。

時間 vs. お金

最後になりますが、場合によっては、コードを最適化するよりも、より強力なハードウェアに投資する方が価値がある場合があります。ソフトウェアの最適化は常にリスクが高く、結果も不確実です。CPU、RAM、GPU、または上記のハードウェアすべてをアップグレードする方が効果的かもしれません。お金と時間はどちらも資源であり、この2つをバランスよく使うことが成功の鍵です。

一部のボトルネックは、ハードウェアのアップグレードによって簡単に解決できます。

最後に

日常のツールを最大限に活用する方法を知ることは、熟練度を向上させる鍵です。近道をしないようにしてください。不明な点に遭遇した場合は、さらに深く掘り下げてください。常に新しい知識を発見するチャンスがあります。 「毎日自分を振り返る」ということわざがあります。つまり、自分のコードは改善できるだろうかと自問してみてください。他のスキルと同様に、継続的な改善に対するこの信念は、コンピューター エンジニアになるための必須スキルです。

オリジナルリンク: https://towardsdatascience.com/efficient-pytorch-part-1-fe40ed5db76c

[この記事は51CTOコラム「Machine Heart」、WeChatパブリックアカウント「Machine Heart(id:almosthuman2014)」によるオリジナル翻訳です]

この著者の他の記事を読むにはここをクリックしてください

<<:  機械学習が金融サイバー犯罪と戦う方法: 人工知能はセキュリティの洞察にとって不可欠

>>:  AI に適切なデータ戦略を構築するにはどうすればよいでしょうか?

ブログ    

推薦する

...

...

...

オフライン手法の可能性を最大限に引き出すために、武漢大学とKuaishouは分離型ビデオインスタンスセグメンテーションフレームワークDVISを提案した。

ビデオセグメンテーションタスクは、画像セグメンテーションタスクの拡張版です。ビデオ内のすべてのターゲ...

...

...

PyTorchの基本操作の詳細な説明

[[406246]] PyTorch とは何ですか? PyTorch は、最大限の柔軟性と速度を備え...

人工知能が学習と発達に及ぼす7つの影響

急速に進化する今日のテクノロジー環境において、人工知能 (AI) はあらゆる業界に革命を起こす可能性...

...

...

最高年収は約56万! 2023年の最新のAIGC雇用動向レポートが発表されました

言うまでもなく、ChatGPT が過去 6 か月間でどれほど人気が​​あったかは誰もが知っています。...

...

...

企業はデータセンターで人工知能を広く利用する準備ができているでしょうか?

今日、ますます多くのサーバーベンダーが、人工知能を活用したサーバー自動化テクノロジーの開発に取り組ん...

教室への人工知能の導入は論争を巻き起こしています。それは教育に役立つのでしょうか?境界はどこにあるのでしょうか?

「人工知能+スマート教育」が人気を集めています。しかし、生徒の表情を捉える「スマートアイ」や「顔ス...