AI エンジン グラフの制御におけるマルチプロセスおよびマルチスレッド サポート - 2023.2 日本語

AI エンジン ツールおよびフロー ユーザー ガイド (UG1076)

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

ザイリンクス ランタイム (XRT) API では、AI エンジン アレイおよびグラフの制御にマルチプロセスがサポートされます。AI エンジン アレイおよびグラフでは、3 つの動作モードがサポートされます。

排他的モード
AI エンジン アレイまたはグラフに完全にアクセスできます。ほかのプロセスにはアクセスできません。
プライマリ モード
AI エンジン アレイまたはグラフに完全にアクセスできます。ほかのプロセスでは、AI エンジン アレイまたはグラフへの非破壊的アクセスのみが可能です。
共有モード
AI エンジン アレイまたはグラフへの非破壊的アクセスのみが可能です。
XRT C++ API は、xrt/xrt_aie.h のアクセス モードでデバイスを開くことができるよう xrt::aie::device クラスを拡張します。
  • xrt::aie::access_mode::exclusive (排他的モード)
  • xrt::aie::access_mode::primary (プライマリ モード)
  • xrt::aie::access_mode::shared (共有モード)
XRT C++ API は、xrt/xrt_graph.h のアクセス モードでグラフを開くことができるよう xrt::graph クラスを拡張します。
  • xrt::graph::access_mode::exclusive (排他的モード)
  • xrt::graph::access_mode::primary (プライマリ モード)
  • xrt::graph::access_mode::shared (共有モード)

AI エンジン アレイのマルチプロセス サポートには、次の規則が適用されます。

  • 排他的モードでは、1 つのプロセスでのみ AI エンジン アレイを開くことができます。AI エンジン アレイを排他的モードで開いた場合、どのモードでも同じプロセスまたはほかのプロセスで再び開くことはできません。
  • プライマリ モードでは、1 つのプロセスでのみ AI エンジン アレイを開くことができます。AI エンジン アレイをプライマリ モードで開いた場合、排他的モードまたはプライマリ モードで再び開くことはません。

AI エンジン グラフのマルチプロセス サポートには、次の規則が適用されます。

  • 1 つのプロセスでグラフを複数回開くことはできません。
  • AI エンジン グラフを排他的モードで開いた場合、そのグラフはどのモードでも開くことはできません。
  • 共有モードでは複数のプロセスでグラフを開くことが可能ですが、プライマリ モードでは 1 つのプロセスでのみ開くことが可能です。

アレイを閉じる前にグラフを閉じる必要があります。開いているすべての AI エンジン アレイおよびグラフを閉じると、AI エンジン アレイおよびグラフを再び開くことができ、同じ規則が適用されます。

複数スレッドでは、複数プロセスと同じモデルを使用することをお勧めします。ただし、AI エンジン デバイス ハンドルとグラフ ハンドルはスレッド間で共有できるので、同じデバイス ハンドルまたはグラフ ハンドルを複数のスレッドで使用できます。特に複数スレッドが AI エンジン アレイまたはグラフの排他的またはプライマリ オーナーである場合、スレッド間で AI エンジン アレイのステートとグラフのステートを同期化するのはホスト アプリケーションの役割です。

次に、複数プロセスを使用するサンプル コードを示します。

#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include "adf/adf_api/XRTConfig.h"
#include "experimental/xrt_aie.h"
#include "experimental/xrt_graph.h"
#include "experimental/xrt_kernel.h"

//8192 matches 32 iterations of graph::run
#define OUTPUT_SIZE 8192
int value1[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
int value2[16] = {-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15,-16};

using namespace adf;

int run(int argc, char* argv[],int id){
	std::cout<<"Child process "<<id<<" start"<<std::endl;
	
	//TARGET_DEVICE macro needs to be passed from gcc command line
	if(argc != 2) {
		std::cout << "Usage: " << argv[0] <<" <xclbin>" << std::endl;
		return EXIT_FAILURE;
	}
	char* xclbinFilename = argv[1];
	std::string graph_name=std::string("gr[")+std::to_string(id)+"]";
	std::string rtp_inout_name=std::string("gr[")+std::to_string(id)+std::string("].k.inout[0]");
	
	int ret;
	int value_readback[16]={0};
	if(fork()==0){//child child process
		xrt::aie::device device{0, xrt::aie::device::access_mode::shared};
		auto uuid = device.load_xclbin(xclbinFilename);
		xrt::graph graph{device, uuid, graph_name, xrt::graph::access_mode::shared};

		graph.read(rtp_inout_name, value_readback);
		std::cout<<"Add value read back are:";
		for(int i=0;i<16;i++){
			std::cout<<value_readback[i]<<",\t";
		}
		std::cout<<std::endl;
		std::cout<<"child child process exit"<<std::endl;
		exit(0);
	}

	xrt::aie::device device{0};   // default primary context
	auto uuid = device.load_xclbin(xclbinFilename);
	xrt::graph graph{device, uuid, graph_name}; // default primary context

	std::string rtp_in_name=std::string("gr[")+std::to_string(id)+std::string("].k.in[1]");
	graph.update(rtp_in_name, value1);
	graph.run(16); // 16 iterations

	graph.wait(0); // wait 0 => wait till graph is done
	std::cout<<"Graph wait done"<<std::endl;
			
	//second run
	graph.update(rtp_in_name.data(), value2);
	graph.run(16); // 16 iterations;

	while(wait(NULL)>0){//Wait for child child process
	}

	graph.wait(0); // wait 0 => wait till graph is done
	std::cout<<"Child process:"<<id<<" done"<<std::endl;
	return 0;
}

int main(int argc, char* argv[])
{
	try {
		for(int i=0;i<GRAPH_NUM;i++){
			if(fork()==0){//child
				auto match = run(argc, argv,i);
				std::cout << "TEST child " <<i<< (match ? " FAILED" : " PASSED") << "\n";
				return (match ? EXIT_FAILURE :  EXIT_SUCCESS);
			}else{
				size_t output_size_in_bytes = OUTPUT_SIZE * sizeof(int);
				//TARGET_DEVICE macro needs to be passed from gcc command line
				if(argc != 2) {
					std::cout << "Usage: " << argv[0] <<" <xclbin>" << std::endl;
					return EXIT_FAILURE;
				}
				char* xclbinFilename = argv[1];
				
				int ret;
				// Open xclbin
				auto device = xrt::device(0); //device index=0
				auto uuid = device.load_xclbin(xclbinFilename);
			
				// s2mm & data_generator kernel handle
				std::string s2mm_kernel_name=std::string("s2mm:{s2mm_")+std::to_string(i+1)+std::string("}");
				xrt::kernel s2mm = xrt::kernel(device, uuid, s2mm_kernel_name.data());
				std::string data_generator_kernel_name=std::string("data_generator:{data_generator_")+std::to_string(i+1)+std::string("}");
				xrt::kernel data_generator = xrt::kernel(device, uuid, data_generator_kernel_name.data());
			
				// output memory
				auto out_bo=xrt::bo(device, output_size_in_bytes,s2mm.group_id(0));
				auto host_out=out_bo.map<int*>();
				auto s2mm_run = s2mm(out_bo, nullptr, OUTPUT_SIZE);//1st run for s2mm has started
				auto data_generator_run = data_generator(nullptr, OUTPUT_SIZE);

				// wait for s2mm done
				std::cout<<"Waiting s2mm to complete"<<std::endl;
				auto state = s2mm_run.wait();
				std::cout << "s2mm "<<" completed with status(" << state << ")"<<std::endl;
				out_bo.sync(XCL_BO_SYNC_BO_FROM_DEVICE);
				
				int match = 0;
				int counter=0;
				for (int i = 0; i < OUTPUT_SIZE/2/16; i++) {
					for(int j=0;j<16;j++){
						if(host_out[i*16+j]!=counter+value1[j]){
							std::cout<<"ERROR: num="<<i*16+j<<" out="<<host_out[i*16+j]<<std::endl;
							match=1;
							break;
						}
						counter++;
					}
				}
				for(int i=OUTPUT_SIZE/2/16;i<OUTPUT_SIZE/16;i++){
					for(int j=0;j<16;j++){
						if(host_out[i*16+j]!=counter+value2[j]){
							std::cout<<"ERROR: num="<<i*16+j<<" out="<<host_out[i*16+j]<<std::endl;
							match=1;
							break;
						}
						counter++;
					}
				}

				std::cout << "TEST " <<i<< (match ? " FAILED" : " PASSED") << "\n";
				while(wait(NULL)>0){//Wait for all child process
				}
				std::cout<<"all done"<<std::endl;
				return (match ? EXIT_FAILURE :  EXIT_SUCCESS);
			}
		}
	}	
		catch (std::exception const& e) {
		std::cout << "Exception: " << e.what() << "\n";
		std::cout << "FAILED TEST\n";
		return 1;
	}
}