行列乗算の最適化と畳み込みにおけるその応用

行列乗算の最適化と畳み込みにおけるその応用

導入

天気予報、石油探査、原子物理学などの現代の科学技術は、主にコンピュータシミュレーションに依存しています。シミュレーション計算の核心は、状態遷移を表す行列計算です。一方、近年登場したコンピュータグラフィックス処理やディープラーニングも行列乗算と深く関係しています。行列の乗算は多くの計算リソースを消費します。コンピュータ アーキテクチャの継続的な更新に加えて、ソフトウェアの最適化に関する研究も数多く行われています。

この記事では、一般行列乗算 (GEMM) 最適化の基本的な概念と方法、特定のシナリオでの行列乗算のための QNNPACK の最適化方法、およびニューラル ネットワークでの畳み込み計算を最適化するために GEMM を使用する方法について簡単に説明します。

目的は、誰もが概念について直感を養えるようにすることであり、深遠な理論はありません。

一般的な行列乗算の最適化

基本概念

一般行列乗算 (GEMM) の一般的な形式は 𝐶=𝐴𝐵C=AB であり、ここで 𝐴A と 𝐵B はそれぞれの転置の意味をカバーします。図 1 は、行列乗算計算の出力ポイントを計算するために使用される入力データを示しています。 3 つのマトリックスの形状も図に示されています。

図1: 1つの出力要素による行列乗算の計算

この計算の疑似コードは次のとおりです。計算操作の総数は(𝑀、𝑁、𝐾は3層ループの実行回数を指し、2はループの最内層の乗算と加算を指します)、メモリアクセス操作の総数は4𝑀𝑁𝐾(4は𝐴、𝐵、𝐶へ​​のメモリアクセスを指します。𝐶は最初にメモリを読み取り、累積して格納し、𝐶が初期化されるときの操作を無視する必要があります)です。 GEMM の最適化はこれに基づいています。

  1. (int m = 0 ; m <   M ;m++){  
  2. (int n = 0 ; n <   N ;n++){  
  3. C[m][n] = 0;  
  4. (int k = 0 ; k <   K ;k++){  
  5. C[m][n] += A[m][k] * B[k][n];  
  6. }  
  7. }  
  8. }

gemm を最適化する方法 (https://github.com/flame/how-to-optimize-gemm/wiki) では、さまざまな最適化手法を使用して最も基本的な計算を約 7 倍改善する方法が示されています (図 2 を参照)。基本的なアプローチは、入力データの再利用性を向上させるために、出力をいくつかの 4×4 サブブロックに分割することです。同時に、レジスタを多用してメモリアクセスを削減したり、メモリアクセスと計算をベクトル化したり、ポインタ計算を排除したり、アドレスが連続するようにメモリを再編成したりといったことも行われます。詳細は原文をご参照ください。

図2: gemmの最適化結果を最適化する方法

計算分割表示

このセクションでは、主に計算の分割をグラフィカルな方法で紹介します。

図3は出力計算を1×4の小さなブロックに分割します。つまり、𝑁次元を2つの部分に分割します。このブロックの出力を計算するときは、𝐴行列の1行と𝐵行列の4列を使用する必要があります。

図3: 行列乗算計算1×4出力

以下は、この計算の疑似コード表現です。1×4 の 𝑁N 次元の内部分割が拡張されています。ここでの計算回数は依然として 2𝑀𝑁𝐾 であり、この記事では変更されません。ここでのメモリアクセス操作の回数は変更されておらず、依然として 4𝑀𝑁𝐾 ですが、今後徐々に改善されていく予定です。

  1. (int m = 0 ; m <   M ;m++){  
  2. (int n = 0 ; n <   N ; n += 4) {  
  3. C[m][n + 0] = 0;  
  4. C[m][n + 1] = 0;  
  5. C[m][n + 2] = 0;  
  6. C[m][n + 3] = 0;
  7. (int k = 0 ; k <   K ;k++){  
  8. C[m][n + 0] += A[m][k] * B[k][n + 0];  
  9. C[m][n + 1] += A[m][k] * B[k][n + 1];  
  10. C[m][n + 2] += A[m][k] * B[k][n + 2];  
  11. C[m][n + 3] += A[m][k] * B[k][n + 3];  
  12. }  
  13. }  
  14. }

単純な観察から、上記の疑似コードの最も内側の計算で使用される行列 𝐴 の要素が一貫していることがわかります。したがって、𝐴[𝑚][𝑘]をレジスタに読み込むことができ、4倍のデータ多重化が実現されます(ここでは例は示していません)。最も内側のループは一般にマイクロカーネルと呼ばれます。このような最適化を行うと、メモリアクセス操作の回数は (2+1/4)𝑀𝑁𝐾 となり、ここで 1/4 は 𝐴 の最適化による効果です。

同様に、図 4 に示すように、出力の 𝑀 次元を分割して、内側のループで 4×4 出力を計算し続けることができます。

図4: 行列乗算計算4×44×4出力

同様に計算コアを展開すると、次の疑似コードが得られます。ここでは、1×4 に示されている 𝑁 次元の計算を簡略化します。この分割は4×1×4と見なすことができ、𝐴と𝐵のメモリアクセスを4回再利用できます。乗数効果により、4×4 分割により入力データへのメモリ アクセスを 2𝑀𝑁𝐾+1/4𝑀𝑁𝐾+1/4𝑀𝑁𝐾=(2+1/2)𝑀𝑁𝐾) に削減できます。これは、元の 4𝑀𝑁𝐾 と比較して 1.6 倍の改善です。これらの改善は、ループを展開し、レジスタを使用してデータを保存し、メモリ アクセスを削減することで実現されます。

  1. (int m = 0 ; m <   M ; m += 4) {  
  2. (int n = 0 ; n <   N ; n += 4) {  
  3. C[m + 0][n + 0..3] = 0;  
  4. C[m + 1][n + 0..3] = 0;  
  5. C[m + 2][n + 0..3] = 0;  
  6. C[m + 3][n + 0..3] = 0;  
  7. (int k = 0 ; k <   K ;k++){  
  8. C[m + 0][n + 0..3] += A[m + 0][k] * B[k][n + 0..3];  
  9. C[m + 1][n + 0..3] += A[m + 1][k] * B[k][n + 0..3];  
  10. C[m + 2][n + 0..3] += A[m + 2][k] * B[k][n + 0..3];  
  11. C[m + 3][n + 0..3] += A[m + 3][k] * B[k][n + 0..3];  
  12. }  
  13. }  
  14. }

これまでは出力の2次元で拡張してきましたが、計算全体には縮小次元𝐾も含まれます。図5は、4×4出力を計算するときに次元𝐾が分割され、最も内側のループごとに出力行列𝐶の4×4部分和が計算されることを示しています。

図5: 4×4行列乗算出力を𝐾次元に分割する

以下に、計算のこの部分の拡張擬似コードを示します。ここでは、次元 𝑀 と 𝑁 が省略されています。ここで、最も内側のループで実行される計算の数は、単純なバージョンから に増加しました。

  1. (int m = 0 ; m <   M ; m += 4) {  
  2. (int n = 0 ; n <   N ; n += 4) {  
  3. C[m + 0..3][n + 0..3] = 0;  
  4. C[m + 0..3][n + 0..3] = 0;  
  5. C[m + 0..3][n + 0..3] = 0;  
  6. C[m + 0..3][n + 0..3] = 0;  
  7. (int k = 0 ; k <   K ; k += 4) {  
  8. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 0] * B[k + 0][n + 0..3];  
  9. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 1] * B[k + 1][n + 0..3];  
  10. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 2] * B[k + 2][n + 0..3];  
  11. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 3] * B[k + 3][n + 0..3];  
  12. }  
  13. }  
  14. }

𝑀と𝑁を展開する場合、それぞれ𝐵と𝐴のデータを再利用できます。𝐾を展開する場合、部分和をレジスタに蓄積し、最も内側のループの各反復の最後にそれらを𝐶のメモリに書き込むことができます。するとメモリアクセスの回数は 

-- 元の実装に比べて 4 倍の改善!

これまで、計算では 3 次元分割、展開とレジスタ化 (コードは示されていません)、およびメモリ アクセスの再利用を実行しました。何百もの安定した順次計算命令がすでにプロセッサ パイプラインに適しているため、これは最も基本的なバージョンの計算には十分なようです。また、コンパイラはソフトウェア パイプラインなどの命令スケジューリングを支援できます。

ただし、1 つの計算命令では 1 つの乗算と加算の演算 (MLA) しか完了できないため、比較的非効率的です。実際、最も低価格の携帯電話プロセッサでも SIMD がサポートされており、メモリ アクセスと計算をベクトル化できます。したがって、さらに一歩進んでベクトル演算を使用すると、計算のパフォーマンスを向上させることができます。

ベクトル化された計算の詳細を紹介すると、疑似コードではわかりにくいので、図6に基づいて量子化計算の具体的な処理を紹介します。図 6 の左側にある 3 つの小さな図は、2 つの 4×4 行列を乗算するためのベクトル化の要素を示しています。最初は、出力要素を計算するために使用される入力要素、次に、すべて行優先形式で番号が付けられた各行列メモリのエンコード、最後にベクトル化の特定の計算方法です。

図6: 2つの4×4行列の乗算のベクトル化

ここでのベクトル番号付けは、入力と出力のメモリ レイアウトが行優先であることを前提としているため、2 つの入力のそれぞれ 16 個の要素を 4 回のベクトル メモリ アクセスを通じてレジスタにロードできます。行列の乗算の規則から、入力𝐴の行は出力の計算に一度に使用できますが、入力𝐵Bの行は分割して使用する必要があることがわかります。これは、ベクトル計算で最もエラーが発生しやすい場所でもあります。

図 6 の右側に 3 つの疑似コードがリストされています。最初の C0 の詳細は、C0 の 4 つの出力要素を計算する単純な方法の拡張であり、4 つの連続した計算によって C0 の 1 つの要素が生成されます。計算プロセスを少し並べ替えると、スケジュールされた C0 に示す計算が得られます。この計算では、連続する 4 つの計算で、C0 の 4 つの要素の結果の 1/4 がそれぞれ処理されます。 4回の連続した計算では、並べ替え前は𝐴Aへのメモリアクセスのみが連続しており、並べ替え後は𝐵と𝐶の両方へのメモリアクセスが連続しています。次に、これらのメモリ アクセスと計算をベクトル化することで、疑似コードの 3 列目の赤い C0 部分を取得できます。 3 列目は、C1、C2、C3 に対して同様の処理を実行することによって取得されます。

ベクトル化を実装すると、本来 64 個の計算命令を必要とした計算処理に必要な命令数が 16 個に削減され、メモリアクセスでも同様の効果が得られます。ベクトル化によるプロセッサ リソースの効率的な使用により、最適化の余地がさらに広がり、たとえば、一度に 8×8 のローカル出力を計算できるようになります。

メモリレイアウトの処理

前のセクションでは、入力と出力の元のメモリ レイアウトに対して行われた最適化を示します。最後にベクトル化されると、各メモリ アクセスは 4 つの要素になります。要素が単精度浮動小数点数の場合、メモリ サイズは 16 バイトになります。これは、通常 64 バイトである最新のプロセッサのキャッシュ ライン サイズよりもはるかに小さくなります。この場合、メモリレイアウトがコンピューティングパフォーマンスに与える影響が現れ始めます。

図 7 は、さまざまなメモリ構成方法が に与える影響を示しています。どちらの図も行優先のメモリ配置になっていますが、違いは、左側の小さな四角形の内部が不連続であるのに対し、右側の小さな四角形の内部は連続している点です。この図では、メモリ全体の各要素の数を示すために複数の数字が使用されています。

図7: 異なるローカルメモリ構成

これら 2 つの異なるメモリ構成の入力と出力でメモリにアクセスすると想像してください。各ベクトル化されたメモリ ロードは依然として 4 つの要素です。ローカル計算で使用される小さなブロックの場合、左側の 4 回アクセスされるメモリは不連続ですが、右側のメモリは連続しています。データ サイズがわずかに大きい場合 (一般的に言えば、十分な大きさです)、左側の 4 つの連続するベクトル化されたメモリ ロードはすべてキャッシュ ミスになりますが、右側では 1 つのミスのみになります。

従来のデータスケールでは、左側でキャッシュミスが多すぎ、行列乗算などの計算ではデータアクセスの繰り返しが多いため、右側のメモリレイアウトに再配置してキャッシュミスを減らすと、パフォーマンスが大幅に向上します。一方、行列の乗算では、2 つの入力行列のうちの 1 つは、複数の計算で変更されない固定パラメータであることがよくあります。その後、計算を開始する前に特定の形状に整理することができます。この最適化により、パフォーマンスが 2 倍向上することもあります。

この時点で、4×4 の計算は十分に最適化されており、より広い視点でグローバル最適化を検討し始めることができます。図 8 はグローバル最適化の小さな例です。

図8: 行列乗算のグローバル最適化の概要

図中の文字は、全体的な動作順序、つまり出力データ内の外側のループ反復方法を示しています。左のサブ画像は従来の行優先のトラバーサル方式で、中央のサブ画像は列制限のトラバーサル方式です。両者の違いは、次元 𝑀 と 𝑁 のどちらのサイクルが一番外側にあるかです。

すでに上で 2 つの次元 𝑀 と 𝑁 を一度分割しましたが、ここでもこの分割を続けることができます。右の図では、2 つの次元 𝑀 と 𝑁 がそれぞれ 𝑋、2、4 の 3 つの部分に分割され、外側の分割が外側のループに交換されています。以下は対応する疑似コードです。

  1. (int mo = 0 ; mo <   M ; 月 += 8) {  
  2. (int no = 0 ; no <   N ; いいえ += 8) {  
  3. (int mi = 0 ; mi <   2 ;mi ++) {  
  4. (int ni = 0 ; ni <   2 ; ニ++) {  
  5. 整数m = mo + mi * 4;  
  6. 整数n = no + ni * 4;  
  7. C[m + 0..3][n + 0..3] = 0;  
  8. C[m + 0..3][n + 0..3] = 0;  
  9. C[m + 0..3][n + 0..3] = 0;  
  10. C[m + 0..3][n + 0..3] = 0;  
  11. (int k = 0 ; k <   K ; k += 4) {  
  12. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 0] * B[k + 0][n + 0..3];  
  13. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 1] * B[k + 1][n + 0..3];  
  14. C[m + 0..3][n + 0..3] += A[m + 0..3][k + 2] * B[k + 2][n + 0..3];
  15.   C[m + 0..3][n + 0..3] += A[m + 0..3][k + 3] * B[k + 3][n + 0..3];  
  16. }  
  17. }  
  18. }  
  19. }  
  20. }

このようなスケジュール設定をすると、全体の計算の観点からは、4×4 の計算を 8×8 に拡張しているように見えますが、これは実際には同じ考え方です。

QNNPACK の行列乗算最適化

QNNPACK (Quantized Neural Network PACKage) は、量子化ニューラル ネットワーク専用に Facebook が開発したオープン ソースのコンピューティング アクセラレーション ライブラリです。 QNNPACK と NNPACK (Neural Network PACKage) の著者はどちらも Marat Dukhan です。現在までに、QNNPACK はモバイル デバイス (携帯電話) 向けの公開されている最高のパフォーマンスを誇る量子化ニューラル ネットワーク アクセラレーション ライブラリです。

QNNPACK がオープンソース化されたとき、技術レポートであるブログが付属していました。このセクションでは、前のセクションの内容に基づいて、元のブログから GEMM に関するいくつかのコンテンツを簡単に抜粋します。

量子化ニューラルネットワーク

ニューラル ネットワークの計算は、通常、単精度浮動小数点 (FP32) に基づいています。ネットワーク アルゴリズムの開発により、ニューラル ネットワークのコンピューティングとメモリに対する要件がますます高まり、モバイル デバイスでは対応できないほどになっています。計算速度を向上させるために、ニューラル ネットワークに量子化が導入されました。主流の方法は、ニューラル ネットワーク アルゴリズム内の重みパラメータと計算を FP32 から INT8 に変換することです。

2つの数値表現方法の式は上記のとおりです。量子化技術の基本原理に興味がある場合は、「ニューラル ネットワークの量子化入門」を参照してください。

量子化技術を適用した後、コンピューティングにおいていくつかの新しい問題が出現しました。まず、FP32 用の NNPACK などのコンピューティング高速化ライブラリは INT8 では使用できないため、新しい高速コンピューティング方法が必要になります。さらに、入力と出力が INT8 に変換されると、メモリ帯域幅の要件が直接 1/4 に削減されます。結果として生じるメモリ容量要件の変化により、いくつかの新たな最適化の機会が生まれます。 QNNPACK はこれらの最適化手法を最大限に活用し、ニューラル ネットワーク分野の特性と組み合わせることで、コンピューティング パフォーマンスを大幅に向上させます。

一方、Gemmlowp は Google のオープンソースの低精度 GEMM アクセラレーション ライブラリです。 Gemmlowp は、高速化に加えて、固定小数点計算を使用して浮動小数点計算をシミュレートするメカニズムを提供するという点で特別です。たとえば、Gemmlowp を使用すると、固定小数点計算のみをサポートするプロセッサでも浮動小数点計算を実行できます。 QNNPACK とは異なり、Gemmlowp は純粋なニューラル ネットワークではなく GEMM のサポートを目的としているようです。そのため、ニューラル ネットワークにおけるパフォーマンスは現時点では QNNPACK に遅れをとっています。

計算による分割と次元削減

前のセクションと同様に、QNNPACK の計算も、図 9 に示すように、出力を 𝑀𝑅×𝑁𝑅MR×NR の小さなブロックに分割することに基づいています。ここで注意する必要があるのは、元の凡例の𝐵Bのラベルにタイプミスがあることです。𝐵Bの列の高さは𝑁Nではなく𝐾Kである必要があります。 (この問題は QNNPACK に報告しましたが、まだ修正されていません。)

図9: QNNPACK行列乗算パーティションの例

QNNPACK は、4×8 と 8×8 の 2 種類のコンピューティング カーネル (マイクロ カーネル) を実装します。これらは、それぞれ armv7 と arm64 命令セットをサポートするプロセッサに使用されます。これら 2 つのコンピューティング コアには原理的にほとんど違いはありません。後者は主に、より多くのレジスタとデュアル発行を使用して計算の並列性を向上させます。

分割後、𝑀𝑅×𝑁𝑅計算ブロックが使用するメモリは𝑀𝑅∗𝑁𝑅+𝑀𝑅∗𝐾+𝑁𝑅∗𝐾です。従来のニューラルネットワーク計算は 𝐾<1024 であるため、ここでのメモリ消費量は通常 16KB を超えず、第 1 レベル キャッシュ (L1 キャッシュ) に収容できます。この QNNPACK の発見は、行列乗算の最適化の基礎となります。

図10に示すように、𝑀𝑅×𝑁𝑅の小さなブロックを計算する場合、従来の方法(つまり、前節の方法)では、𝐾次元で分割し、1回の計算カーネル処理でローカル𝐾次元のみを計算します。次に、コンピューティング カーネルの各処理で、出力がロードされ、保存されます。この計算によって生成された部分合計が出力に累積されます。

図10: QNNPACKと従来の手法による次元削減の比較

一方、QNNPACK は計算カーネル内で 𝐾K 次元全体を処理するため、出力部分和のメモリアクセスがほぼ完全に排除されます。両者の違いの詳細は、図 10 の両側の疑似コードで確認できます。ここで言う「𝐾次元全体」とは、𝐾次元が分割されないという意味ではなく(実際の計算では、𝐾K次元は8に基づいて分割されます)、分割後に他の次元と交換されないことを意味します。

記憶組織の特徴

前のセクションで述べたように、メモリを再パックするとキャッシュヒット率が向上し、パフォーマンスが向上します。しかし、この再編成にもオーバーヘッドが伴います。

コンピューティング コア内の最小のコンピューティング ユニットは、2 つの 4×4 行列の乗算を処理します。 𝐾 は非常に大きい可能性があるため、従来の方法では、図 11 に示すように、隣接するメモリ アクセスによって発生するキャッシュ競合を防ぐために入力メモリを再編成する必要があります。

図11: QNNPACKと従来の行列乗算による局所計算処理

量子化ニューラルネットワークでは、𝐾K が比較的小さいため、コンピューティングコアの処理で使用されるメモリは、第 1 レベル キャッシュに完全に収容できます。メモリを再編成しなくても、キャッシュの再利用率は十分に高くなります。

図 7 の左側を参照すると、QNNPACK カーネルは一度に 8 行の入力を使用します (図が 8 に基づいてブロックに分割されていると仮定)。最初の 8×8 行列ブロックのベクトル化されたロードはすべてキャッシュ ミスになる可能性がありますが、2 番目の 8×8 はすべてヒットになります。これは、最初の行列ブロックと同じキャッシュ ライン上のコンテンツとしてすでにキャッシュにロードされているためです。他のマトリックス ブロックについても同様です。

ニューラル ネットワーク分野における事前の知識に基づいたこれらの最適化手法を採用することで、QNNPACK はニューラル ネットワーク量子化の分野におけるすべてのモバイル アクセラレーション ライブラリを上回ります。ただし、QNNPACK の分割は次元の削減に重点を置いており、出力次元に対してグローバル スケジューリングは実行しません。 QNNPACK に基づいて、𝑀 次元と 𝑁 次元の外側のループ分割スケジューリングを実装しました。簡単な実験で、QNNPACK に比べて 1.1 倍のパフォーマンス向上が達成されました。

畳み込みと行列乗算

畳み込みはニューラル ネットワークの核となる計算です。畳み込みにはさまざまなバリエーションがあり、計算も比較的複雑なため、最適化アルゴリズムも im2col、Winograd など多岐にわたります。このセクションでは、畳み込みと行列乗算の関係に焦点を当てます。

im2col計算方法

初期のディープラーニング フレームワークとして、Caffe は im2col ベースの方法を使用して畳み込みを実装します。これは、現在でも畳み込みの重要な最適化方法の 1 つです。

im2col は、コンピューター ビジョンの分野における計算プロセスであり、画像のさまざまなチャネルをマトリックスの列に変換します。 Caffe は畳み込みを計算する際、まず im2col を使用して入力された 3 次元データを 2 次元行列に変換します。これにより、畳み込み計算は 2 つの 2 次元行列の乗算として表現され、最適化された GEMM ライブラリを最大限に活用して、各プラットフォームの畳み込み計算を高速化します。

図 12 は、畳み込みの im2col プロセスの例です。畳み込みフィルタが入力上をスライドすると、使用される入力の部分がサイズ 𝐼𝐶×𝐾𝐻×𝐾𝑊 の行ベクトルに拡張されます。スライドが完了すると、特徴行列(𝐻×𝑊)×(𝐼𝐶×𝐾𝐻×𝐾𝑊)が得られます。フィルターを (𝑂𝐶)×(𝐼𝐶×𝐾𝐻×𝐾𝑊) の行列に展開すると、畳み込みはこれら 2 つの行列を乗算した結果として表現できます (特徴行列は転置する必要があります)。

図12: im2colプロセス

im2col で GEMM を使用して畳み込みを計算するコストは追加のメモリ オーバーヘッドであり、入力には追加の 𝐾𝐻×𝐾𝑊 倍のメモリが使用されます。畳み込みカーネルのサイズが 1×1 の場合、入力を並べ替える必要がないため、GEMM は元の入力に対して直接操作でき、追加のメモリを使用する必要がありません。

メモリレイアウトと畳み込みパフォーマンス

ニューラル ネットワークの畳み込みには、NCHW と NHWC という 2 つの主なメモリ レイアウトがあります。最後に、im2col 1×1 畳み込みパフォーマンスとメモリレイアウトの関係を分析することに焦点を当てます。

追加の入力調整を必要としない 1×1 畳み込みの場合、NCHW メモリ レイアウトの畳み込みは行列乗算 𝐶=𝐴𝐵 に対応します。ここで、𝐴 は畳み込みカーネル (フィルター)、𝐵 は入力 (入力) です。各マトリックスの次元を図 12 に示します。

図12: 畳み込みを行列乗算に変換したNCHWメモリレイアウト

マトリックスを分割した後、コンピューティング コアのメモリ アクセスの局所性が図 12 にマークされます。 「Inside」は小ブロック行列乗算内の局所性を表し、「Outside」は次元削減方向の局所性を表します。

出力の場合、小さなブロック内の量的なメモリ アクセスの局所性は低く、外部パフォーマンスはグローバル計算方向に依存します。行の優先順位は局所性が高く、列の優先順位は局所性が低くなります。畳み込みカーネルはメモリを事前に再配置できるため、局所性が優れています。削減次元が列優先であり、入力のロードのほぼすべてでキャッシュ ミスが発生するため、入力は小さなブロックの内側と外側の両方で悪化します。

図13は、対照的にNHWCのメモリレイアウトの例です。 NHWC と NCHW の 𝐴 行列と 𝐵 行列によって表されるテンソルが入れ替わっていることに注目する価値があります — 𝑂𝑢𝑡𝑝𝑢𝑡=𝐼𝑛𝑝𝑢𝑡×𝐹𝑖𝑙𝑡𝑒𝑟。具体的な分割方法は同じままです。

図13: NHWCメモリレイアウト畳み込みを行列乗算に変換

NHWC では、出力の局所性は NCHW と同じように動作します。同様に、畳み込みカーネルもローカルではパフォーマンスが向上すると考えられています。入力の場合、複数のベクトル ロードのアドレスが連続していないため、小さな正方形の内部局所性はあまり良くありません。一方、縮小された次元でのスライドに使用されるメモリが連続しているため、外部局所性はより良くなります。これについては、「メモリ レイアウトの処理」セクションで説明しました。

1×1の場合、計算にim2col法を使用し、入力と出力に追加のメモリ再配置を行わない場合、NHWCのメモリアクセス特性はNCHWよりも大幅に優れていることがわかります。

要約する

これまで、本稿では、GEMM最適化の基本的な手法の概念、ニューラルネットワーク分野におけるQNNPACKによる量子化に基づくGEMMの最適化、畳み込み計算におけるim2col法の重要性とそのメモリレイアウトについて紹介しました。 GEMM 最適化は、実際には特定の分野に密接に関連する非常に重要なトピックです。さらに研究を進めるには、特定の分野の詳細な研究が必要です。さまざまな GEMM 最適化手法によってもたらされるパフォーマンスの向上に興味がある場合は、「gemm を最適化する方法」を参照してください。 GEMM の最適化とアーキテクチャの組み合わせの理論に興味がある場合は、「高性能行列乗算の構造」を参照してください。

<<:  K-means クラスタリングがあるのに、なぜ DBSCAN クラスタリング アルゴリズムが必要なのでしょうか?

>>:  スポーツイベントではロボットが人間に取って代わるのでしょうか?

ブログ    
ブログ    
ブログ    
ブログ    
ブログ    

推薦する

IBMの調査によると、AIのフィッシング能力は人間と同等であることが判明

この研究は、フィッシングメールの作成において AI と熟練した人間のエンジニアを対決させるという中核...

ハードコア! CES 2021 アワード: 労働者は仮想人間、口紅は AI アルゴリズムを追加、ロボットは毛皮で覆われる

この記事はLeiphone.comから転載したものです。転載する場合は、Leiphone.com公式...

インテリジェント車両認識技術がスマートシティへの道を開く

著者:Chris Kadoch 氏は Rekor Systems の最高技術責任者です。 [[376...

「機械」が他より賢く、「技術」が他より劣っているという観察と思考

[[277716]] 9月21日、CCTV-1の「スーパースマート」番組では、杭州の霊隠寺に毎日訪れ...

...

...

MyScale を使用して知識ベースを OpenAI の GPT にインポートする方法

翻訳者 |李睿レビュー | Chonglou 2023年11月6日、OpenAIはChatGPTをリ...

MetaMath: 逆思考で大規模モデルをトレーニングする新しい数学的推論言語モデル

複雑な数学的推論は、大規模言語モデルの推論能力を評価するための重要な指標です。現在、一般的に使用され...

一緒にハイキングに行きませんか? Baidu Brain EasyDLは、企業向けAI実装の山を登るお手伝いをします

エンタープライズ AI モデルの開発では、データの準備からモデルのトレーニング、サービスの展開まで、...

人工知能から新素材まで、IBMが5つの革新的な技術を発表

[原文は51CTO.comより] 2019年のIBM中国フォーラム(シンクサミット)で、IBMは各分...

マルチラベル分類とは何ですか?ここにいくつかの実用的な古典的な方法があります

これはおそらく、マルチラベル分類のための最も実用的なヒントです。ご存知のとおり、バイナリ分類タスクは...

Facebookは機械学習を使ってコンパイラを最適化

Facebook は最近、コンパイラ最適化タスクを実行するための高性能で使いやすい強化学習 (RL)...

各自動車会社の「地図なし」インテリジェント運転ソリューションについてお話ししましょう

01 起源産業発展のニーズ2022年下半期には、高速道路や都市高速道路でのインテリジェント運転の問題...