HUOXIU

GPU ディープラーニング パフォーマンスの 3 つの原動力: Tensor コア、メモリ帯域幅、メモリ階層。

編集者注:近年、ディープラーニングアプリケーションはますます普及し、その需要も急速に高まっています。では、最適なトレーニングと推論のパフォーマンスを実現するために、適切なGPUをどのように選択すればよいのでしょうか?

本日は、Tensor コア、メモリ帯域幅、メモリ階層が GPU ディープラーニングのパフォーマンスに影響を与える最も重要な要素であるということを中心とする記事をご紹介します。

著者らは、ディープラーニングにおける行列乗算の重要性と、Tensorコアが独自の行列乗算ユニットによって計算性能を大幅に向上させる仕組みについて詳細な分析を提供しています。また、メモリ帯域幅のパフォーマンス制約を分析し、様々なGPUアーキテクチャにおけるメモリ階層の違いを比較しています。

この記事では、ディープラーニングのパフォーマンスに密接に関連するGPUのハードウェアメトリクスを明確に理解し、GPUの選択と使用においてより情報に基づいた意思決定を行う上で役立ちます。最後に、GPUのこれらの主要なパフォーマンスメトリクスのさらなる最適化とブレークスルーに期待しています。

著者 | ティム・デトマーズ

編纂者:岳陽

この記事は、原著者の許可を得てBaihai IDPによって翻訳されました。翻訳の転載をご希望の場合は、お問い合わせください。

オリジナルリンク:

https://timdettmers.com/2023/01/30/どのGPUがディープラーニング用か/


🚢🚢🚢AIテクノロジーソフトウェアと技術交流グループへのご参加をお待ちしております!最新のトレンドを把握し、一緒に技術的な課題を探求しましょう!


この記事は、ディープラーニングにおけるGPUパフォーマンスに影響を与える様々な要素を理解し、GPUの評価と選定に役立ちます。GPUコンポーネントを重要度の高い順に紹介します。Tensorコアが最も重要な要素であり、次にGPUメモリ帯域幅とキャッシュ階層、そして最後にGPU FLOPSが続きます。

目次

01テンソルコア

1.1 テンソル計算カーネルなしで行列乗算を実行する

1.2 テンソル計算カーネルを用いた行列乗算

1.3 テンソルコンピューティングコアと非同期コピーを使用した行列乗算(RTX 30/RTX 40)およびTMA(H100)

02メモリ帯域幅

03 L2キャッシュ/共有メモリ/L1キャッシュ/レジスタ

01 テンソルコア

Tensorコアは、効率的な行列乗算演算を実行する小さなコアです。行列乗算はあらゆるディープニューラルネットワークにおいて最も計算負荷の高い部分であるため、Tensorコアは非常に有用です。Tensorコアは非常に強力なので、Tensorコアを搭載していないGPUの使用はお勧めしません。

これらの独自の行列乗算計算ユニットの動作を理解することで、その重要性を理解するのに役立ちます。以下の例は、単純なA*B=C行列乗算(すべての行列は32×32)であり、テンソル計算コアを使用する場合と使用しない場合の計算モードを示しています。これは簡略化された例であり、高性能な行列乗算コアの記述方法を正確に表しているわけではありませんが、必須の要素はすべて含まれています。CUDAプログラマーはこれを最初の「草案」として使用し、ダブルバッファリング、レジスタ最適化、占有最適化、命令レベルの並列化などの概念を用いて段階的に最適化していきますが、これらについてはここではこれ以上説明しません。

この例を完全に理解するには、サイクルの概念を理解する必要があります。プロセッサが1GHzで動作する場合、1秒間に10^9サイクルを実行できます。各サイクルは1回の計算機会を表します。しかし、ほとんどの場合、計算時間は1サイクルを超えます。そのため、実際には、次の操作が前の操作の完了を待つ必要があるキューが存在します。これは、操作のレイテンシとも呼ばれます。

以下は重要なレイテンシサイクルタイミングです。値はGPUの世代によって異なります。以下の値は、キャッシュ速度が比較的遅いAmpereアーキテクチャGPU [1]のものです。

  • グローバルメモリ(最大80GB)へのアクセス:約380サイクル

  • L2キャッシュ: 約200サイクル

  • L1キャッシュまたは共有メモリへのアクセス(ストリーミングマルチプロセッサあたり最大128KB):約34サイクル

  • 命令セットレベルでの融合乗算加算(FFMA):4サイクル

  • Tensor Core 行列乗算演算: 1 サイクル

各計算は常に、ワープと呼ばれる 32 個のスレッドのグループによって実行されます。ワープは通常、同期(C パターン)で動作し、ワープ内のスレッドは互いに待機する必要があります。GPU 上のすべてのメモリ操作はワープ用に最適化されています。たとえば、グローバルメモリからのロードは 32*4 バイト、つまり 32 個の浮動小数点数という粒度で行われ、各ワープ内の各スレッドは 1 つの浮動小数点数を受け取ります。ストリーミング マルチプロセッサ(SM)は最大 32 個のワープ(つまり 1024 個のスレッド)を持つことができ、これは CPU コアの GPU 部分に相当します。SM リソースはすべてのアクティブなワープに割り当てられます。つまり、各ワープのレジスタ、共有メモリ、テンソル計算コアリソースを増やすために、実行するワープの数を減らしたい場合があります。

以下の2つの例では、同じコンピューティングリソースを使用していることを前提としています。この32×32行列乗算の小規模な例では、8つのストリーミングマルチプロセッサ(SM)(RTX 3090の約10%)を使用し、各SMで8つのスレッドバンドルを使用しました。

各SMにおけるサイクルレイテンシがスレッドや共有メモリなどのリソースとどのように相互作用するかを理解するために、行列乗算の例をいくつか見てみましょう。これらの例は、テンソル計算コアの有無にかかわらず、行列乗算の計算ステップの順序にほぼ従っていますが、非常に簡略化された例であることに注意してください。実際の行列乗算演算では、はるかに大きな共有メモリブロックが使用され、計算パターンも若干異なります。

1.1 テンソル計算カーネルなしで行列乗算を実行する

単純な行列乗算 A*B=C(各行列は 32×32)を例にとると、頻繁にアクセスされるデータを共有メモリにロードします。その主な理由は、共有メモリのレイテンシがグローバルメモリの約 6 分の 1(200 サイクル対 34 サイクル)であるためです。(訳者注:アクセスを高速化するために、頻繁にアクセスされるデータを共有メモリにロードすることができます。共有メモリは、GPU の各 SM(ストリーミング マルチプロセッサ)にある小さなキャッシュで、レイテンシが低くなっています。)共有メモリ内のメモリ ブロックは、単にメモリ タイルまたは単にタイルと呼ばれることがよくあります。それぞれ 32 個のスレッドを持つ 2 つのスレッド バンドルを使用することで、2 つの 32×32 浮動小数点数を共有メモリ タイルに並列にロードできます。それぞれ 8 つのスレッド バンドルを持つ 8 つの SM がある場合、並列化により、グローバル メモリから共有メモリに順番にロードする処理は 1 回だけで済み、この処理には 200 サイクルしかかかりません。

行列乗算を実行するには、共有メモリ A と共有メモリ B から 32 個の数値を含む 2 つのベクトルをロードし、命令セット レベルで FFMA (Fused Multiplication and Addition) を実行し、出力をレジスタ C に格納する必要があります。 この作業を複数の部分に分割し、各 SM で 8 回のドット積演算 (32×32) を実行して、C の 8 つの出力を計算します。 古いアルゴリズムの 4 回ではなく 8 回である理由は非常に技術的な問題です。 詳細については、Scott Gray の行列乗算に関するブログ記事 [2] を読むことをお勧めします。 これは、共有メモリに 8 回アクセスしてそれぞれ 34 サイクルを必要とし、8 回の FFMA 演算 (32 を並列で実行) をそれぞれ 4 サイクル必要とすることを意味します。 したがって、合計は次のようになります。

200サイクル(グローバルメモリへのアクセス)+ 8.34サイクル(共有メモリへのアクセス)+ 8.4サイクル(FFMA操作)= 504サイクル

ここで、テンソル計算カーネルを使用してこの行列乗算を実行するのに何サイクルかかるかを見てみましょう。

1.2 テンソル計算カーネルを用いた行列乗算

テンソルコンピューティングコアを使用することで、4×4 行列乗算を 1 サイクルで実行できます。これを実現するには、まずメモリデータをテンソルコンピューティングコアに転送する必要があります。前のステップと同様に、グローバルメモリからデータを読み取り (200 サイクル必要)、共有メモリに格納する必要があります。32×32 行列乗算の場合、64 回のテンソルコンピューティングコア操作 (つまり、8×8=64) が必要です。各 SM には 8 つのテンソルコンピューティングコアがあるため、8 つの SM があれば 64 個のテンソルコンピューティングコアがあり、これはニーズに完全に適合します。共有メモリからテンソルコンピューティングコアにデータを 1 回のメモリ転送で転送し (34 サイクル必要)、その後 64 回の並列テンソルコンピューティングコア操作 (1 サイクルのみ必要) を実行できます。したがって、この場合、テンソルコンピューティングコアを使用した行列乗算に必要な合計時間は次のとおりです。

200 サイクル (グローバル メモリにアクセス) + 34 サイクル (共有メモリにアクセス) + 1 サイクル (テンソル計算コアを使用) = 235 サイクル。

そのため、テンソル計算コアを使用することで、行列乗算の時間コストを504サイクルから235サイクルへと大幅に削減できました。この簡略化されたケースでは、テンソル計算コアのアプローチにより、共有メモリアクセスとFFMA演算の時間コストが削減されています。

この例は簡略化されています。通常、グローバルメモリから共有メモリにデータを転送する場合、各スレッドは読み書きするメモリ位置を計算する必要があります。新しいHooper(H100)アーキテクチャでは、Tensor Memory Accelerator(TMA)がこれらのインデックスをハードウェアで計算できるため、各スレッドはインデックス計算ではなく、より計算的なタスクに集中できます。

1.3 テンソルコンピューティングコアと非同期コピーを使用した行列乗算(RTX 30/RTX 40)およびTMA(H100)

RTX 30 AmpereおよびRTX 40 AdaシリーズGPUは、グローバルメモリと共有メモリ間の非同期転送もサポートしています。H100 Hopper GPUは、Tensor Memory Accelerator(TMA)ユニットの導入により、この機能をさらに拡張しています。TMAユニットは非同期コピーとインデックス計算を組み合わせるため、各スレッドは次の要素の読み取りを計算する必要がなくなり、より多くの行列乗算演算の実行に集中できます。詳細は以下をご覧ください。

TMAユニットはグローバルメモリからデータを取得し、共有メモリに転送します。この処理には200サイクルかかります。データが到着すると、TMAユニットはグローバルメモリから次のデータブロックを非同期的に取得します。この処理の間、スレッドは共有メモリからデータを読み込み、テンソル演算コアを使用して行列乗算を実行します。スレッドはタスクを完了すると、TMAユニットが次のデータ転送を完了するまで待機してから、この処理を繰り返します。

したがって、非同期の性質により、スレッドが現在の共有メモリタイルを処理している間に、TMAユニットは既に2番目のグローバルメモリ読み取りを開始しています。つまり、2番目の読み取りには200 - 34 - 1 = 165サイクルしかかかりません。

複数回の読み出しを実行したため、最初のメモリアクセスのみが比較的遅く、それ以降のメモリアクセスはTMAセルと重複していました。そのため、平均で35サイクルの削減が実現しました。

165 サイクル (非同期コピーの完了を待機) + 34 サイクル (共有メモリへのアクセス) + 1 サイクル (テンソル計算コアの使用) = 200 サイクル。

これにより、行列乗算演算の速度がさらに約 15% 向上します。

これらの例は、次に議論する属性であるメモリ帯域幅が、テンソルコンピューティングコアを搭載したGPUにとってなぜ非常に重要であるかを明確に示しています。グローバルメモリへのアクセスは、現在、テンソルコンピューティングコアを用いた行列乗算演算において最も時間のかかる方法です。グローバルメモリのレイテンシを削減することで、開発者はさらに高速なGPUを実現できる可能性があります。これは、メモリクロック周波数を上げることで実現できます(1秒あたりのサイクル数は増加しますが、発熱と消費電力も増加します)。これにより、同時に転送できる要素数(バス幅)を増やすことができます。

02 メモリ帯域幅

先ほど述べたように、テンソル計算コアは非常に高速です。そのため、グローバルメモリからデータが転送されるのを待つ間、ほとんどの時間はアイドル状態になっています。例えば、 GPT-3規模の大規模ニューラルネットワークを大きな行列を用いて学習させる場合(大きな行列の方がテンソル計算コアの演算に有利であるため)、この場合でもテンソル計算コアの使用率はわずか45~65%程度です。これは、大規模なニューラルネットワークを学習させる場合でも、テンソル計算コアがアイドル状態になる時間が約50%あることを意味します。

これは、テンソルコンピューティングコアを搭載した2つのGPUを比較する場合、考慮すべき重要なパフォーマンス指標がメモリ帯域幅であることを示しています。例えば、A100 GPUのメモリ帯域幅は1555 GB/sですが、V100は900 GB/sです。したがって、A100のV100に対する速度向上は、1555/900 = 1.73倍と推定されます。

03 L2キャッシュ/共有メモリ/L1キャッシュ/レジスタ

テンソル計算コアへのデータ転送速度の遅さは、GPUパフォーマンスの大きな制限要因です。そのため、テンソル計算コアへのデータ転送を高速化するには、他のGPU特性を通してこの制限に対処する方法を見つける必要があります。L2キャッシュ、共有メモリ、L1キャッシュ、および使用されるレジスタの数はすべて関連するGPU特性です。GPU上での行列乗算の実行プロセスを理解することで、現在のメモリ階層がメモリ転送速度をどのように向上させるかをよりよく理解できます。(訳者注:メモリ階層とは、コンピュータシステム内の異なるレベルのメモリコンポーネントを速度と容量に応じて階層的に配置することです。このアーキテクチャは、大きくても低速なメモリ(メインメモリなど)から小さくても高速なメモリ(キャッシュなど)やレジスタまで、複数の層に分かれています。この設計は、最も頻繁に使用されるデータをより高速なメモリレベルに格納することで、データアクセス速度とシステムパフォーマンスを向上させることを目的としています。下の図は、微細なGPUメモリ階層の例を示しています。)

画像は翻訳者提供。GeForce GTX780 (Kepler) のメモリ階層。Mei, X., & Chu, X. (2015). マイクロベンチマークによるGPUメモリ階層の分析。IEEE Transactions on Parallel and Distributed Systems, 28, 72-86.

行列乗算を実行するには、GPUのメモリ階層を有効活用する必要があります。低速なグローバルメモリから高速なL2キャッシュ、高速なローカル共有メモリ、そして超高速なレジスタへと続きます。しかし、メモリが高速であればあるほど、そのサイズは小さくなります。

論理的にはL1キャッシュと似ていますが、L2キャッシュはサイズが大きいため、キャッシュラインを取得するために必要な平均物理距離が長くなります。L1キャッシュとL2キャッシュは、必要なアイテムを取り出す倉庫と考えることができます。アイテムがどこにあるか分かっていても、倉庫が大きいほど、平均して目的の場所に到達するのに時間がかかります。これがL1キャッシュとL2キャッシュの根本的な違いです。大きい=遅い、小さい=速い。

行列乗算では、このメモリ階層化手法を用いることで、行列を徐々に小さく高速なメモリブロックに分割し、非常に高速な行列乗算演算を実現できます。そのためには、大きな行列乗算演算をより小さなサブ行列乗算演算に分割する必要があります。これらのメモリブロックはメモリタイルと呼ばれ、多くの場合、単にタイルと呼ばれます。

これらの小さなタイルを用いて、CPUコアと同様にストリーミングマルチプロセッサ(SM)の近くに配置された高速なローカル共有メモリ内で行列乗算を実行します。テンソルコンピューティングコアを使用することで、さらに一歩進んで、各タイルの一部をレジスタで直接アドレス指定されるテンソルコンピューティングコアにロードできます。L2キャッシュ内の行列メモリタイルはGPUグローバルメモリ(GPU RAM)の3~5倍、共有メモリはGPUグローバルメモリの約7~10倍、テンソルコンピューティングコアのレジスタはGPUグローバルメモリの約200倍高速です。

タイルが大きいほど、より多くのメモリ空間を再利用できることを意味します。この点については、TPU vs GPUに関するブログ記事[3]で詳しく説明しています。実際、 TPUの各テンソル計算コアは非常に大きなタイルを持っていることがわかります。そのため、 TPUはグローバルメモリからデータを転送するたびにより多くのメモリを再利用できるため、行列乗算演算においてGPUよりも効率的です。

各タイルのサイズは、各ストリーミングマルチプロセッサ(SM)のメモリと、全SMにまたがるL2キャッシュのサイズによって決まります。以下は、異なるアーキテクチャのGPUにおける共有メモリのサイズです。

  • Volta (Titan V): 128KB共有メモリ / 6MB L2キャッシュ

  • Turing (RTX 20 シリーズ): 96KB 共有メモリ / 5.5MB L2 キャッシュ

  • Ampere (RTX 30 シリーズ): 128KB 共有メモリ / 6MB L2 キャッシュ

  • Ada (RTX 40 シリーズ): 128KB 共有メモリ / 72MB L2 キャッシュ

Ada アーキテクチャの GPU にはより大きな L2 キャッシュがあり、より大きなタイル サイズに対応できるため、グローバル メモリへのアクセスが削減されることがわかりますたとえば、BERT large のトレーニング中、行列乗算演算の入力行列と重み行列は、Ada アーキテクチャの L2 キャッシュに完全に収容できます (他のアーキテクチャとは異なります)。したがって、データはグローバル メモリから一度ロードし、次に L2 キャッシュから取得するだけでよく、Ada アーキテクチャでは行列乗算が約 1.5 ~ 2.0 倍高速になります。より大きなモデルの場合、トレーニング中の高速化は小さくなる可能性がありますが、特定のスイート スポット (特定のモデル サイズ、バッチ サイズ、またはその他のパラメーター設定により、特定のポイントまたは領域でモデルのトレーニングが高速化または効率化される) が存在するため、一部のモデルのトレーニングが高速化される可能性があります。バッチ サイズが 8 を超える大規模モデル推論タスクでは、L2 キャッシュを大きくすると推論の効率が向上します。