記事ディレクトリ
1. 概要
高性能のテンソル プログラムは、ディープ ニューラル ネットワークを効率的に実行するための鍵となります。ただし、さまざまなハードウェア プラットフォーム上のさまざまな演算子に対して良好なパフォーマンスのテンソル プログラムを取得するのは非常に困難です。kernel libraries
現在、深層学習システムは、パフォーマンスの良いテンソル プログラムを取得するために、カーネル ライブラリ ( ) またはハードウェア ベンダーが提供するさまざまな検索戦略に依存しています。しかし、これらの方法には 2 つの欠点があります:
(1)
プラットフォーム固有に最適化されたコードを開発するには多大なエンジニアリング作業が必要であること、
(2)
限られた検索スペースと非効率な検索戦略により、高性能の tensor プログラムの発見が困難であることです。
上記の欠点に基づいて、著者はAnsor
深層学習アプリケーションのためのテンソル プログラム生成フレームワークを提案します。既存の検索戦略と比較して、検索空間の階層表現からプログラムをサンプリングすることでより最適な組み合わせを探索する、進化的検索と学習コストモデルを使用してサンプリングされたプログラムを微調整して最適なプログラムを決定する、を使用するAnsor
という特徴があります。ディープ ニューラル ネットワークの複数のサブグラフを同時に最適化するタスク スケジューラ。 著者らの実験は、Ansor が既存の最先端の手法の検索空間の外で高性能プログラムを見つけることができることを示しています (はい、回、回、回) 。
(1)
(hierarchical representation)
(optimization combinations)
(2)
(evolutionary search)
(cost model)
(fine-tune)
(3)
(task scheduler)
(state-of-the-art,SOAT)
Intel CPU
3.8
ARM CPU
2.6
NVIDIA GPU
1.7
2. はじめに
ディープ ニューラル ネットワーク(DNN)
の低遅延実行は、自動運転(autonomous driving)
、拡張現実(augmented reality)
、言語翻訳(language translation)
、その他のアプリケーションAI
において重要な役割を果たします。DNN は有向非巡回コンピューティング グラフ として表現でき(directed acyclic graph, DAG)
、ノードは演算子 (畳み込み、行列乗算) を表し、有向エッジは演算子間の依存関係を表します。既存の深層学習フレームワークは、(Tensorflow, PyTorch, MXNet)
DNN の演算子をベンダー提供のカーネル ライブラリにマッピングして、(cuDNN, MKL-DNN)
高いパフォーマンスを実現します。ただし、これらのカーネル ライブラリは、ハードウェア プラットフォームやオペレーターごとに手動で調整するために多大なエンジニアリング作業を必要とし、ターゲット アクセラレータごとに効率的なオペレーター実装を作成するために必要な大量の手動作業により、新しいオペレーターや特定のアクセラレータの開発とイノベーションが制限されます。 。
パフォーマンスの重要性を考慮して、研究者や業界関係者は、テンソル演算子の低レベル実装など、コンパイラ検索DNN
ベースのテンソル プログラムの自動生成に注目しています。(search-based compilation)
演算子または複数の演算子のサブグラフの場合、ユーザーは高級宣言言語で計算を定義する必要があり、その後、コンパイラーがさまざまなハードウェア プラットフォーム用のカスタム プログラムを検索します。
3. 背景
CPU
ディープ ラーニング エコシステムは、 、GPU
、FPGA
を 含む、急速に多様化するハードウェア プラットフォームを受け入れていますASIC
。これらのプラットフォームにデプロイするには、 で使用される演算子に高性能のテンソル プログラムを提供するDNN
必要があり、必要な演算子のセットには通常、標準の演算子と機械学習の研究者によって発明された新しい演算子が含まれます。 これらの演算子を広範囲のハードウェア プラットフォームに効率的に移植できるようにするために、さまざまなコンパイラ技術が登場しました。ユーザーは高レベルの宣言言語を使用して数式に似た形式で計算を定義し、コンパイラーはその定義に基づいて最適化されたテンソル プログラムを生成します。以下の図はテンソル表現言語における行列乗算の計算定義を示しており、ユーザーは主に入力テンソルの形状と出力テンソルの各要素の計算方法を定義する必要があります。DNN
(matmul, conv2d)
(capsule conv2d, dilated conv2d)
(TVM, Halide, Tensor Comprehensions)
TVM
しかし、高レベルの定義から高性能の tensor プログラムを自動生成することは非常に困難です。ターゲット プラットフォームのアーキテクチャに応じて、コンパイラは、最適化の組み合わせの選択肢 (アンロール構造、アンロール サイズ、ベクトル化、並列化など) で構成される非常に大きく複雑な空間を検索する必要があり、高性能プログラムを見つけるには検索戦略が必要(tile structure)
です。包括的な空間を効率的に探索できます。(tile size)
(vectorization)
(parallelization)
4. 設計の概要
Program sampler
:Ansor
対処しなければならない重要な課題は、特定の計算グラフに対して大規模な検索スペースを生成することです。さまざまな高レベルの構造と低レベルの詳細を備えたさまざまなテンソル プログラムをカバーするために、スケッチとアノテーションのAnsor
2 つのレベルの検索スペースを持つ階層表現が利用されます。プログラムの高レベルの構造をスケッチとして定義し、数十億もの低レベルの選択肢 (例: タイル サイズ、並列処理、アンローリング アノテーション) をアノテーションとして定義するこの表記により、高レベルの構造の柔軟な列挙と低レベルの効率的なサンプリングが可能になります。詳細。: ランダム サンプリング プログラムのパフォーマンスは必ずしも良好ではありません。次の課題はプログラムを微調整することです。微調整は、進化的探索と学習されたコスト モデルを使用して反復的に実行されます。各反復では、リサンプリングされた新しいプログラムと、以前の反復からの良好なプログラムを初期母集団として使用して進化的探索が開始されます。進化的探索は、突然変異と交差を通じてプログラムを微調整し、アウトオブオーダーの書き換えを実行し、逐次構築の制約に対処します。学習されたコスト モデルのクエリは、実際に測定するよりも桁違いに速いため、数千のプログラムを数秒で評価できます。: プログラム サンプリングとパフォーマンス微調整を使用すると、Ansor は計算グラフ用の高性能 tensor プログラムを見つけることができます。直観的には、完全なものを単一の計算グラフとして処理し、その完全なテンソル プログラムを生成すると、潜在的に最高のパフォーマンスを達成できる可能性があります。ただし、検索スペースの不必要な指数関数的な爆発に対処する必要があるため、これは非効率的です。通常、コンパイラーは大きな計算グラフをいくつかの小さなサブグラフに分割しますが、レイヤーごとの構築機能により、この分割によるパフォーマンスへの影響はごくわずかであり、グラフ生成プログラム時に時間リソースをどのように割り当てるかという最後の課題が生じます。 ? のタスク スケジューラは、勾配降下ベースのスケジューリング アルゴリズムを使用して、エンドツーエンドの DNN パフォーマンスを向上させる可能性が高いサブグラフにリソースを割り当てます。(sketch)
(annotation)
Ansor
(tile size)
(parallel)
(unroll annotations)
Ansor
Performance tuner
Ansor
Ansor
Task scheduler
DNN
DNN
DNN
(layer-by-layer)
Ansor
Ansor
5. プログラムサンプリング
アルゴリズムが探索する検索スペースによって、見つけられる最適なプログラムが決まります。既存の方法で考慮される検索スペースは、次の要因によって制限されます:
(1)
手動列挙(TVM)
: テンプレートを使用してすべての可能な選択肢を手動で列挙するのは現実的ではないため、既存の手動テンプレートは限られた検索スペースしかヒューリスティックにカバーできません; 積極的な早期プルーニング : 積極的な早期
(2)
プルーニング(Halide auto-scheduler)
ベース不完全なプログラムを評価すると、検索アルゴリズムが空間内の特定の領域を探索できなくなります。
を解決するには(1)
、柔軟な導出ルールのセットを再帰的に適用することで検索空間を自動的に拡張します。
それを避けるために(2)
、検索空間内の完全なプログラムをランダムにサンプリングします。
ランダムサンプリングは各サンプリングポイントに等しいチャンスを与えるため、著者が提案した検索アルゴリズムは、最適なプログラムを見つけるためにランダムサンプリングに依存せずに、考慮された空間内のすべてのプログラムを探索できる可能性があります。これは、サンプリングされた各プログラムが後ですべて微調整されるためです。
最上位レベルでは、いくつかの導出ルールを再帰的に適用することによってスケッチが生成されます。最下位レベルでは、完全なプログラムを取得するために、これらのスケッチにランダムに注釈が付けられます。この表現は、何十億もの低レベルの選択肢からいくつかの基本構造を要約し、高レベルの構造の柔軟な列挙と低レベルの詳細の効率的なサンプリングを可能にします。
5.1 スケッチの生成
上図の最初の列は 2 つの入力例を示しています。入力には 3 つの同等の形式があります。それは、数式、naive
ループ インデックスの直接展開から得られる対応するプログラム、および対応する計算グラフです(DAG)
。
コンピューター プログラミングの分野では、
"naive program"
通常、プログラムを実装する単純または単純な方法を指します。このような手順では、考えられるすべてのケースが考慮されていない場合や、既存の最適化手法が活用されていない場合があります。"naive"
この用語は、経験が浅い、またはコードを書くスキルが低い特定のプログラマを指すのによく使用されます。このような場合、プログラマは、より複雑または効率的な解決策を考慮せずに、何らかの基本的なアルゴリズムまたはデータ構造を使用する可能性があります。このようなプログラムは通常、多くのコンピューティング リソースを消費し、実行が遅くなります。------by ChatGPT
複数のノードを含むスケッチを生成するにはDAG
、トポロジ順にすべてのノードを参照し、反復的に構造を構築します。計算量が多く、データを再利用する機会が多い計算ノードの場合は(conv2d, matmul)
、基本的なタイリング構造と融合構造をスケッチとして構築します。また、単純な要素ノードの場合は(ReLU, elementwise add)
、安全にインライン化できます。新しいノード(キャッシュ ノード(caching nodes)
、レイアウト変換ノード(layout transform nodes)
)もスケッチ生成中に導入できることに注意してくださいDAG
。著者らは、いくつかの基本ルールを再帰的に適用することで、考えられるすべてのスケッチを生成する
導出ベースの列挙法を提案しています。(derivation-based enumeration)
このプロシージャはDAG
入力として受け取り、スケッチのリストを返します。σ = ( S ; i ) \sigma = (S;i) と定義します。State
p=( S ;i),其中 S S SDAG
は現在のパーツによって生成されたスケッチです。iiiは現在動作しているノードのインデックスであり、DAG 内のノードは出力から入力のトポロジ順に並べ替えられます。導出は、初期naive
プログラムと最後のノード、または初期状態から始まります σ = ( naive プログラム ; 最後のノードのインデックス ) \sigma = (naive\ プログラム ;\ the\ last\ ノードのインデックス\)p=(ナイーブプログラム; _ _ _ _ _ _ 最後のノードのd e x )では、すべての導出ルールをこれらの状態に再帰的に適用しよう とします。各ルールについて、現在の状態が適用条件を満たしている場合、このルールを適用しますσ = ( S ; i ) \sigma = (S;i)p=( S ;i )得られるσ ' = ( S ' ; i ' ) , i ' < i \sigma \prime= (S\prime;i\prime),\ i\prime < i' _=( S ' ;私は)、 私は<i、そのようなインデックスiii (ワーカーノード)i = 0 i = 0私=0の場合、状態は終了状態になります。列挙中に、複数のルールを 1 つの状態に適用して、複数の後続の状態を生成できます。また、1 つのルールで複数の可能な後続の状態を生成することもできます。したがって、すべての中間状態を保存するキューを維持し、キューが空になるとプロセスは終了します。すべてのσ . S \sigma .S は終端状態にありますσ . S は、スケッチ生成の最後にスケッチ リストを形成します。一般的なサブグラフの場合、スケッチの数は よりも少なくなります10
。
// 递归应用几个基本规则来生成所有可能的sketch
// Derivation rule based enumeration
Array<State> out_states;
while (!pnow->empty()) {
pnext->clear();
for (const State& state : *pnow) {
int stage_id = cur_stage_id_map[state];
// Reaches to the terminal stage
if (stage_id < 0) {
out_states.push_back(state);
continue;
}
// Try all derivation rules
for (const auto& rule : sketch_rules) {
auto cond = rule->MeetCondition(*this, state, stage_id);
if (cond != SketchGenerationRule::ConditionKind::kSkip) {
for (const auto& pair : rule->Apply(*this, state, stage_id)) {
cur_stage_id_map[pair.first] = pair.second;
pnext->push_back(pair.first);
}
// Skip the rest rules
if (cond == SketchGenerationRule::ConditionKind::kApplyAndSkipRest) {
break;
}
}
}
}
std::swap(pnow, pnext);
}
// Conv2d(3, 64, kernel_size=(7, 7), stride=2, padding=1)有3个sketch生成
Derivation rules
: 上の表は、CPU
の導出ルールを示しています。著者らは、まず使用される述語の定義を提供し、次に各ルールの機能を説明し、次に計算定義に対して静的分析を実行してこれらの述語の値を取得します。分析は、次の読み取り/書き込みパターンを解析することによって自動的に行われます。数式。上の表を整理してみました。
Condition |
Description |
---|---|
IsStrict Inliable ( S , i ) IsStrictInliable(S,i)厳格に制限されています( S 、_ _ _ _ _私) | SSを意味しますSのiii は、およびなど(element-wise) の単純な要素ごとの演算子ですelement-wise add ReLU |
H as Data Reuse ( S , i ) HasDataReuse(S,i)HA sDATA再利用( S 、_ _ _ _ _ _私) | SSを意味しますSのiiiは計算集約型の(compute-intensive) オペレーターであり、オペレーター内でデータを再利用する機会が多数ありますmatmul 。conv2d |
H as F usible Consumer ( S , i ) HasFusibleConsumer(S, i)可使性のある消費者( S 、_ _ _ _ _ _ _ _ _私) | SSを意味しますSのiiコンシューマ ノードjjが1 つだけありますj、ノードjjj はノードiiに融合できます私、 やmatmul + bias_add などconv2d + relu |
H as More Reduction Parallel ( S , i ) HasMoreReductionParallel(S, i)より多くの削減を実現( S 、_ _ _ _ _ _ _ _ _ _ _ _ _ _ _私) | SSを意味しますSのiii は空間次元での並列性がほとんどありませんが、L2 行列のノルムの計算、モーメントC 2 × 2 = A 2 × 512 ⋅ B 512 × 2 C_{2\times2} の乗算など、次元=A_{2\times512} \cdot B_{512\times2}C2 × 2=あ2 × 512⋅B512 × 2 |
コンピューター プログラミングでは、
"inline"
通常、コードのコンパイル時に関数呼び出しを関数本体内のコードに直接置き換えるコンパイラ最適化手法を指します。これにより、関数呼び出し時の余分なオーバーヘッドが回避され、コードの実行効率が向上します。
ではC++
、キーワードを使用して、関数を関数として扱う"inline"
ようにコンパイラに指示できます。プログラム内で関数を使用するメリットは、関数呼び出しのオーバーヘッドが軽減され、プログラムの動作効率が向上することですinline
。さらに、関数を使用すると、関数を呼び出すたびに呼び出しサイトに関数のコードが埋め込まれるため、コードの重複が減ります。関数を使用するとプログラムのパフォーマンスを向上させることができます が、すべての関数が関数として適しているわけではないことに注意してください。一般に、小さくて頻繁に呼び出される関数は関数として最適ですが、大きくて複雑な関数は適していません。さらに、関数によってコードのサイズが増加する可能性があるため、コードのサイズとパフォーマンスの間にはトレードオフが存在します。C++
inline
inline
inline
inline
inline
inline
inline
------by ChatGPT
Rule 1
ノードが厳密にインラインでない場合は単純にノードをスキップします。との条件は相互に排他的である
Rule 2
ため、常に厳密にインライン ノードです。 i > 1 i > 1Rule1
Rule2
私>状態1
Rule 3
は、データ再利用可能なノードに対してマルチレベル タイリングを実行するという条件の 1 つを常に満たして導出を続行できますの場合タイル構造をCPU
使用しますタイル レベルの空間サイクルが表され、タイル レベルの縮小サイクルが表されます。たとえば、モーメント乗算ではC ( i , j ) = ∑ k A [ i , k ] × B [ k , j ] C(i,j) = \sum_k A[i,k] \times B[k,j] 】"SSRSRS"
"S"
(space loop)
"R"
(reduction loop)
C (私、j )=∑kA [私、k ]×B [ k ,j ]、ii私とJJjはスペースリング、kkkは減速リングです。モーメント乗算の"SSRSRS"
元の3
レベル( i , j , k ) (i,j,k) を(私、j 、k )10
はレベル ループ(i 0 , j 0 , i 1 , j 1 , k 0 , i 2 , j 2 , k 1 , i 3 , j 3 ) (i_0,j_0,i_1,j_1,k_0,i_2) に展開されます。、j_2、k_1、i_3、j_3)(私は0、j0、私1、j1、k0、私2、j2、k1、私3、j3)、ループ順序は乱れませんが、このマルチレベル タイリングはいくつかの並べ替えケースもカバーできます。たとえば、上記のレベル ループは、( k 0 , j 2 , j 3 ) (k_0,j_2,j_3)10
の単純な並べ替えに特化できます。( k0、j2、j3)他のループの長さを に設定することによって1
。"SSRSRS"
タイリング構造は、すべて と で構成されているため、通常、深層学習における計算量の多い演算子に使用されます。これは(matmul, conv2d, conv3d)
、マルチレベルのタイリングを実行するためのものであり、融合コンシューマーも組み込まれています。たとえば、要素ごとのノードをタイル ノードに融合でき。はい、現在のデータ再利用可能なノードに融合されたコンシューマがない場合は、キャッシュ ノードを追加します。たとえば、の最終出力ノードにはコンシューマがないため、デフォルトでは結果がメイン メモリに直接書き込まれますが、メモリ アクセスの待ち時間が長いため非効率的です。キャッシュ ノードを追加することで、、この新しく追加されたキャッシュ ノードを最終出力ノードに融合するために適用できますキャッシュ ノードの融合により、最終出力ノードはその結果をキャッシュ ブロックに書き込み、ブロック内のすべてのデータが計算されるとすぐにメイン メモリに書き込まれます。並列処理、可視性、および マルチ処理に分解できます。-レベルのタイリングとデータの再利用によるノードの融合。space loop
reduction loop
Rule 4
(ReLU,bias_add)
(conv2d, matmul)
Rule 5
DAG
DAG
Rule 4
Rule 6
rfactor
reduction loop
space loop
Rule 3
Rule 4
Rule 5
の場合
GPU
、タイル構造を使用し"SSSRRSRS"
、最初の 3 つのスペース タイルのループはそれぞれBlockIdx
、仮想スレッド (競合virtual thread
を減らすためbank
) およびにバインドThreadIdx
され、2 種類のスケッチ導出ルールを追加します。1 つは、キャッシュ ノードを挿入することによるものです。共有メモリ ( と同様Rule 5
)、およびクロススレッド削減用のもう 1 つ ( と同様Rule 6
)。
このセクションの最初の図は、生成されたスケッチの 3 つの例を示しています。スケッチは、TVM
手動テンプレートでは高レベルの構造と低レベルの詳細の両方を指定するのに対し、スケッチは高レベルの構造のみを定義するという点で異なります。たとえばInput 1
、DAG
4 つのノードの並べ替え順序は( A 、 B 、 C 、 D ) (A、B、C、D)です。( A 、B 、C 、D )。スケッチを取得するにはDAG
、出力ノードD ( i = 4 ) D(i=4)D (私=4 )ルールを開始し、ノードに 1 つずつ適用します。具体的には、生成されるSketch 1
導出プロセスは次のとおりです。
右側
Input 1
はDAG
理解すべきノードです。A
およびB
は入力データ ノード、C
はmatmul
ノード、D
は出力ノード、A
およびB
ノーD
ド アプリケーションRule 1
、C
ノード アプリケーション ですRule 4
。
たとえばInput 2
、5 つのノードの並べ替え順序は( A 、 B 、 C 、 D 、 E ) (A、B、C、D、E)です。( A 、B 、C 、D 、E )。同様に、出力ノードE ( i = 5 ) E(i = 5)E (私=5 )最初に、ルールを再帰的に適用すると、結果のSketch 1
導出は次のようになります。
右側
Input 2
はDAG
理解するノードです。A
はD
入力データ ノード、B
はmax
ノード、C
は xxx ノード、E
はmatmul
ノード、 は出力ノードでもありA
、 、C
およびD
ノード アプリケーションRule 1
、ノードB
アプリケーションRule 2
、E
ノード アプリケーションRule 5
キャッシュ ノードを挿入します。をクリックして適用しますRule 4
。
同様に、結果として得られるSketch 3
派生プロシージャは次のようになります。
5.2 ランダムな注釈
前のサブセクションで生成されたスケッチは、特定のタイル サイズや(loop annotation)
並列処理、アンローリング、ベクトル化などのループ アノテーションを持たないタイル構造のみを備えているため、不完全なプログラムです。このサブセクションでは、スケッチに注釈を付けて、微調整と評価のための完全なプログラムを作成します。
生成されたスケッチのリストを基に、ランダムにスケッチを選択し、ランダムにタイル サイズを埋め、一部の外側ループを並列化し、一部の内側ループをベクトル化し、一部の内側ループを展開します。また、プログラム内の一部のノードの計算された位置をランダムに変更して、タイル構造をわずかに調整します。ここでのすべては、"随机"
すべての有効な値にわたる一様な分布を意味します。特殊なアルゴリズムがカスタム注釈を有効にする必要がある場合 (特殊なアンローリングなど)、ユーザーは計算定義に簡単なヒントを与えて注釈戦略を調整することができます。最後に、定数テンソルのレイアウトの変更は実行時のオーバーヘッドなしでコンパイル時に実行できるため、マルチレベル タイリングの観点から定数テンソルのレイアウトを書き直して、可能な限りキャッシュしやすいものにします。この最適化が機能するのは、畳み込み層または全結合層の重みテンソルが推論アプリケーションでは一定であるためです。
ランダム サンプリングの例は、このセクションの冒頭の図に示されていますが、長さのサイクルが単純化されているため、サンプリング プログラムのサイクルはスケッチよりも少ない場合があります1
。
loop annotation
これは、ループ本体に特定のタグを追加して、コンパイラにループ本体の性質と特性を伝え、コンパイラがループ本体の実行をより適切に最適化できるようにすることを指します。これらのタグは通常、コードのループ本体にコメントの形式で追加されます。
一般的なものは次loop annotation
のとおりです。
1.
unroll
: ループをアンロールします。つまり、ループ本体内のコードを複数回コピーして、ループ制御ステートメントのオーバーヘッドを削減します。
2.
vectorize
: ベクトル化されたループ、つまり、複数回実行される同じ操作を 1 つの操作に結合して、ループ本体の実行を高速化します。
3.
parallelize
: ループを並列化します。つまり、ループ本体の実行を高速化するために、ループ本体内の複数の反復を異なるプロセッサ コアまたはスレッドに割り当てて実行します。
4.
パイプライン: ループ本体の複数の反復を複数のステージに分割し、異なるプロセッサ コアまたはスレッドで同時に実行して、ループ本体の実行を高速化します。
使用する場合は、loop annotation
特定の状況に応じて適切なマークを選択し、ハードウェア デバイスの特性に応じて最適化する必要があります。loop annotation
ループ本体の実行効率は向上しますが、多すぎるloop annotation
とコードの可読性と保守性が低下する可能性があります。したがって、Lloop annotation
最適な最適化スキームを使用する場合は、トレードオフと評価を行って最適な最適化スキームを決定する必要があります。------by ChatGPT
6. パフォーマンスの微調整
プログラム サンプラーによってサンプリングされたプログラムは、検索スペースを十分にカバーしていますが、タイル構造やループ アノテーションなどの最適化オプションがランダムにサンプリングされるため、品質は保証されません。そこで著者らは、進化的探索とコスト モデルの学習を通じてサンプル プログラムのパフォーマンスを微調整するパフォーマンス チューナーを導入しました。
各反復では、まず進化的検索を使用して、学習したコスト モデルに従って有望なプログラムの小さなバッチを見つけます。次に、これらのプログラムをハードウェア上で測定して実際の実行時間コストを取得し、最後に測定結果のパフォーマンス データにより、コスト モデルがより正確になるように再トレーニングされます。
進化的探索では、以前に測定された高品質のプログラムを初期母集団としてランダムにサンプリングされたプログラムを使用し、突然変異と交叉を適用して次世代を生成します。学習されたコスト モデルは、各プログラムの適合性を予測するために使用されます(fitness)
。この場合、適合性はプログラムのスループットです。一定回数の進化を実行し、検索中に見つかった最適なプログラムを選択します。コスト モデルは、実際の測定よりも桁違いに速く、相対的な精度でプログラムの適合性を推定できるため、学習済みコスト モデルを利用します。これにより、サーチスペース内の数万のプログラムを数秒で比較し、実際の測定に有望なプログラムを選択することができます。
6.1 進化的探索
Tile size mutation
: このアクションはプログラムをスキャンし、タイル サイクルをランダムに選択します。このタイリング ループでは、1 つのタイル レイヤーのタイル サイズをランダムな係数で除算し、この係数を他のタイル レイヤーに乗算します。この操作により、タイル サイズの積が元のループ長と等しくなるため、変更されたプログラムは常に動作します。
Parallel mutation
: このアクションはプログラムをスキャンし、parallel
注釈付きのループをランダムに選択します。このループの場合、隣接するループ レベルを融合するか係数で分割することにより、並列処理の粒度が変更されます。
Pragma mutation
: プログラム内の一部の最適化は、プログラムをスキャンしてランダムに 1 つを選択するコンパイラ固有の操作によってpragma
指定されますpragma
。この場合pragma
、op はそれを別の有効な値にランダムに変換します。たとえば、基になるコード ジェネレーターは、auto_unroll_max_step=N
pragma
数値をランダムに調整することにより、最大ステップ数の自動展開をサポートしますN
。
Computation location
: この操作はプログラムをスキャンし、非多層タイル化フレキシブル ノード (畳み込み層のパディング ノードなど) をランダムに選択します。このノードの場合、操作により計算された位置が別の有効な追加ポイントにランダムに変更されます。
Node-based crossover
: においてAnsor
、プログラムの遺伝子はプログラムの書き換えステップです。Ansor
生成された各プログラムは、最初の単純な実装から書き直され、スケッチ生成およびランダム アノテーション中にAnsor
各プログラムの完全な書き換え履歴が保存されます。書き換えステップは、このプログラムが最初の元のプログラムからどのように形成されたかを記述するため、プログラムの遺伝子と考えることができます。これに基づいて、2 つの既存のプログラムの書き換え手順を組み合わせて、新しいプログラムを生成できます。ただし、2 つのプログラムの書き換えステップを任意に組み合わせると、ステップ間の依存関係が壊れ、無効なプログラムが作成される可能性があります。したがって、異なるノードにわたる書き換えステップの依存性は通常低いため、 のAnsor
クロスオーバー操作の粒度は のノードに基づきます。各ノードの親をランダムに選択し、選択したノードの書き換えステップをマージします。ノード間に依存関係がある場合は、単純なヒューリスティックを使用してこれらのステップを分析および調整しようとします。マージされたプログラムをさらに検証して、機能が正しいことを確認します。少数のループ変換書き換えステップのみが使用され、基礎となるコード ジェネレーターが依存関係分析を通じて正確さをチェックできるため、検証は簡単です。DAG
Ansor
Ansor
Ansor
Ansor
進化的探索では、突然変異とクロスオーバーを使用して、複数のラウンドで新しい候補プログラムのセットを繰り返し生成し、ターゲット ハードウェア上でコンパイルおよび測定される最高スコアのプログラムのセットを出力して、現実的な実行時コストを取得します。コストモデルを更新するために使用されます。このようにして、学習されたコスト モデルの精度がターゲット ハードウェアに合わせて徐々に向上します。したがって、進化的探索により、ターゲットのハードウェア プラットフォーム向けに、より高品質なプログラムが徐々に生成されます。固定グリッド状パラメータ空間でのみ機能する および の
検索アルゴリズムTVM
とは異なり、の進化演算は、テンソル プログラム用に特別に設計されています。これらは一般的な tensor プログラムに適用でき、複雑な依存関係を持つ検索空間を処理できます。自動スケジューラのアンワインド ルールとは異なり、これらの操作はプログラムに対して順序外の変更を実行して、順序制約に対処することができます。FlexTensor
Ansor
Halide
6.2 学習されたコストモデル
私たちのターゲット プログラムは主に、最も内側のステートメントとして複数の代入ステートメントを持つ複数のインターリーブ ループ ネストで構成されるデータ並列テンソル プログラムであるため、コスト モデルをトレーニングして、ループ ネスト内の最も内側の非ループ ステートメントのスコアを予測します。完全なプログラムでは、最も内側の非循環ステートメントごとに予測を行い、予測をスコアとして合計します。完全なプログラムのコンテキストで特徴を抽出することによって、最も内側の非循環ステートメントの特徴ベクトルを構築します。抽出された特徴には、算術特徴とメモリ アクセス特徴が含まれます。機能の紹介については、他のサブセクションを参照してください。
損失関数として重み付き二乗誤差を使用します。主に検索空間からパフォーマンスの良いプログラムを特定することに関心があるため、より高速に実行されるプログラムにより多くの重みを与えます。具体的には、 yyのスループットでyさんのプログラムPPP、モデルfffの損失関数はloss ( f , P , y ) = wp ( ∑ s ∈ S ( P ) f ( s ) − y ) 2 = y ( ∑ s ∈ S ( P ) f ( s ) − y ) 2 loss (f,P,y) = w_p \big( \sum_{s \in S(P)} f(s) - y \big)^2 \\[5pt] =y \big( \sum_{s \in S(P)} f(s) - y \big)^2損失( f 、_P 、y )=wp(s ∈ S ( P )∑f ( s )−y )2=や(s ∈ S ( P )∑f ( s )−y )2 ここでS ( P ) S(P)S ( P )はPPですPに設定された最も内側の非循環ステートメントはyy をyを重みとして使用します。基礎となるモデルとして
勾配ブースト決定木をトレーニングします。(XGBoost)
fDAG
、すべてのテンソル プログラムのモデルをトレーニングしDAG
すべてのプログラムのスループットを同じから の[0,1]
範囲まで正規化します。を最適化する場合DNN
、測定されるプログラムの数は通常 10,000 未満であり3
、このような小さなデータセットでのトレーニングはXGBoost
非常に高速であるため、増分更新を行うのではなく、毎回新しいモデルをトレーニングします。
7. タスクスケジューラ
DNN
は多くの独立したサブグラフ (例: ) に分割できconv2d + relu
、一部のサブグラフでは、サブグラフのチューニングに時間を費やしても、次の 2 つの理由により、エンドツーエンドのDNN
パフォーマンスが大幅に改善されません:
(1)
サブグラフはパフォーマンスのボトルネックではない、
(2)
チューニングは最小限の改善しかもたらしません。サブグラフのパフォーマンスに。
重要でないサブグラフの調整に時間を無駄にしないように、Ansor
異なる量の時間リソースが異なるサブグラフに動的に割り当てられます。たとえばResNet-50
、グラフを分割すると、29
固有のサブグラフができます。これらのサブグラフのほとんどは、さまざまな形状構成 ( input size
、など) を持つ畳み込み層です。最適なテンソル プログラムはこれらの形状構成に依存するため、畳み込み層ごとに異なるプログラムを生成する必要があります。実際、ユーザーのすべてのアプリケーションには複数の が存在する可能性があります。これにより、サブグラフが増え、全体のチューニング時間を短縮する機会が増えます。これは、サブグラフ間で知識を共有して再利用でき、サブグラフが 1 つまたは別のサブグラフで複数回出現する可能性があるためです。 サブグラフの高性能プログラムを生成するために実行されるプロセスとしてタスクを定義します。これは、単一のタスクを最適化するには数十のタスク (タスクなど) を完了する必要があることを意味します。タスク スケジューラは, 時間リソースをタスクに繰り返し割り当てます. 各繰り返しでは,タスクが選択され, サブグラフに対して有望なプログラムのバッチが生成され, プログラムはハードウェア上で測定されます. このような繰り返しを時間リソース単位として定義します. 。時間単位のリソースをタスクに割り当てると、そのタスクには新しいプログラムを生成して測定する機会が与えられます。これは、より良いプログラムを見つけるチャンスを意味します。kernel size
stride
DNN
DNN
DNN
DNN
ResNet-50
29
Ansor
Ansor
7.1 問題の定式化
1 つDNN
またはグループをチューニングする場合、ユーザーは、遅延の削減、グループの遅延要件を満たす、チューニングによってパフォーマンスが大幅に向上しなくなった場合のチューニング時間を最小限に抑えるDNN
など、さまざまな種類の目標を設定できます。したがって、私たちはユーザーに目標を表現するための目的関数のセットを提供します。また、ユーザーが独自の目的関数を提供することもできます。 合計nnがあるとします。DNN
DNN
DNN
n個のタスク、t ∈ Z nt \in \mathcal Z^nt∈Znは割り当てベクトルです。ここでti t_it私はタスクiiへの支出のためiの時間単位の数iiiによって得られる最小サブグラフ遅延は、gi ( t ) g_i(t)g私は( t )関数の場合、DNN
エンドツーエンドのコストを(cost)
部分グラフf ( g 1 ( t ) , g 2 ( t ) , … , g 3 ( t ) ) f\big( g_1(t), g_2(t) とします。 ) 、 \dots、 g_3(t) \big)f ( g1( t ) 、g2( t ) 、…、g3( t ) ) の場合、私たちの目標はエンドツーエンドのコストを最小限に抑えることです。 g_2(t)、\dots、g_3(t) \big)最小化f ( g1( t ) 、g2( t ) 、…、g3( t ) )DNN
単一のエンドツーエンド遅延を 最小限に抑えるために、 f ( g 1 , g 2 , … , gn ) = ∑ i = 1 nwi × gif\big( g_1, g_2, \dots, g_n \ big) = \sum_{i=1}^{n} w_i \times g_if ( g1、g2、…、gん)=i = 1∑んw私は×g私は 其中 w i w_i w私はタスクiiですi がDNN
に出現する。この式は単純なので、fffDNN
はエンドツーエンド遅延の近似値です
上の表は、複数の を調整するために使用されるDNN
目的関数の例を示しています。ミリメートルにしましょうmはDNN
数値、S ( j ) S(j)S ( j )はjjに属しますDNN
jのタスクセット。f1f_1f1各 の遅延を合計するとDNN
、これは、DNN
すべてのパイプラインを連続して一度に実行するコストを最適化することを意味します ( f 2 f_2で)。f2、私たちはL j L_jしますLjjjとして定義DNN
jの遅延要件。これは、DNN
j の遅延が満たされる場合、それに時間を費やしたくないことを意味します;f 3 f_3f3、私たちはB j B_jしますBjjjとして定義DNN
jの基準遅延であるため、私たちの目標は、指定された基準遅延に対するスピードアップの幾何平均を最大化することです (最終的にはf 4 f_4 )。f4では、関数ES ( gi , t ) ES(g_i,t)を定義します。JP ( g私は、t ) 、タスクiiを見ることによりiの遅延履歴は早期停止値を返します。これにより、各タスクの早期停止の効果を実現できます。
7.2 勾配降下法による最適化
目的関数を効果的に最適化するために、著者は勾配降下法に基づくスケジューリング アルゴリズムを提案します。そのアイデアは、現在の割り当てttが与えられた場合に、t 、タスクiiを選択するためi、近似目的関数∂ f ∂ ti \frac {\partial f} {\partial t_i}∂t _私は∂f _,使i = argmaxi ∣ ∂ f ∂ ti ∣ i = argmax_i \big| \frac {\partial f} {\partial t_i} \big|私=最大値_ _ _私は
∂t _私は∂f _
。楽観的な推測を行い、タスク間の類似性を考慮して勾配を近似します。
勾配近似式は次のとおりです。∂ f ∂ ti = ∂ f ∂ gi ( α gi ( ti ) − gi ( ti − Δ t ) Δ t + ( 1 − α ) ( min ( − gi ( ti ) ti , β C imaxk ∈ N ( i ) V k − gi ( ti ) ) ) ) \frac {\partial f} {\partial t_i} = \frac {\partial f} {\partial g_i} \bigg( \alpha \frac { g_i(t_i ) - g_i(t_i - \Delta t)} {\Delta t} + \big(1 - \alpha\big)\big(min(-\frac {g_i(t_i)} {t_i}, \beta \frac { C_i} {max_{k\in N(i)} V_k} - g_i(t_i))\big) \bigg)∂t _私は∂f _=∂g _私は∂f _( _Δt _g私は( t私は)−g私は( t私は−Δt ) _+( 1−a ) (分( −t私はg私は( t私は)、bマ×k ∈ N ( i )VkC私は−g私は( t私は)) ) ) ここで、Δ t \Delta tΔtは小さな後方ウィンドウ サイズgi ( ti ) g_i(t_i )です。g私は( t私は)和gi ( ti − Δ t ) g_i(t_i-\Delta t)g私は( t私は−Δt ) はすべて分布履歴N ( i ) N(i)N(i)是 i i i 、 C i C_i内の同様のタスクのコレクションC私はタスクiiですi 、 V k V_kの浮動小数点演算の数Vkタスクkkにありますkで 1 秒あたりに完了できる浮動小数点演算の数α \alphaα和β \betaβ は、特定の予測を信頼するように重みを制御します。
アルゴリズムを実行するには、t = 0Ansor
からt=0 を設定し、(round-robin)
ウォームアップし(warm-up)
初期割り当てベクトルt = ( 1 , 1 , … , 1 ) t=(1,1,\dots,1) をt=( 1 、1 、…、1 )。warm-up
その後、各反復で各タスクの勾配を計算し、argmaxi ∣ ∂ f ∂ ti ∣ argmax_i \big| \frac {\partial f} {\partial t_i} \big| を最大値_ _ _私は
∂t _私は∂f _
次に、タスクiiにリソースユニットを割り当てます。i を計算し、割り当てベクトルti = ti + 1 t_i = t_i +1t私は=t私は+図1に示すように、最適化プロセスは、時間バジェットが使い果たされるまで継続される。探索を促進するために、 ϵ \epsilonを使用します。ϵ貪欲戦略ϵ \epsilon(e-greedy)
で保存しますϵはタスクをランダムに選択します。