画像、音声認識、自然言語処理、強化学習などの多くの技術分野において、ディープラーニングは非常に効果的であることが証明されており、いくつかの問題では人間のレベルに到達、あるいはそれを上回っています。しかし、ディープラーニングはコンピューティング能力に大きく依存しています。モデルやアルゴリズムの変更に加えて、システム レベルからディープラーニング コンピューティングを最適化し、コンピューティング リソースの効率を向上させることは可能でしょうか。この記事では、Microsoft Research Asia のヘテロジニアス コンピューティング グループのシニア リサーチャーである Wu Ming が、ディープラーニング コンピューティングの最適化に関する見解を共有します。 ディープラーニングは近年大きな進歩を遂げており、自動運転、セキュリティ、翻訳、医療など、私たちの生活のさまざまな場面ですでに応用されているか、あるいは今後応用されることが期待されています。コンピュータの計算能力と通信能力の大幅な向上は、ディープラーニングの成功の重要な要因であると言えます。 なぜディープラーニングは膨大な計算能力を必要とするのでしょうか? まず、ディープラーニングは本質的に統計に基づいた科学であるため、ディープラーニングの有効性には大規模なサンプルデータが不可欠です。第二に、より大規模で複雑なニューラル ネットワーク モデルは非常に効果的であることが証明されており、製品で広く使用されていますが、これにより、コンピューティング能力の要件と消費も増加します。たとえば、8 層のニューロンを持つ AlexNet ネットワークは、2012 年に ImageNet データセットで 16% のエラー率を達成しました。ネットワークの 1 回の反復には、約 1.4 GFLOP の計算が必要でした。 Microsoft が提案した 152 層のニューロンを使用する残差ネットワーク (ResNet) は、2015 年のデータセットで 3.5% のエラー率を達成しました。1 回の反復の計算ワークロードは約 22.6GFLOP で、これは AlexNet の 16 倍です。今日の生産環境では、顔認識、音声テキスト変換、機械翻訳など、画像、音声、自然言語処理に関連する多くのモデルでは、かなりのコンピューティング リソースが与えられても、トレーニングを完了するのに数週間かかります。
繰り返しになりますが、ディープラーニング モデルは急速に反復されます。 AIの分野では、学界と産業界から毎年多数の新しいモデルが提案されています。あらゆる実用的な問題に対して、開発者は常にさまざまなモデルやアルゴリズムを試す必要があります。同じモデル アルゴリズムであっても、最良の予測結果を得るためにはハイパーパラメータを繰り返しデバッグする必要があります。ご想像のとおり、モデルのトレーニングにそれぞれ数週間かかると、最適なモデルを見つけるプロセスは非常に長く、苦痛なものになります。
さらに、モデルのオンライン推論には、より厳しいパフォーマンス要件があります。オンライン サービスには厳格なサービス レベル契約 (SLA) があるため、実際に大規模なモデルを展開する際には、ディープラーニング フレームワーク (TensorFlow など) でトレーニングされたモデルを手動で再最適化する必要があり、結果としてエンジニアリングのオーバーヘッドが大幅に増加します。
ディープラーニングの計算をさらに最適化することが、ディープラーニングの急速な発展と応用の成功に重要な役割を果たすことがわかります。
ディープラーニング計算を最適化するための課題と機会
現在、ディープラーニングの計算を最適化するには、いくつかの大きな課題があります。
1) 単一のマシンと単一のコンピューティングユニット(GPU など)のリソース制限では、大規模なデータやモデルの処理要件を満たせない場合が多く、複数のマシンと複数のコンピューティングユニットを使用してコンピューティングの規模を水平に拡張する必要があります。通信オーバーヘッドを最小限に抑え、複数のマシンの並列処理を最大化するにはどうすればよいでしょうか?
2) ニューラル ネットワークの計算を最適化して、単一のハードウェア計算ユニットの効率を最大化するにはどうすればよいでしょうか。
3) 多くのハードウェアコンピューティングユニット (GPU、FPGA など) の計算能力は強力ですが、メモリリソース (デバイスメモリ) は非常に不足しています。モデル演算に必要なメモリ リソースを提供できない場合、演算を続行できないか、計算に必要なデータをメイン メモリとデバイス メモリ間で転送する必要があり、大きな演算オーバーヘッドが発生します。コンピューティング効率に悪影響を与えずに、限られたデバイス メモリ リソースをより有効に活用するにはどうすればよいでしょうか?
4) ディープラーニングの開発者や研究者は通常、ニューラルネットワークのモデルとアルゴリズム自体にのみ焦点を当てたいと考えており、複雑な最適化問題に気を取られたくありません。これは、ディープラーニング フレームワークなどのシステム ソフトウェアを、モデル開発者に対して自動的かつ透過的に最適化できることを意味します。したがって、特定の最適化を合理的に抽象化して、より柔軟で汎用性が高く、システム フレームワークに簡単に統合できるようにする方法は、真剣に検討する必要がある問題です。 実際、あらゆる最適化問題は、モデル アルゴリズムとシステムという 2 つの観点から見ることができます。一方では、モデルとアルゴリズムを変更してコンピューティング リソースの効率的な使用を最適化することで、実行速度を向上させることができます。このような最適化は特定のアルゴリズムに対しては非常に効果的であることが多いですが、他のアルゴリズムに拡張して適用するのは簡単ではありません。一方、Microsoft Research Asia のヘテロジニアス コンピューティング グループが行っている研究は、モデルに依存しない最適化をシステムに実装することです。このような最適化により、通常、より多くのアプリケーションにパフォーマンス上の利点がもたらされ、前述の透明性の要件も満たすことができます。
システム最適化によるディープラーニングコンピューティングの強化
システムのこのレベルでの最適化をよりよく理解するために、まずディープラーニング フレームワーク システムの背景知識を簡単に紹介しましょう。現在業界で人気のあるディープラーニング システムのほとんど (TensorFlow、PyTorch、CNTK、MxNet、Caffe など) は、階層型アーキテクチャ設計を採用しています。フロントエンドに高級言語(Pythonなど)インターフェースの抽象化を提供し、ユーザーがニューラルネットワーク構造、つまりディープラーニングモデルを簡単に記述できるようにします。記述されたモデルは、システムによって実行される前に、まずデータフロー グラフに変換されます。このデータ フロー グラフでは、ノードは特定の行列演算 (つまり、シグモイド、行列乗算などの演算子) であり、異なるノードを接続するエッジは演算ノードの入力行列と出力行列です。このデータフローグラフは、ディープラーニング計算の中間表現としても見ることができます。次に、ディープラーニング システムのバックエンドは、このデータ フロー グラフを実際のハードウェアにマッピングして効率的に実行し、システム レベルの最適化のほとんどはこの段階で実行されます。
分散型ディープラーニングトレーニングの加速
分散トレーニングの主なボトルネックとなるのは、複数のマシン間の通信オーバーヘッドです。現在、コンピュータネットワークのハードウェア技術は大きく進歩しています。InfiniBand の RDMA ネットワーク カード (Remote Direct Memory Access、リモート マシンの CPU の介入なしにコンピュータがリモート メモリにアクセスできるハードウェア ネットワーク技術) は、すでに 50 ~ 100Gbps のネットワーク帯域幅とマイクロ秒単位の伝送遅延を提供できます。現在、ディープラーニング アプリケーションをターゲットとする多くの GPU クラスターがこのようなネットワークを導入しています。しかし、ディープラーニング システムは、ハードウェアが提供する通信機能を最大限に活用して、分散トレーニングのパフォーマンスをさらに向上させるにはどうすればよいのでしょうか。さらに、通信に RDMA ソフトウェア インターフェイスを使用すると、TCP/IP プロトコル スタックをバイパスし、オペレーティング システム カーネル状態の動作オーバーヘッドを削減できます。このようなネットワーク通信テクノロジのサポートにより、通信関連のコンピューティングと処理のオーバーヘッドが非常に大きくなります。これは、もともと TCP/IP に基づいて設計された多くのネットワーク通信メカニズムに存在する問題とまったく同じです。
RPC (リモート プロシージャ コール) は、複数のマシン間の通信に広く使用されている抽象プリミティブです。その主な設計目標は、汎用性です。多くのディープラーニング フレームワークは、RDMA を考慮せずに、RPC メカニズム (gRPC など) を使用して複数のマシン間の通信を実現します。ただし、RPC では内部のプライベート キャッシュを維持する必要があり、ユーザー データ ストレージ領域と内部キャッシュの間でデータのコピーを導入する必要があります。このメモリ コピーのオーバーヘッドは、RDMA ネットワークを使用する場合に非常に重要になります。マイクロベンチマークを通じて、TCP/IP ベースの gRPC を使用する場合と比較して、RDMA インターフェイスを介して直接メッセージを送信すると (さまざまなメッセージ サイズに対して)、パフォーマンスが 2 ~ 10 倍向上することがわかりました。
では、ディープラーニング アプリケーションの負荷に対して RDMA ハードウェアの機能をより有効に活用するにはどうすればよいでしょうか。まず、ディープラーニング アプリケーションのいくつかの特性を分析してみましょう。
1) テンソルはディープラーニングの計算において最も重要なデータ構造であり、テンソルの処理には大量の計算オーバーヘッドが費やされます。 Tensor は比較的単純なデータ構造で、主にメタデータとペイロードの 2 つの部分で構成されます。ペイロードは基本要素の配列であり、メタデータはテンソルの形状情報、つまり次元と各次元のサイズです。この単純なデータ構造では、実際には送信中に複雑なシリアル化およびデシリアル化機能は必要ありません。
2) 多くの場合、テンソルは密度が高く、サイズが大きいため、そのようなテンソルを送信するときに追加のバッチ処理は必要ありません。
3) ディープラーニングのトレーニングプロセスは反復的です。各反復ではミニバッチが処理されます。異なる反復間では、多くのテンソルのデータフロー グラフと形状情報は変化せず、形状情報の多くは実行前に静的に決定できます。
上記の特性に基づいて、データフローグラフを分析し、形状情報を静的に決定できるテンソルを見つけることができるため、実行前に受信側でそれらのテンソルに RDMA アクセス可能なメモリ領域を事前に割り当て、対応するリモート アクセス可能なアドレスを送信側に送信することができます。このようにして、実行時に、送信者は一方的な RDMA 要求を通じて Tensor データを受信側に直接転送できるため、不要な追加のメモリ コピーが完全に回避され、ゼロ コピーの通信プロセスが実現されます。私たちはこのメカニズムを TensorFlow で実験し、TCP/IP ベースの gRPC と比較したところ、この方法は一連の典型的なモデルで複数のパフォーマンス向上を達成しました。 RDMA 向けに最適化された gRPC と比較しても、当社のアプローチでは 50% を超えるパフォーマンスの向上を実現できます。
さらに、分散型ディープラーニングの分野で私たちが関心を持っているもう一つの課題は、リソースに依存しないデータフローグラフの分散実行を自動的に最適化する方法、つまり、データフローグラフ内の計算タスクを自動的に分割し、対応する計算リソースを割り当てて計算効率を最大化することです。 Google の Jeff Dean 氏のチームは、この方向で優れた先駆的な仕事をしてきました。ただし、これはモデル並列性と単一マシンマルチカードの動作環境に限定されており、現時点では、これは依然として非常に重要かつ有望な方向性であり、データ並列性、分散および異種環境と組み合わせて総合的に検討する必要があります。
単一のコンピューティングユニットの計算効率を向上させる
前述したように、ディープラーニング フレームワークを使用して実装されたモデル アルゴリズムは、実行前にデータ フロー グラフに変換されます。実用価値のあるモデルの多くは非常に複雑です。それらから変換されたデータフローグラフは通常、数千の操作ノードで構成され、その中には計算の複雑さが非常に小さいノードが多数含まれています。つまり、入力マトリックスのサイズが非常に小さいか、入力データにアクセスする複雑さに比べて計算ロジックの複雑さが非常に低いということです。このような操作ノードが多数存在すると、次のような実行時オーバーヘッドが発生し、このようなオーバーヘッドは非常に大きくなる可能性があります。
1) ディープラーニングシステムの実行時には、データフローグラフ内のノードの依存関係に応じてノードの実行をスケジュールする必要があります。各ノードのスケジューリングによるシステム オーバーヘッドは、演算ノードの計算量とは直接関係がありません。そのため、多数の小さな演算ノードで構成される計算フロー グラフの場合、システム スケジューリングによって発生する追加のオーバーヘッドは比較的大きくなります。
2) GPU 上で実行される計算の場合、各演算ノードの実装は GPU カーネル関数に対応しており、このカーネル関数を実行するたびに CPU がグラフィックス ドライバーを呼び出して起動する必要があり、これによっても一定桁の追加オーバーヘッドが発生します。このオーバーヘッドは、計算負荷の小さいカーネル関数の実行に比べて非常に重要です。
3) 計算負荷が小さい演算ノードでは、十分なデータ並列性を活用することが困難な場合が多く、プロセッサハードウェアの計算リソースを十分に活用できません。
この問題を解決するための主なアイデアはカーネル融合です。 CuDNN に基づく NVIDIA の RNN ライブラリ関数など、いくつかの手動最適化方法ではこの考え方が使用されています。リカレント ニューラル ネットワーク全体を GPU カーネル関数として実装し、非常に優れたパフォーマンスを実現します。しかし、その欠点も非常に明白です。つまり、柔軟性と汎用性が十分ではなく、他のネットワークやリカレント ニューラル ネットワークのいくつかのバリアントには適用できません。私たちは、ディープラーニング システム内のネットワーク モデルを自動的に最適化する方法にもっと関心を持っています。 現在、学界や産業界には、TVM、Halide、Taco など、コンパイル方式を使用して融合カーネル コードを生成するシステムがいくつかあります。これらのシステムでは、フロントエンドの表現方法としてテンソル代数を使用し、各テンソル代数式を対応するカーネル コードにコンパイルできます。テンソル代数は、低レベルの中間表現としてディープラーニング システムに統合できます。つまり、高レベルのデータ フロー グラフを最初にテンソル代数表現で構成されたコード ブロックに変換し、次に実行可能コードにコンパイルすることができます。しかし、これらのシステムでは、融合できる演算ノードに多くの制限があり、複数の行列乗算演算など、複数の非点演算をうまく融合することができません。しかし、この制限を打ち破り、より多くの操作ノードを融合すると、パフォーマンスがさらに大幅に向上することがわかりました。
GPU 環境で複数の非点演算を融合することは困難です。非点演算の入力行列の各要素は、前の演算の出力行列内のさまざまな位置にある要素の値に依存する可能性があるため、2 つの演算の間にバリア同期プリミティブを挿入する必要があります。 GPU に Barrier を実装するには、実行時にカーネルのすべてのスレッド ブロックがアクティブなままであることを保証する必要があります。つまり、融合カーネルが限られた数のスレッド ブロックを使用するように要求すると同時に、スレッド ブロックの数をはるかに超えるデータ ブロックを処理できるようにする必要があります。
この問題を解決するために、永続スレッド スレッド ブロック モデルを採用し、固定数のスレッド ブロックを開始し、融合カーネルのライフ サイクル全体にわたってアクティブな状態を維持しようとします。私たちの最適化システムは、融合カーネルコードを生成するプロセスにおけるビンパッキング問題を解決することに似ています。つまり、融合されるサブデータフローグラフ内の各操作ノードによって処理されるデータブロックを適切なアクティブスレッドブロックに割り当て、各スレッドブロックの負荷が可能な限り均等になり、元のデータフローグラフ内の操作ノード操作の並列性が維持されるようにします。
最適化された GPU カーネル関数を生成するには、スレッド ブロックとデータ ブロックを適切に分割することが重要な考慮事項です。ただし、これは、演算ノード演算における計算とメモリ アクセスの複雑さの比率、GPU の共有メモリのサイズ、レジスタ ファイルのサイズと割り当て方法など、いくつかの非常に複雑な要因に依存します。したがって、静的な方法では最適な選択を決定することは困難です。幸いなことに、ディープラーニングの反復的な性質と、収束するまでにかなりの数の反復が必要であるという事実により、早期の反復を使用して実行時に動的な情報を収集し、最適化システムがより情報に基づいた決定を下せるようにすることができます。
デバイスのメモリリソース制限を克服する
多くの場合、デバイス メモリのサイズによって、処理できるモデルの規模が制限されます。この問題を解決する 1 つのアイデアは、モデルを圧縮して量子化することです。現在、学界や産業界ではさまざまな圧縮および量子化方法を提案する研究が盛んに行われていますが、実際のアプリケーション シナリオで圧縮と量子化を使用するのは、依然として面倒な反復プロセスです。このプロセス中に、ユーザーは次の方法を試すことができます。
1) 異なる圧縮方法。例えば、モデルパラメータ値がゼロに近づくかどうかに基づいているのか、それとも特定の寄与値に変換されてゼロに近づくのか?圧縮時に特定の構造が考慮されているのか(GPU 用の場合は、実行効率を向上させるためにブロックスパース行列に圧縮する必要があるかもしれない)?量子化された値ポイントは、平均値の範囲に従って分割されているのか、それとも何らかのクラスタリングに基づいているのか?
2) 圧縮の度合いが異なる。すべての層が圧縮モデルの影響に対して同じように敏感なわけではないため、ニューロン パラメータのどの層を圧縮するかを検討し、異なる圧縮率または量子化ビットを選択します。
3) 高い圧縮率で良好なモデル結果を維持するためには、一度に 10% 圧縮してから再トレーニングし、目標の圧縮率が達成されるまでこのプロセスを繰り返すなど、圧縮プロセスを段階的に行う必要がある場合があります。次に、各プログレッシブ プロセスの圧縮率は調整する必要があるパラメーターです。
当然のことながら、このような面倒なプロセスを便利にするには、優れたツールが必要です。これも私たちのグループが注目している問題です。私たちは、TensorFlow の API を拡張して、ユーザーがモデル スクリプトで量子化と圧縮の方法、オブジェクト、次数、プロセスを直接制御できるようにすることに取り組んでいます。
圧縮と量子化は通常、モデルの展開中にパフォーマンスとメモリ リソース不足の問題を解決するために使用されます。モデルのトレーニング中にメモリが不足する問題を解決する 1 つの方法は、メモリと引き換えにコンピューティングを使用することです。たとえば、データ フロー グラフ内の操作ノードの計算量は少ないが、出力される中間結果のデータ量が多い場合、中間結果をメモリに保存するのではなく、後で必要になったときにこの操作ノードの計算を再実行する方が適切な処理方法です。もちろん、再計算によって、ある程度の追加オーバーヘッドが発生します。
実は、この問題を解決する別の方法があります。それは、大きな入力データをCPUのメインメモリに格納し、演算ノードをストリーミング処理として実装し、大きな入力データをセグメントごとにGPUのデバイスメモリにコピーし、非同期コピーを使用して各セグメントの計算時間と次のセグメントのコピー時間を重ね合わせることで、データコピーのオーバーヘッドをカバーするというものです。行列乗算などの演算では、計算の複雑さがメモリアクセスの複雑さよりも高いため、セグメントが大きいほど、計算時間とコピー時間を最大限に重ねることができます。ただし、実行される演算が行列乗算ではなく、いくつかの単純な点ごとの演算である場合、計算の複雑さはメモリ コピーのオーバーヘッドによって相殺することはできません。したがって、このアプローチもカーネル融合と組み合わせる必要があります。たとえば、行列乗算と後続の点ごとの演算を統合すると、各セグメントの計算では、次のセグメントを処理する前に、セグメントの行列乗算と点ごとの演算が完了します。 |