AXI パフォーマンスのケース スタディ - 2023.2 日本語

Vitis 高位合成ユーザー ガイド (UG1399)

Document ID
UG1399
Release Date
2023-12-18
Version
2023.2 日本語

はじめに

このケース スタディの目的は、HLS メトリックを使用して読み出し/書き込みのループ/関数のスループットを向上させるため最適化方法を手順を追って示すことです。これらの最適化では、グローバル メモリからカーネルへの効率的なデータ転送を実行することで、システムのカーネル時間とスループットを向上させます。次の transfer_kernel の例では、DDR の単純な (可変サイズで NUM_ITERATIONS の) 読み出し/書き込みを実行します。

ヒント: ホスト コード (表示されていません) は、データを転送し、カーネルを順序どおりのキューに入れるだけです。
1 #include "config.h"
 2 #include "assert.h"
 3 extern "C" {
 4    void transfer_kernel(wd* in,wd* out, const int size, const int iter ) {
 5 ···
 6        wd buf[256];
 7        int off = (size/16);
 8  
 9        read_loop: for (int i = 0; i <off; i++)
10        {
11           buf[i] = in[i];
12        }
13
14     write_loop: L1: for (int i = 0; i < iter; i++) {
15        L2: for (int j = 0; j <off; j++) {
16        #pragma HLS PIPELINE II=1
17           out[j+off*i] = buf[j];
18           }
19        }
20 ···
21    }
22 }

このケース スタディは、次の 4 つの手順に分かれています。

  1. ポート幅を 512 ビット幅でカーネルの実行時間のベースラインを設定
  2. レイテンシ パラメーターを変更してパフォーマンスを向上
  3. 書き込みループの自動バースト推論を改善
  4. これ以上の複数ポートと未処理の書き込み数を使用した改善はなし

手順 1: 512 ビットのポート幅でカーネルのベースラインを設定

デフォルト設定を使用してカーネル時間のベースラインを設定します。この実行中、自動バーストは読み出しおよび書き込みループに対して次を推論します。

  • ツールが連続するメモリ アクセス パターンを予測できるため、読み出しループはパイプライン バーストを達成します。このため、可変サイズの DDR からの読み出し要求がパイプライン処理されます。
  • 書き込み外部ループの L1 は、シーケンシャル バーストを取得します。これは、コンパイラがすべての組み合わせを繰り返し、L2 ループの開始前に L1 ループに if 条件を挿入するかどうかを判断するからです (コンパイル時にサイズが不明であるため)。同時に、最も内側のループ L2 がパイプライン バーストを達成します。L2 ループは可変サイズの書き込み要求を要求し、L1 は L2 ループのすべてのデータが DDR から戻って L1 の次の反復を開始するまで待機します。

アプリケーションを構築して実行した後は、Vitis アナライザー ツールを使用してパフォーマンスを評価し、ビルド プロセスまたは実行サマリによって生成されたレポートを表示できます。Vitis HLS からの合成レポートに表示されるバースト サマリを確認します。読み出しループおよび書き込みループのバーストが成功したか、エラーになったかを確認します。

図 1. 合成レポート - バースト サマリ

Vitis アナライザーでは、プロファイル サマリおよびタイムライン トレース レポートも、FPGA アクセラレーション アプリケーションのパフォーマンスを解析するのに役立ちます。プロファイル サマリでは、Kernels & Compute Unit: Kernel Execution にベースライン ビルドで transfer_kernel が必要とする合計時間がレポートされます。

図 2. プロファイル サマリ - カーネルの実行

手順 2: パフォーマンス レイテンシの改善

Vitis HLS はデフォルトの 64 カーネル サイクルのレイテンシを使用しますが、それが長すぎる場合もあります。レイテンシはシステムの特性によって異なります。この例では、レイテンシはデフォルトから 21 カーネル サイクルに減少しています。次の例に示すようにコードを変更し、INTERFACE プラグマまたは指示子を使用して、レイテンシを指定します。

1 #include "config.h"
 2 #include "assert.h"
 3 extern "C" {
 4    void transfer_kernel(wd* in,wd* out, const int size, const int iter ) {
 5    #pragma HLS INTERFACE m_axi port=in0_index offset=slave latency=21
 6    #pragma HLS INTERFACE m_axi port=out offset=slave latency=21

 7 ...

アプリケーションを構築して実行したら、Vitis アナライザーを使用して、ビルド プロセスまたは実行サマリによって生成されたレポートを表示できます。Vitis HLS の合成レポートの HW Interface の表で、指定したレイテンシが適用されていることを確認します。

図 3. 合成レポート - HW インターフェイス

Burst Summary でそのプロセスが成功したか、エラーになったを確認します。

図 4. 合成レポート - バースト サマリ 2

プロファイル サマリ レポートの「Kernel Execution」で、インターフェイスのレイテンシを設定することでパフォーマンスが向上したことを確認します。

図 5. プロファイル サマリ - カーネルの実行 2

手順 3: 書き込みループの自動バースト推論を改善

コンパイル時にサイズとループ トリップ数が不明なため、コンパイラの自動バースト推論の見積もりは不必要に悪くなります。次に示すようにコードを変更すると、コンパイラがパイプライン バーストを推論できます。

1 #include "config.h"
  2 #include "assert.h"
  3 extern "C" {
  4    void transfer_kernel(wd* in,wd* out, const int size, const int iter ) {
  5    #pragma HLS INTERFACE m_axi port=in offset=slave latency=21
  6    #pragma HLS INTERFACE m_axi port=out offset=slave latency=21
  7
  8       int k=0;
  9       wd buf[256];
 10       int off = (size/16);
 11 
 12       read_loop: for (int i = 0; i <off; i++)
 13       {
 14          buf[i] = in[i];
 15       }
 16
 17       write_loop: for (int j = 0; j <off*iter; j++) {
 18       #pragma HLS PIPELINE II=1
 19          out[k++] = buf[j%off];
 20       }
 21    }
 22 }

アプリケーションを構築して実行したら、Vitis アナライザーを使用して、ビルド プロセスまたは実行サマリによって生成されたレポートを表示できます。合成レポートからは、コンパイラへのバースト ヒントによって書き込みループのシーケンシャル バーストが修正されたことが確認できます。Burst and Widening Missed というメッセージは、ポートを 512 ビットに拡張することに関連しています。この例では既にポート幅 512 を使用しているため、無視して問題ありません。コードで幅が 512 ビットになっていない場合は、これらのメッセージの示す問題を解決する必要があります。

図 6. 合成レポート - バースト サマリ 3

プロファイル サマリ レポートの「Kernel Execution」からは、手順 2 のレイテンシ変更と現在の手順の書き込みループのパイプライン バーストの結果、パフォーマンスが改善したことがわかります。

図 7. プロファイル サマリ - カーネルの実行 3

概要

Vitis HLS インターフェイス メトリックから実行できるその他の改善はありません。ケース スタディの例では、同時読み出しまたは書き込みがないため、複数のポートをターゲットにするのは、この場合には役立ちません。この例では、ツールが最大スループットのパイプライン バーストを達成しているため、未処理の読み出しと書き込みの数も無視できます。カーネル時間からは、これ以上の改善は確認できません。

ケース スタディのように、効率的な load-store 関数のインプリメンテーションは、ポート幅、バースト アクセス、レイテンシ、複数ポート、および未処理の読み取りと書き込みの数などの HLS インターフェイス メトリックに依存します。AMDでは、システム パフォーマンスを向上させるために次のガイドラインを推奨します。

  • ポート幅: ポートのデータ型として hls::vector または ap_(u)int<512> を使用して、インターフェイスのポート幅 (各 AXI ポートのビット幅) を最大化します。
  • 複数ポート: 同時メモリの読み出し/書き込みを解析し、同時アクセス用の専用/独立ポートを備えます。
  • パイプライン バースト: AXI レイテンシ パラメーターはパイプライン バーストに影響を与えません。パフォーマンスを大幅に向上させることができるパイプライン バーストを達成するようにコードを記述することをお勧めします。
  • シーケンシャル バースト: AXI レイテンシ パラメーターは、シーケンシャル バーストに大きな影響を与え、ツールのデフォルト レイテンシからレイテンシ数を減少させることで、パフォーマンスを向上します。
  • 未処理の数: ほとんどのバースト長が 16 以上の場合、デフォルトの未処理の数 (num outstanding) で十分です。AMD では、バースト サイズが 16 未満の場合は、デフォルト (=16) から未処理の数のサイズを 2 倍にすることをお勧めしています。
  • データの並べ替え: パイプライン バーストの達成は常に推奨されますが、メモリ アクセス パターンのために、コンパイラがシーケンシャル バーストのみを達成できる場合もあります。データをメモリに保存するさまざまな方法を検討して、パフォーマンスを改善することもできます。たとえば、DRAM 内のデータに列優先順でアクセスすると、効率性がかなり劣ることがあります。カーネルに専用のデータ ムーバーをインプリメントするよりも、ソフトウェア内のデータを置き換えて、行優先順で格納した方が良い場合があり、ハードウェア アクセス パターンをかなりシンプルにできます。