計算を行列乗算として表現すると、なぜ高速になるのですか?


18

TensorFlowを使用した GoogleのMNistチュートリアルでは、1つのステップが行列にベクトルを掛けることに相当する計算が示されています。Googleは最初に、計算の実行に使用される各数値の乗算と加算が完全に書き出された図を示します。次に、代わりに行列乗算として表されている図を示します。このバージョンの計算はより高速であるか、少なくとも高速であると主張しています。

それを方程式として書くと、次のようになります。

スカラー方程式

この手順を「ベクトル化」して、行列乗算とベクトル加算に変換できます。これは計算効率に役立ちます。(それはまた、考えるのに便利な方法です。)

ベクトル方程式

このような方程式は通常、機械学習の実践者によって行列乗算形式で記述されていることを知っています。もちろん、コードの簡潔さや数学の理解の観点からそうすることの利点を理解できます。私が理解していないのは、長文形式から行列形式への変換が「計算効率に役立つ」というGoogleの主張です

計算を行列乗算として表現することにより、いつ、なぜ、どのようにソフトウェアのパフォーマンスを向上させることができますか?人間として、2番目の(マトリックスベースの)イメージでマトリックスの乗算を自分で計算する場合、最初の(スカラー)イメージに示されている個別の計算を順番に実行することでそれを行います。私にとって、それらは同じ計算シーケンスの2つの表記法にすぎません。なぜ私のコンピューターと違うのですか?なぜコンピューターはスカラー計算よりも速くマトリックス計算を実行できるのでしょうか?

回答:


19

これは明らかに聞こえるかもしれませんが、コンピュータが実行されない数式を、彼らは実行コードを、そしてどのくらいの実行がかかることを、彼らはどんなコンセプトそのコードの実装にのみ間接的に実行し、コードに直接依存します。論理的に同一の2つのコードは、非常に異なるパフォーマンス特性を持つ場合があります。特に行列の乗算で発生する可能性が高いいくつかの理由:

  • 複数のスレッドを使用します。複数のコアを持たない最新のCPUはほとんどなく、多くは最大8個あり、高性能コンピューティング専用のマシンは複数のソケットに簡単に64個搭載できます。通常のプログラミング言語で、明らかな方法でコードを記述すると、そのうちの1つだけが使用されます。つまり、実行中のマシンの使用可能なコンピューティングリソースの2%未満しか使用しない可能性があります。
  • SIMD命令の使用(紛らわしいことに、これは「ベクトル化」とも呼ばれますが、質問のテキスト引用とは異なる意味で)。本質的に、4つまたは8つ程度のスカラー算術命令の代わりに、CPU に4つまたは8つ程度のレジスタで並列に演算を実行する1つの命令を与えます。これにより、文字通りいくつかの計算を行うことができます(完全に独立していて、命令セットに適合している場合)。
  • キャッシュをよりスマートに使用する一時的および空間的に一貫性がある場合、つまり、連続したアクセスが近くのアドレスにあり、アドレスに2回アクセスする場合、長い休止ではなく連続して2回アクセスすると、メモリアクセスが高速になります。
  • GPUなどのアクセラレーターを使用します。これらのデバイスはCPUとは非常に異なる獣であり、それらを効率的にプログラミングすることは、それ自体が芸術の形です。たとえば、数百個のコアがあり、それらは数十個のコアのグループにグループ化され、これらのグループはリソースを共有します。ifそのグループの他のすべての人はそれを待つ必要があります。
  • 作業を複数のマシン(スーパーコンピューターでは非常に重要です!)に分散させます。これにより、新しい頭痛の種が大量に発生しますが、もちろん、非常に大きなコンピューティングリソースにアクセスできます。
  • よりスマートなアルゴリズム。行列乗算の場合、上記のトリックで適切に最適化された単純なO(n ^ 3)アルゴリズムは、適切な行列サイズを得るためにサブキュービックのものよりも速いことがよくありますが時々勝ちます。スパース行列などの特殊なケースでは、特殊なアルゴリズムを作成できます。

多くの賢明な人々が、上記のトリックを使用して、一般的な線形代数演算のための非常に効率的なコードを書いてきました。したがって、数式を行列乗算に変換してから、成熟した線形代数ライブラリを呼び出してその計算を実装すると、その最適化の効果が得られます。対照的に、高レベル言語で明白な方法で式を単純に記述すると、最終的に生成されるマシンコードはこれらのトリックをすべて使用せず、高速になりません。これは、マトリックスの定式化を行い、自分で作成した単純なマトリックス乗算ルーチンを呼び出して実装する場合にも当てはまります(これも明らかな方法です)。

コードを高速化するには作業が必要であり、最後の1オンスのパフォーマンスが必要な場合は、多くの場合非常に多くの作業が必要になります。非常に多くの重要な計算は、いくつかの線形代数演算の組み合わせとして表現できるため、これらの演算に対して高度に最適化されたコードを作成するのは経済的です。ただし、1回限りの特殊なユースケースですか?あなた以外は誰も気にしないので、それを完全に最適化するのは経済的ではありません。


4

(スパース)行列ベクトル乗算は高度に並列化可能です。データが大きく、サーバーファームを自由に使用できる場合、これは非常に便利です。

これは、マトリックスとベクトルをチャンクに分割し、別々のマシンに作業の一部を任せることができることを意味します。次に、それらの結果の一部を互いに共有し、最終結果を取得します。

あなたの例では、操作は次のようになります

  1. グリッド内の座標に従って、それぞれがWx、yを保持するプロセッサのグリッドを設定します

  2. 各列に沿ってソースベクトルをブロードキャストします(コストO(log height)

  3. 各プロセッサをローカルで乗算する(コストO(width of submatrix * heightof submatrix)

  4. 合計(コストO(log width))を使用して各行に沿って結果を折りたたみます

合計は結合的であるため、この最後の操作は有効です。

これにより、冗長性を構築でき、すべての情報を単一のマシンに入力する必要がなくなります。

グラフィックスに見られるような小さな4x4行列の場合、CPUにはこれらの操作を処理するための特別な命令とレジスタがあるためです。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.