DUICUO

オープンソースのパワーハウスである MegPeak は、プロセッサをより深く理解するのに役立ちます。

コンピューティングパワー需要の爆発的な増加を背景に、既存ハードウェアのコンピューティングパワーを最大限に活用することが不可欠になっています。簡単に言えば、既存のアルゴリズムを特定のプロセッサ向けに極限まで最適化し、現在のAIアルゴリズムの高いコンピューティングパワー要件を可能な限り満たす必要があります。

究極のパフォーマンス最適化を実現するには、次のアプローチが可能です。

  • 精度を維持しながらメモリアクセスと計算を最小限に抑えるようにアルゴリズムを最適化します。
  • これらのアルゴリズムを実装するプログラムがプロセッサのパフォーマンスを最大化できるようにプログラムを最適化します。

プログラムを最適化するプロセスで解決すべき最初の問題は、プログラムがプロセッサの計算能力をどれだけ利用しているかを評価する方法と、さらにどのような最適化の余地と方向性があるかということです。

プロセッサをより深く理解するために、MegEngine チームは MegPeak と呼ばれるプロセッサ デバッグ ツールを開発しました。

このツールは主に、開発者のパフォーマンス評価と開発ガイダンスを支援するために使用されます。プロジェクトコードは現在完全にオープンソース化されており、ご興味のある方はご覧いただけます。

GitHub: https://github.com/MegEngine/MegPeak

このツールの機能とプロジェクトの技術的な実装の詳細を簡単に見てみましょう。

MegPeakの紹介

MegPeak を使用すると、ユーザーはターゲット プロセッサをテストできます。

  • 命令のピーク帯域幅
  • 命令遅延
  • メモリピーク帯域幅
  • 任意の命令の組み合わせのピーク帯域幅

上記の情報の一部は、チップのデータシートを参照し、理論計算と組み合わせることで取得できますが、対象プロセッサの詳細なパフォーマンスドキュメントを入手することは多くの場合不可能です。さらに、MegPeakを使用した測定はより直接的かつ正確であり、特定の命令の組み合わせにおけるピーク帯域幅をテストできます。

MegPeakの使い方

使用方法については、MegPeak の Readme ドキュメントを参照してください。

https://github.com/MegEngine/MegPeak#build

MegPeakの使用例

ArmV8 の汎用命令のピークパフォーマンスとレイテンシをテストするには、コンパイル後にターゲットプロセッサで `megpeak` を実行して、次の結果を取得します。

上図に示すように、MegPeakはCPU上の各命令のピーク計算時間とレイテンシを正確にテストできます。OpenCLでは、異なるデータ型のローカルメモリとグローバルメモリへのアクセス帯域幅、および異なるint/floatデータ型のピーク計算時間をテストします。

これらの値は、プログラムの現在のパフォーマンスを評価し、ルーフラインをプロットする際に効果的に役立ちます。これにより、メモリアクセスや計算など、プログラムをブロックする主な要因を診断するのに役立ちます。具体的な分析方法については後ほど紹介します。

MegPeakの原理

MegPeak テストの主なパラメータは次のとおりです。

  • さまざまな CPU 命令、命令のレイテンシ、およびメモリ帯域幅のピーク計算時間。
  • OpenCL のさまざまなメモリ タイプでのデータ アクセス帯域幅と、さまざまなデータ タイプでのピーク計算時間。

MegPeak がこれらのパフォーマンス データをテストし、データシートのデータと可能な限り一致するようにする方法を理解するには、読者は次の CPU パイプライン関連の詳細を理解する必要があります。

プロセッサパイプライン

命令スループットを向上させるため、現代のプロセッサは命令パイプラインを導入しています。命令パイプラインは、命令の実行プロセスを複数のステージに分割できます。典型的な5ステージパイプラインは、命令フェッチ、命令変換、命令実行、レジスタアクセス、データライトバックで構成されます。

これらの5つのステージは、プロセッサ内の物理ユニットによって独立して実行されます。したがって、理想的には、各ステージの各物理ユニットは、対応する演算を1クロックサイクルごとに1回実行でき、パイプラインを形成します。これにより、プロセッサは1クロックサイクルごとに1つの命令の実行を完了できます。

下の表に示すように、5 番目のクロック サイクルから、クロック サイクルごとに 1 つの命令が実行されます。

しかし、実際の実行においては、パイプラインがいつまでもスムーズに動作し続けることはできず、パイプラインが詰まるなど、以下のようなリスクが発生する可能性があります。

  • 構造的ハザードは、ハードウェアがすべての可能な命令の組み合わせを同時にサポートできない場合に発生し、リソースの競合と構造的ハザードにつながります。
  • データ ハザード – パイプライン命令には連続した順序があります。命令が前の命令の結果に依存する場合、データ ハザードが発生する可能性があります。
  • 制御ハザード - 分岐命令やプログラム カウンターを変更するその他の命令がパイプされると、制御ハザードが発生する可能性があります。

MegPeakは、命令間のデータハザードを制御し、構造的および制御的ハザードを最小限に抑えることで、プロセッサ命令のピーク計算とレイテンシを測定します。MegPeakではプロセッサのデータハザードを制御するためのコードを記述する必要があるため、MegPeakのテストにおけるコアコードはアセンブリ言語で実装されており、コンパイラの最適化による干渉を排除しています。

テストコマンドピーク

プロセッサ命令の計算ピークを測定するには、その命令を繰り返し実行しながらもリスクを一切負わないコードを書く必要があります。そのため、データリスクと制御リスクの両方を制御するコードが必要になります。

  • データハザードの排除 – 繰り返される命令間のデータ依存性を排除し、先行する命令と後続の命令間にデータ依存性がないようにします。WAWとWRAは真のデータ依存性ではなく、プロセッサはレジスタリネーミングを使用してこれらを解決する可能性がありますが、それでもこのようなデータ依存性の記述は避けるべきです。
  • リードアフターライト(RAW):前の命令でデータを書き込み、次の命令でそのデータを読み取ります。この場合、次の命令は前の命令の実行が完了するまで待機する必要があります。
  • ライトアフターライト (WAW): 2 つの命令が同じレジスタにデータを次々に書き込みます。データが書き込まれる順序は非常に重要です。
  • リードアフターライト(WRA):前の命令でレジスタからデータを読み取り、次の命令でそのレジスタに新しいデータを書き込みます。これらの操作の順序も重要です。
  • 制御ハザードを可能な限り排除します。同じ命令を繰り返し実行するためにループを使用することは可能ですが、ループには分岐が含まれるため、制御ハザードが発生する可能性があります。そのため、ループを可能な限り拡張し、1つのループ内でより多くのデータ非依存命令を実行する必要があります。ただし、この数はプロセッサのレジスタ数によって制限されます。

以下は、Arm64 上で fmla 命令のピーク値計算をテストする MegPeak のコア コードです。

静的int fmla_throughput() {
アセンブリ揮発性(
「eor v0.16b、v0.16b、v0.16b\n」
「eor v1.16b、v1.16b、v1.16b\n」
...
「eor v19.16b、v19.16b、v19.16b\n」
"mov x0, #0\n"
「1:\n」
「fmla v0.4s、v0.4s、v0.4s\n」
「fmla v1.4s、v1.4s、v1.4s\n」
...
"fmla v19.4s、v19.4s、v19.4s\n"
"x0、x0、#1 を追加 \n"
"cmp x0, %x[実行] \n"
「blt 1b \n」
:
: [RUNS] "r" (megpeak::RUNS)
: "cc""v0""v1""v2""v3""v4""v5""v6""v7""v8""v9""v10""v11""v12""v13"
"v14""v15""v16""v17""v18""v19""x0" );
megpeak::RUNS * 20を返します。
}

上記のインライン アセンブリ コードは主に次の処理を実行します。

  • ネオンレジスタ0~19をゼロに初期化します。この手順は必須ではありませんが、計算プロセス中にNaN値が発生する可能性を回避できます。
  • メインループを作成します。メインループ内の各命令が実行され、対応するレジスタからデータが読み出され、fmla命令が実行されて計算結果が同じレジスタに書き込まれます。同じ命令内ではデータ依存関係はありません。

ここで説明が必要な問題があります。なぜ 20 個のレジスタを選択するのでしょうか?

  • 選択されるレジスタが少なすぎると、次のループが同じレジスタを読み取る前に前のループの計算が終了していない可能性があり、データ依存関係が発生する可能性があります。したがって、ループで実行される命令の数は、命令レイテンシとプロセッサが1サイクルで実行できる命令数の積よりも大きくする必要があります。ただし、命令レイテンシは正確にはわかりませんが、推定することは可能です。特殊な命令を除き、レイテンシは通常10クロックサイクル以下です。
  • Arm64には32個のネオンレジスタがあります。なぜ32個のレジスタを選ばないのでしょうか?データと制御の依存関係を回避するには、20個のレジスタで十分だからです。テストの結果、レジスタの数を増やしても影響はほとんどないことが分かっています。

上記のコードを実行することで、実行時間を測定できます。また、命令数とループ回数から実際の計算負荷を事前に計算できるため、命令のピーク時の計算負荷を算出することもできます。

テストコマンドの遅延

プロセッサ命令の実行レイテンシを測定するには、繰り返し実行される命令を記述し、これらの命令間に厳密なデータ ハザード ルールがあることを確認しながら、他のハザードを最小限に抑える必要があります。

  • データ アドベンチャーを作成するには、連続する 2 つの命令間に真のデータ依存性 (RAW) があることを確認する必要があります。つまり、前の命令の出力が次の命令の入力になるということです。
  • 制御リスクを可能な限り排除する - 上記と同じ。

以下は、Arm64 上の fmla 命令のレイテンシをテストする MegPeak のコア コードです。

静的int fmla_latency() {
アセンブリ揮発性(
「eor v0.16b、v0.16b、v0.16b\n」
"mov x0, #0\n"
「1:\n」
「fmla v0.4s、v0.4s、v0.4s\n」
// 20回繰り返す
...
「fmla v0.4s、v0.4s、v0.4s\n」
"x0、x0、#1 を追加 \n"
"cmp x0, %x[実行] \n"
「blt 1b \n」
:
: [RUNS] "r" (megpeak::RUNS)
: "cc""v0""x0"
);
megpeak::RUNS * 20を返します。
}

上記のインラインアセンブリコードには主に以下が含まれます

 fmla v0.4s、v0.4s、v0.4s\n

この命令は 20 回繰り返されるため、各命令は前の命令の計算結果に依存し、厳密なデータ依存性が生じます。

コードを実行し、実行時間を追跡することで、実行された命令の数に基づいて各命令の最終的なレイテンシを計算できます。

上記のコードは MegPeak で実装されていますが、それほど直接的ではなく、代わりにマクロを使用してコードを生成します。

MegPeakで測定したデータは何に利用できますか?

MegPeak は、プロセッサのメモリ帯域幅、理論上のピーク命令計算、命令のレイテンシなどの情報をテストできるため、次のことが可能になります。

  • ルーフライン モデルを描画すると、モデルのパフォーマンスを最適化できるようになります。
  • 評価プロセスの最適化の可能性
  • 命令の組み合わせの理論的なピーク値の探究

さらに、MegPeakは理論検証も提供します。例えば、プロセッサ周波数、1コアあたり1サイクルあたりの命令発行数、実行命令あたりの計算量を用いて理論上のピーク演算を算出し、MegPeakを用いて実測することで検証することができます。

図面指示に関連するルーフラインモデル

ルーフラインモデルは高性能コンピューティングで広く利用されており、アルゴリズムの最適化可能性と最適化方向を評価するための重要なツールです。MegPeakを使用すると、様々な命令に対してより詳細なルーフラインモデルを描画できます。例えば、CPUでは、異なるデータ型のメモリアクセス帯域幅はほぼ同じでも、ピーク時の計算コストは​​大きく異なる場合があります。例えば、ARMプロセッサにおけるfloat型のピーク時の計算コストは​​、int8型のそれと大きく異なります。

コード最適化の可能性を評価する

特定のアルゴリズムを最適化する際に、MegPeak を使用することで、カーネル内の主要命令の最大ピーク値をテストできます。例えば、Arm 上の fp32 Matmul を最適化する場合、主に使用される命令は fmla 命令です。このとき、実際のプログラム実行時のピーク値をテストできます。命令のピーク値とプログラムのピーク値の差が小さいほど、コードの最適化が優れているといえます。

さらに、アルゴリズムの実装に基づいて計算コストとメモリアクセスコストを計算し、MegPeakを使用して上記のルーフラインをプロットすることができます。実際の計算密度を計算し、それをルーフラインにマッピングすることで、計算密度が図の緑色の領域に収まる場合、プログラムはメモリアクセスのさらなる最適化を必要としており、ブロック分割やデータの事前パック化など、より適切なメモリアクセスモデルを提供する必要があることが示されます。計算密度が灰色の領域に収まる場合、コードは既に最適化されていることを意味します。さらなる速度向上が必要な場合は、畳み込みにFFTやWinogradアルゴリズムを使用するなど、アルゴリズムの観点からの最適化が必要です。

最適な命令の組み合わせを探る

多くのカーネル最適化は単一の命令では測定できず、カーネル全体の計算を表現するために複数の命令の組み合わせが必要になる場合があります。そのため、最適なプロセッサ性能を実現するために、これらの命令をどのように構成するかを検討する必要があります。次の例は、A53スモールコアにおけるfp32 Matmulの最適化を示しています。Matmulは計算負荷の高い演算子であるため、メモリアクセス命令のオーバーヘッドを複数の問題に隠蔽することを検討します。MegPeakを用いて解析を行い、命令をどのように組み合わせれば可能な限り多くの問題を達成できるかを検討します。

小さなコアではリソースが限られているため、複数の命令を発行する際には多くの制限があります。

  • まず、MegPeak を使用して、A53 の fp32 プロセッサ上の fmla 命令のピーク計算パフォーマンスをテストし、それを 100% ピーク計算パフォーマンスとして定義します。
  • どの命令の組み合わせがデュアル発行をサポートできるかをテストする
  • ベクトルロードとfmlaを1:1で組み合わせたコードをMegPeakに追加し、テストします。ピーク値はfloatのピーク値のわずか36%であり、ベクトルロードとfmlaは二重発行できないことを示しています。
  • 同様に、汎用レジスタロード命令 ldr+fmla の組み合わせは float ピーク値の 93% に達することが測定されており、ldr と fmla が二重発行可能であることがわかります。
  • 上記のように、ins + fmla はデュアル発行を実行でき、ins + ベクトルロード 64 ビットもデュアル発行を実行できることが測定できます。
  • Matmul の最内カーネルの計算原理に基づくと、最内カーネルのブロック サイズが 8x12 の場合、最内カーネルは 20 個の float データ ポイントを読み取り、24 回の FLAX 計算を実行する必要があります。
  • 上記のMegPeakテストの情報に基づいて、20回の浮動小数点データロードと24回のフラグデータ計算を最小のクロックサイクルで完了する命令の組み合わせを見つける必要があります。したがって、データロードに費やされた時間を隠蔽するために、可能な限り多くのデータロードとフラグを二重発行する必要があります。
  • 最終的な命令の組み合わせは次のようになります。
  • ベクトルロード64命令、ldr、およびinsを組み合わせて、単一のネオンレジスタデータを形成します。ldrとinsはどちらもFMLAで二重発行できるため、FMLAと一緒に配置することでレイテンシを隠すことができます。
  • fmla 命令をこれらの 3 つの命令に織り交ぜ、可能な限りデータ依存関係を解決します。

上記の命令の組み合わせにより、Matmul は小さなコア上で計算ピークの約 70% に到達できます。

要約

MegPeakは、高性能コンピューティングツールとして、開発者がターゲットプロセッサの詳細な内部情報を容易に取得できるようにし、コードパフォーマンスの評価や最適化手法の設計を支援します。しかしながら、MegPeakにはさらなる開発が必要な領域もいくつかあります。

  • L1 キャッシュと L2 キャッシュのサイズなどのプロセッサ パフォーマンス データの取得、さまざまな命令の組み合わせに対するデュアル発行シナリオの自動探索、プロセッサ バックエンドの大まかなサムネイルの描画などをサポートします。(例: en.wikichip.org/w/image)
  • ワープ サイズやローカル メモリ サイズなど、モバイル デバイス上の OpenCL に関するより詳細な情報の測定をサポートします。

上記の機能に興味のある学生は、GitHub でコードを提出するか、MegPeak を使用してみてください。