「4つの簡単なステップであなたのMLシステムを超高速化する」

「4つの簡単なステップであなたの美とファッションを充実させる」

DALL.E-3で生成されたイメージ

ML最適化のローラーコースターへようこそ!この投稿では、4つの簡単なステップでいかなるMLシステムを高速なトレーニングと推論に最適化するプロセスを説明します。

想像してください。ついにクールな新しいMLプロジェクトに参加しました。写真に写っているホットドッグの数をカウントするエージェントのトレーニングを行い、その成功は会社に何十ドルもの利益をもたらす可能性があります!

お気に入りのフレームワークに最新の注目の物体検出モデルを実装し、いくつかのおもちゃの例を実行してから1時間ほどで、大学の3年目の貧乏学生のようにホットドッグを見つけるようになり、すこぶる調子が良くなりました。

次のステップは明らかです。より難しい問題にスケールアップしたいので、さらに多くのデータ、大きなモデル、そしてもちろん、より長いトレーニング時間が必要です。今では数時間ではなく数日間のトレーニングが見えています。しかし大丈夫です、3週間チームの他のメンバーを無視してきているので、たぶん数日間はコードのレビューや穏やかなけんか腰のメールの残りを片付けるのに費やすべきです。

あなたは1日後に気分良く同僚のマージリクエストについての的確で絶対に必要な意見を残して、15時間以上のトレーニングセッションの後にあなたのパフォーマンスが低下し、崩壊しているのを見つけます(因果応報は速いです)。

続く数日は、トライアル、テスト、実験の旋風に変わり、各ポテンシャルなアイデアには1日以上の時間がかかります。これらはすぐに膨大な計算コストを蓄積し、すべてが大きな疑問を生み出します:どのようにしてこれをより早く、より安価にすることができるのですか?

感情のローラーコースターのML最適化へようこそ!次に紹介する4つの簡単なステップを使って状況を好転させる方法を説明します:

  1. ベンチマーク
  2. 単純化
  3. 最適化
  4. 繰り返し

これは反復的なプロセスであり、次に進む前に各ステップを繰り返すことが多いので、4つのステップというよりはツールボックスと言えるかもしれませんが、4つのステップの方が良いです。

1 — ベンチマーク

「二度計るのは一度で切るより良い」 – 賢い人か何か。

最初(おそらく2番目)に常に行うべきことは、システムのプロファイリングです。コードの特定のブロックを実行するのにかかる時間を単純に計測するだけでも構いませんし、フルプロファイルトレースを行うなど、より複雑なプロファイリングも行うことができます。重要なのは、システムのボトルネックを特定するための十分な情報を持っていることです。私はプロセスの進行状況に応じて複数のベンチマークを実施し、通常はハイレベルとローレベルのベンチマークに分けます。

ハイレベル

これらは通常、週ごとの「どれくらい絶望的な状況ですか?」ミーティングで上司に提示するようなもので、毎回の実行に必要なメトリクスとして取得したいものです。これらはシステムのパフォーマンスを高レベルで把握するのに役立ちます。

1秒あたりのバッチ数 – 1つのバッチをどれくらい早く処理できるか?これは可能な限り高い値であるべきです。

1秒あたりのステップ数 – (RL特有)環境を進めながらデータを生成するためのステップ数をどれくらい早く進めるか?可能な限り高い値であるべきです。ステップ時間とトレーニングバッチの間には複雑な関係があるため、ここでは詳細には触れません。

GPUの利用率 – トレーニング中にGPUのどれくらいが利用されているか?これは常に100%に近い値でなければなりません。もし利用率が低い場合は、最適化できるアイドル時間があるということです。

CPUの利用率 – トレーニング中にCPUのどれくらいが利用されているか?これも可能な限り100%に近い値であるべきです。

FLOPS – 秒あたりの浮動小数点演算数。これはハードウェア全体を効果的に利用しているかどうかを示します。

ローレベル

上記のメトリクスを使用して、ボトルネックの所在を詳しく調べていくことができます。これらを持っている場合、より詳細なメトリクスやプロファイリングを見ていくことが望ましいです。

タイムプロファイリング – これは最も単純でしばしば最も有用な実験です。プロファイリングツール(例えば)などを使用して、各コンポーネントのタイミング全体の俯瞰を得ることもできますし、特定のコンポーネントのタイミングを見ることもできます。

メモリプロファイリング — 最適化ツールボックスの必須アイテムの一つです。大規模なシステムでは多くのメモリが必要ですので、無駄にならないように注意が必要です。 メモリプロファイラ などのツールを使用すると、どこでシステムがRAMを消費しているかを特定するのに役立ちます。

モデルプロファイリングTensorboard のようなツールには、モデル内でパフォーマンスを消費している要素を調べるための優れたプロファイリングツールが付属しています。

ネットワークプロファイリング — ネットワークの負荷はシステムのボトルネックの一因となります。 wireshark のようなツールを使用してプロファイリングできますが、正直なところ私は使ったことがありません。代わりに、コンポーネントの時間プロファイリングを行い、コンポーネント内でかかっている総合的な時間を測定し、ネットワークI/O自体がどれくらいの時間を要しているのかを特定しています。

さらに詳しい情報については、RealPython のプロファイリングに関する素晴らしい記事も参考にしてください!

2 — シンプルにする

プロファイリングで最適化が必要な領域を特定したら、それをシンプルにしてください。それ以外の部分はすべてカットしてください。システムをボトルネックまで小さな部分に絞り込んでください。簡素化する際にプロファイリングを行っても問題ありません。これにより、反復した際に正しい方向に進んでいることを確認できます。ボトルネックを特定するまで、これを繰り返してください。

ヒント

  • 他のコンポーネントをスタブやモック関数で置き換え、期待されるデータを提供します。
  • sleep 関数やダミーの計算で重い関数をシミュレートします。
  • ダミーデータを使用して、データの生成と処理のオーバーヘッドを取り除きます。
  • 分散環境に移行する前に、システムのローカルなシングルプロセスバージョンから始めてください。
  • ネットワークのオーバーヘッドを取り除くために、単一のマシン上で複数のノードとアクターをシミュレートします。
  • システムの各部分の理論的な最大パフォーマンスを見つけてください。このコンポーネント以外のボトルネックがない場合、どのようなパフォーマンスが期待されますか?
  • プロファイリングを再実行してください!システムをシンプルにするたびに、プロファイリングをやり直してください。

質問

ボトルネックが特定されると、次に答えたい重要な質問がいくつかあります

このコンポーネントの理論的な最大パフォーマンスは何ですか?

ボトルネックとなるコンポーネントを適切に特定できていれば、この質問に答えることができるはずです。

最大値に対してどのくらい離れていますか?

この最適性の差は、システムがどれだけ最適化されているかを示しています。もちろん、このコンポーネントをシステムに戻した際に他の制約が存在する場合もありますが、少なくともその差を把握することが重要です。

より深いボトルネックはありますか?

常に自問してください。問題が最初に考えていたよりも深刻かもしれませんので、ベンチマークを行い、簡素化のプロセスを繰り返しましょう。

3 — 最適化

では、最大のボトルネックが特定されたとして、次は楽しい部分、どのように改善するかです。通常、改善の可能性がある3つの領域を見ていく必要があります。

  1. 計算
  2. 通信
  3. メモリ

計算

計算ボトルネックを軽減するためには、データとアルゴリズムの効率を最大限にする必要があります。これはプロジェクトによって異なりますし、行えることは非常に多岐にわたりますが、いくつかの原則を見てみましょう。

並列化 — 多くの作業をできるだけ並列で行うようにしてください。これはシステムを設計する際に大きな成功となるパフォーマンス向上の第一歩です。ベクトル化、バッチ処理、マルチスレッディング、マルチプロセッシングなどの手法を見てみてください。

キャッシング — 可能な限り計算を予め行い、再利用するようにしてください。多くのアルゴリズムは、予め計算された値を再利用して、トレーニングの各ステップごとの重要な計算を節約することができます。

オフローディング — 私たちは皆、Pythonがスピードには向いていないことを知っています。幸いなことに、C/C++などの低レベル言語によって重要な計算をオフロードすることができます。

ハードウェアスケーリング — これはちょっとした策略ですが、すべてがうまくいかない場合は、問題に対して追加のコンピュータを投入することができます!

コミュニケーション

経験豊富なエンジニアは、成功を収めるためにはコミュニケーションが重要であることを理解しています。もちろん、それはシステム内部でのコミュニケーションを指しています(同僚たちと話す必要がないことを神のお許しがありますように)。いくつかの良いルールは次のとおりです:

アイドルタイムなし —全ての利用可能なハードウェアを常に活用する必要があります。そうしないと、性能向上の機会を失ってしまいます。通常、これはシステム内の通信の複雑さとオーバーヘッドが原因です。

ローカルを保つ — ディストリビューションシステムに移行する前に、できるだけ長くシングルマシンで全てを保つようにしてください。これにより、システムを単純に保つことができ、またディストリビューションシステムに伴う通信オーバーヘッドも避けることができます。

非同期処理 > 同期処理 — 非同期に処理できるものを特定しましょう。これにより、データの移動中でも作業が進行し、通信コストを軽減することができます。

データの移動を避ける — CPUからGPUへのデータの移動やプロセス間のデータの移動は費用がかかります!できるだけこれを少なくするか、非同期で実行することでその影響を軽減しましょう。

メモリ

最後にメモリです。上記で述べた領域の多くはボトルネックの緩和に役立つかもしれませんが、利用可能なメモリがない場合はそれが不可能かもしれません!以下に考慮すべきいくつかのポイントを見ていきましょう。

データ型 — コミュニケーションやメモリのコストを削減するために、データ型を可能な限り小さく保つことが重要です。また、最新のアクセラレータを使用すると、計算も削減されます。

キャッシング — 計算削減と同様に、スマートなキャッシングはメモリを節約するのに役立ちます。ただし、キャッシュされたデータが頻繁に使用されていることを確認してください。

事前割り当て — Pythonではあまり慣れ親しんでいないかもしれませんが、メモリの事前割り当てに厳格に取り組むことで必要なメモリ量を正確に把握し、断片化のリスクを減らし、共有メモリへの書き込みが可能であればプロセス間の通信を削減することができます!

ガベージコレクション — 幸いにも、Pythonはこれを大部分自動で処理してくれますが、大きな値を不要なままスコープ内に保持したり、さらに悪いことにメモリリークを引き起こす可能性のある循環依存関係を持つことがないかを確認することは重要です。

怠け者でいましょう — 必要な時にのみ式を評価しましょう。Pythonでは、怠惰に評価できる演算にはリスト内包表記の代わりにジェネレータ式を使用することができます。

4 — 繰り返す

では、いつ終了するのでしょうか?それは実際にはプロジェクトや要件、狂気が崩壊するまでの時間によって異なります!

ボトルネックを除去するにつれて、システムの最適化に費やす時間と労力の対効果は減少します。プロセスを進める中で、いつ良い結果が得られるかを判断する必要があります。スピードは目的を達成する手段であり、ただ最適化に陶酔する罠にはまらないように注意しましょう。ユーザーに影響を与えないのであれば、次に進む時が来たかもしれません。

結論

大規模なMLシステムを構築することは困難です。それは「ウォーリーを探せ」が暗黒魂と交じり合った捻ったゲームをやっているようなものです。問題を見つけた場合、それを打ち負かすために複数の試みが必要であり、ほとんどの時間は敵に倒されながら過ごすことになります。「なぜ金曜日の夜にこれをしているのか?」と自問自答しながらですね。シンプルで原則的なアプローチは、最終ボスバトルを乗り越え、理論上の最大計算量を味わうのを助けてくれるでしょう。

ML in Action | Donal Byrne | Substack

急速に進化する機械学習のニュースレターで、求められないアドバイス、実践的な洞察、そして学んだ教訓を提供しています…

donalbyrne.substack.com

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more