AI エンジンと DDR メモリ接続のプログラミング モデル - 2023.2 日本語

AI エンジン カーネルおよびグラフ プログラミング ガイド (UG1079)

Document ID
UG1079
Release Date
2023-12-04
Version
2023.2 日本語

input_gmio/output_gmio ポート属性を使用して、PS プログラム内で AI エンジン と DDR 間のメモリ読み出し/書き込みトランザクションを開始できます。これにより、PS プログラム内に記述された API を介して AI エンジン と DDR コントローラー間でデータを転送できます。次に、GMIO API を使用して AI エンジンにデータを送信して処理し、DDR に返された処理済みデータを受け取る PS プログラムの例を示します。

graph.h
class mygraph: public adf::graph
{
private:
  adf::kernel k_m;

public:
  adf::output_gmio gmioOut;
  adf::input_gmio gmioIn;
  mygraph()
  { 
    k_m = adf::kernel::create(weighted_sum_with_margin);
    gmioOut = adf::output_gmio::create("gmioOut", 64, 1000);
    gmioIn = adf::input_gmio::create("gmioIn", 64, 1000);

    adf::connect(gmioIn.out[0], k_m.in[0]);
    adf::connect(k_m.out[0], gmioOut.in[0]);

    dimensions(k_m.in[0]) = {256};
    dimensions(k_m.out[0]) = {256};

    adf::source(k_m) = "weighted_sum.cc";
    adf::runtime<adf::ratio>(k_m)= 0.9;
  };
};

graph.cpp

myGraph gr;
int main(int argc, char ** argv)
{
    const int BLOCK_SIZE=256;
    int32 *inputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
    int32 *outputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
 
    // provide input data to AI Engine in inputArray
    for (int i=0; i<BLOCK_SIZE; i++) {
        inputArray[i] = i;
    }
 
    gr.init();
          
    gr.gmioIn.gm2aie_nb(inputArray, BLOCK_SIZE*sizeof(int32));
    gr.gmioOut.aie2gm_nb(outputArray, BLOCK_SIZE*sizeof(int32));
 
    gr.run(8);
 
    gr.gmioOut.wait();
 
    // can start to access output data from AI Engine in outputArray
	... 

    GMIO::free(inputArray);
    GMIO::free(outputArray);
    gr.end();
}

次の例では、2 つの I/O オブジェクトを宣言しています。gmioInAI エンジンによって読み出される DDR メモリ空間を表し、gmioOutAI エンジンによって書き込まれる DDR メモリ空間を表します。コンストラクターに、GMIO の論理名、メモリ マップド AXI4 トランザクションのバースト長 (64、128、256 バイトのいずれか)、必要な帯域幅 (MB/s) が指定されています。

gmioOut = adf::output_gmio::create("gmioOut", 64, 1000);
gmioIn  = adf::input_gmio::create("gmioIn", 64, 1000);

アプリケーション グラフ (myGraph) には、処理カーネルに接続する入力ポート (myGraph::gmioIn) があります。カーネルは、カーネルから処理されたデータを生成する出力ポート (myGraph::gmioOut) にデータを生成します。次のコードは、グラフの入力ポートに接続し、グラフの出力ポートに接続します。

adf::connect(gmioIn.out[0], k_m.in[0]);
adf::connect(k_m.out[0], gmioOut.in[0]);
dimensions(k_m.in[0]) = {256};
dimensions(k_m.out[0]) = {256};

main 関数内で、GMIO::malloc によって要素数が 256 の int32 配列が 2 つ割り当てられます。inputArrayAI エンジンで読み出されるメモリ空間をポイントし、outputArrayAI エンジンで書き込まれるメモリ空間をポイントします。Linux では、GMIO::gm2aie_nbGMIO::aie2gm_nbGMIO::gm2aie、および GMIO::aie2gm に渡される仮想アドレスは、GMIO::malloc で割り当てる必要があります。入力データは割り当て後に初期化できます。

const int BLOCK_SIZE=256; 
int32 *inputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
int32 *outputArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32));
gr.gmioIn.gm2aie_nb() を使用して、AI エンジンが DDR メモリ空間から読み出すメモリ マップド AXI4 トランザクションを初期化します。gr.gmioIn.gm2aie_nb() の最初の引数は、トランザクションのメモリ空間の開始アドレスへのポインターです。2 番目の引数はトランザクション サイズ (バイト) です。トランザクションのメモリ空間は、GMIO::malloc によって割り当てられたメモリ空間内になければなりません。同様に、gr.gmioOut.aie2gm_nb() を使用して、AI エンジンが DDR メモリ空間に書き込むメモリ マップド AXI4 トランザクションを初期化します。gr.gmioOut.gm2aie_nb() または gr.gmioOut.aie2gm_nb() は、トランザクションの発行後すぐに返され、トランザクションの完了を待たないので、ノンブロッキング関数であると言えます。一方、gr.gmioIn.gm2aie() または gr.gmioOut.aie2gm() は、ブロッキング関数のように動作します。
この例では、グラフは 1 回の繰り返しで 32 の int32 データを入力ポートから消費し、32 の int32 データを出力ポートに生成します。8 回繰り返して実行する場合、グラフは 256 の int32 データを消費し、256 の int32 データを生成します。これに対応するメモリ マップド AXI4 トランザクションは、次のコードを使用して初期化されます。gr.gmioIn.gm2aie_nb() を 1 回呼び出すと、8 回の繰り返しに相当するデータの読み出しトランザクションが実行され、gr.gmioOut.aie2gm_nb() を 1 回呼び出すと、8 回の繰り返しに相当するデータの書き込みトランザクションが実行されます。
gr.gmioIn.gm2aie_nb(inputArray, BLOCK_SIZE*sizeof(int32));
gr.gmioOut.aie2gm_nb(outputArray, BLOCK_SIZE*sizeof(int32));

gr.run(8) もグラフを 8 回繰り返して実行するノンブロッキング呼び出しです。PS と AI エンジンの間で DDR メモリ読み出し/書き込みアクセスを同期するには、gr.gmioOut.wait() を使用して、GMIO トランザクションが完了するまで PS 実行をブロックします。この例では、gr.gmioOut.wait() を呼び出して、出力データが outputArray DDR メモリ空間に書き込まれるまで待機しています。

注記: Linux の GMIO では、メモリはキャッシュ不可能です。

この後、PS プログラムからデータにアクセスできます。PS の処理が完了したら、GMIO::free を呼び出して、GMIO::malloc によって割り当てられたメモリ空間を解放します。

GMIO::free(inputArray);
GMIO::free(outputArray);

input_gmio/output_gmio API をさまざまな方法で使用して、異なるレベルの読み出し/書き込みアクセス制御や AI エンジン、PS、および DDR メモリ間の同期を実行できます。グラフ実行のさまざまなフェーズで input_gmio::gm2aieoutput_gmio::aie2gminput_gmio::gm2aie_nb または output_gmio::aie2gm_nb を複数回呼び出すことにより、異なるメモリ空間を同じ input_gmio/output_gmio オブジェクトに関連付けることができます。インプレースの AI エンジン - DDR 読み出し/書き込みアクセスのために、異なる input_gmio/output_gmio オブジェクトを同じメモリ空間に関連付けることもできます。ブロッキング バージョンの input_gmio::gm2aie および output_gmio::aie2gm API は、それ自体がデータ転送とカーネル実行の同期ポイントになっています。input_gmio::gm2aie (または output_gmio::aie2gm) の呼び出しは、input_gmio::gm2aie_nb (または output_gmio::aie2gm_nb) を呼び出した後で output_gmio::wait を呼び出すのと同じです。次に、上で説明したユース ケースを組み合わせた例を示します。

myGraph gr;

 int main(int argc, char ** argv)
{

    const int BLOCK_SIZE=256;
    // dynamically allocate memory spaces for in-place AI Engine read/write access
    int32* inoutArray=(int32*)GMIO::malloc(BLOCK_SIZE*sizeof(int32)); 
    gr.init();
 
    for (int k=0; k<4; k++)
    {
        // provide input data to AI Engine in inoutArray
        for(int i=0;i<BLOCK_SIZE;i++){
            inoutArray[i]=i;
        }

        gr.run(8);
        for (int i=0; i<8; i++)
        {
            gr.gmioIn.gm2aie(inoutArray+i*32, 32*sizeof(int32)); //blocking call to ensure transaction data is read from DDR to AI Engine
            gr.gmioOut.aie2gm_nb(inoutArray+i*32, 32*sizeof(int32));
        }
        gr.gmioOut.wait();
     
        // can start to access output data from AI Engine in inoutArray
	//	...        
    }
    GMIO::free(inoutArray);
    gr.end();
    return 0;
}

上記の例では、inoutArray によって割り当てられた同じメモリ空間を、2 つの GMIO オブジェクト (gmioIngmioOut) がインプレース読み出し/書き込みアクセスのために使用しています。

グラフ内でのカーネル間のデータフロー依存関係が不明な場合や、inoutArray メモリ空間で読み出し後の書き込みを確実にするために、ブロッキング バージョンの gr.gmioIn.gm2aie() を呼び出して、トランザクション データが DDR メモリから AI エンジンのローカル メモリにコピーされたことを確認してから、gr.gmioOut.aie2gm_nb() で同じメモリ空間への書き込みトランザクションを実行します。

gr.gmioIn.gm2aie(inoutArray+i*32, 32*sizeof(int32)); //blocking call to ensure transaction data is read from DDR to AI Engine
gr.gmioOut.aie2gm_nb(inoutArray+i*32, 32*sizeof(int32));

gr.gmioOut.wait() の目的は、データが DDR メモリに移行されたことを確認することであり、その完了後に、PS から出力データにアクセスしてポストプロセスを実行できます。

グラフの実行は、for ループ (for (int k=0; k<4; k++)) 内で 4 つのフェーズに分かれています。for ループ内で inoutArray を再初期化すると、フェーズごとに異なるデータを処理できます。