用于控制 AI 引擎 graph 的多进程和多线程支持 - 2022.1 简体中文

Versal ACAP AI 引擎编程环境 用户指南 (UG1076)

Document ID
UG1076
Release Date
2022-05-25
Version
2022.1 简体中文

赛灵思 XRT API 提供多进程支持,用于控制 AI 引擎阵列和 graph。它支持 AI 引擎阵列和 graph 上的 3 种操作模式。

Exclusive Mode(专享模式)
全权访问 AI 引擎阵列或 graph。没有任何其它进程可对其进行访问。
Primary Mode(基准模式)
全权访问 AI 引擎阵列或 graph。其它进程可对 AI 引擎阵列或 graph 进行非破坏性访问。
Shared Mode(共享模式)
仅限对 AI 引擎阵列或 graph 进行非破坏性访问。

赛灵思 XRT 提供了下列 API,用于在 3 种模式下打开 AI 引擎阵列。

  • xrtAIEDeviceOpenExclusive(专享模式)
  • xrtAIEDeviceOpen(基准模式)
  • xrtAIEDeviceOpenShared(共享模式)
注释: 如果应用不调用 xrtAIEDeviceOpen* 以获取器件句柄,那么默认情况下它将尝试获取基准上下文,同时尝试通过 XRT API 来访问 AI 引擎阵列。

赛灵思 XRT 还提供了以下 API,以便在打开 AI 引擎阵列后打开 graph。

  • xrtGraphOpenExclusive(专享模式)
  • xrtGraphOpen(基准模式)
  • xrtGraphOpenShared(共享模式)

AI 引擎阵列上的下列 API 在专享模式和基准模式下允许执行加载、复位和 GMIO 数据传输。

  • xrtDeviceLoadXclbin
  • xrtAIEResetArray
  • xrtAIESyncBO
  • xrtDeviceClose

AI 引擎阵列上的下列 API 在共享模式下允许执行非破坏性操作。

  • xrtDeviceLoadXclbin:从 xclbin 读取元数据。
  • xrtDeviceClose

AI 引擎 graph 上的专享模式和基准模式下允许的 API 包括:

  • xrtGraphRun
  • xrtGraphWait
  • xrtGraphEnd
  • xrtGraphUpdateRTP
  • xrtGraphReadRTP
  • xrtGraphTimeStamp
  • xrtGraphClose

AI 引擎阵列和 graph 上的共享模式下允许的 API 包括:

  • xrtGraphUpdateRTP(异步模式)
  • xrtGraphReadRTP
  • xrtGraphTimeStamp
  • xrtGraphClose

以下规则适用于 AI 引擎阵列多进程支持。

  • 在专享模式下,仅有一个进程能打开 AI 引擎阵列。如果 AI 引擎阵列在专享模式下打开,那么无论是在同一进程内还是在其它进程内,都无法在任何其它模式下再次打开。
  • 在基准模式下,仅有一个进程能打开 AI 引擎阵列。如果 AI 引擎阵列在基准模式下打开,则在专享模式下或基准模式下无法再次打开,但在共享模式下可以将其再次打开。

以下规则适用于 AI 引擎 graph 多进程支持。

  • 在任一进程内,graph 不应多次打开。
  • 如果某个 AI 引擎 graph 已在专享模式下打开,则无法在任何模式下再次打开。
  • 在共享模式下,可有多个进程打开 graph,但在基准模式下,仅允许在单个进程内打开此 graph。

必须先关闭 graph,然后才能关闭阵列。关闭所有已打开的 AI 引擎阵列和 graph 后,即可再次打开这些 AI 引擎阵列和 graph,并且前述规则再次适用。

注释: 当某个 graph 或 AI 引擎阵列由 xrtGraphClosexrtDeviceClose 关闭后,仅关闭已打开的上下文。它并不会清除 AI 引擎阵列中的数据存储器、程序存储器、FIFO 和 DMA 状态。

下图汇总了专享模式下的多进程支持(黑色:受支持,红色:不受支持,“*”:任意模式)。

图 1. 专享模式下的多进程支持

下图汇总了基准模式和共享模式下的多进程支持(黑色:受支持,红色:不受支持)。

图 2. 基准模式和共享模式下的多进程支持

建议多线程使用与多进程相同的模型。但由于 AI 引擎器件句柄和 graph 句柄可在线程之间共享,因此在线程之间使用相同的器件句柄或 graph 句柄是合规的。主机应用负责在线程之间同步 AI 引擎阵列状态和 graph 状态,当多线程是 AI 引擎阵列或 graph 的专享或基准所有者时,尤其如此。

以下提供了使用多进程的样本代码。

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

#include "graph.cpp"

//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
		xrtDeviceHandle dhdl2=xrtAIEDeviceOpenShared(0);
		ret=xrtDeviceLoadXclbinFile(dhdl2,xclbinFilename);
   		if(ret){
			printf("child child Xclbin Load fail\n");
    		}
		if(!dhdl2){
			std::cout<<"child child device open error"<<std::endl;
			return 1;
		}else{
			std::cout<<"child child device open pass"<<std::endl;
		}
		xuid_t uuid2;
    		ret=xrtDeviceGetXclbinUUID(dhdl2, uuid2);
		if(ret){
			std::cout<<"child child get xclbin uuid error"<<std::endl;
			return 1;
		}else{
			std::cout<<"child child get xclbin uuid pass"<<std::endl;
		}
		auto ghdl2=xrtGraphOpenShared(dhdl2,uuid2,graph_name.data());
		if(!ghdl2){
			std::cout<<"child child graph open error"<<std::endl;
			return 1;
		}else{
			std::cout<<"child child graph open pass"<<std::endl;
		}

		ret=xrtGraphReadRTP(ghdl2, rtp_inout_name.data(), (char*)value_readback, 16*sizeof(int));
		if(ret){
			std::cout<<"child child Graph RTP read fail"<<std::endl;
			return 1;
		}
		std::cout<<"Add value read back are:";
		for(int i=0;i<16;i++){
			std::cout<<value_readback[i]<<",\t";
		}
		std::cout<<std::endl;
		xrtGraphClose(ghdl2);
		xrtDeviceClose(dhdl2);
		std::cout<<"child child process exit"<<std::endl;
		exit(0);
	}

	xrtDeviceHandle dhdl=xrtAIEDeviceOpen(0);
	ret=xrtDeviceLoadXclbinFile(dhdl,xclbinFilename);
   	if(ret){
		printf("Xclbin Load fail\n");
    	}
	xuid_t uuid;
    	xrtDeviceGetXclbinUUID(dhdl, uuid);

	auto ghdl=xrtGraphOpen(dhdl,uuid,graph_name.data());
	if(!ghdl){
		std::cout << "Graph Open error" << std::endl;
	}else{
		std::cout << "Graph Open ok" <<std::endl;
	}
	std::string rtp_in_name=std::string("gr[")+std::to_string(id)+std::string("].k.in[1]");
	ret=xrtGraphUpdateRTP(ghdl,rtp_in_name.data(),(char*)value1,16*sizeof(int));
	if(ret){
		std::cout<<"Graph RTP update fail"<<std::endl;;
		return 1;
	}
	xrtGraphRun(ghdl,16);

	xrtGraphWait(ghdl,0);
	std::cout<<"Graph wait done"<<std::endl;
			
	//second run
	ret=xrtGraphUpdateRTP(ghdl,rtp_in_name.data(),(char*)value2,16*sizeof(int));
	if(ret!=0){
		std::cout<<"Graph RTP update fail"<<std::endl;
		return 1;
	}else{
		std::cout<<"Graph RTP update pass"<<std::endl;
	}
	xrtGraphRun(ghdl,16);

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

	ret=xrtGraphWait(ghdl,0);
	if(ret){
		std::cout << "Graph wait error" << std::endl;
	}else{
		std::cout<<"Graph done"<<std::endl;
	}
	xrtGraphClose(ghdl);
	xrtDeviceClose(dhdl);
	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;
	}
}