OpenVINO 2021r2 - Remote Blob API of GPU Plugin 示例复现(一) OpenCL Kernel Execution on a Shared Buffer

本文档介绍了如何使用OpenVINO的GPU Remote Blob API进行推理,并将输出数据存储在GPU内存中,以便直接进行OpenCL后处理,避免了数据在GPU和CPU间不必要的传输。通过创建cl::Buffer,将其转换为InferenceEngine的Blob,实现了数据共享。在代码示例中,展示了从输入图像到GPU推理再到GPU内存中输出数据的完整流程。
摘要由CSDN通过智能技术生成

前一阵尝试了一下OpenVINO纯GPU推理的代码实现, 主要复现了OpenVINO官方文档Remote Blob API of GPU Plugin 中的 OpenCL Kernel Execution on a Shared Buffer例子

官方示例的完整实现可以具体参考这篇文章 OpenVINO 2020r3 体验GPU Remote Blob API

 

在代码里,把OpenVINO clDNN的cl::context提取出来,再基于这个context创建了cl::device和cl::queue.

然后基于这个cl::context创建了一个cl::Buffer (就是cl_mem对象), 再将这个cl::Buffer 用gpu::make_shared_blob把Buffer转换成了Inference Engine能识别的内存Blob,

最后通过 InferRequest::SetBlob将这个Blob和推理网络的输入层连在一起,这个在inf_req_shared.Infer()的时候就会在读取输入层数据的时候从这个Blob的cl::Buffer里读取数据,这样就完成了从GPU显存(OpenCL cl_mem对象)里读取输入数据

 

上次的这段代码主要参考自OV源码库的openvino-master\inference-engine\tests\functional\plugin\gpu\remote_blob_tests\cldnn_remote_blob_tests.cpp里面。这次基于OpenVINO 2021r2把这段代码补全一下,将上图中的“OpenVINO GPU推理”的输出部分的数据也存放到cl_mem对象里,这样推理结束后就可以基于这个cl_mem对象用自己的OpenCL代码来实现基于GPU的数据后处理,避免使用CPU将数据读取到系统内存中再拷回显存的开销。 这个例子叫 OpenCL Kernel Execution on a Shared Buffer and Output to a Shared Buffer :)

 

GPU RemoteBlob API推理代码的实现



		// inference using remote blob
		auto inf_req_shared = executable_network.CreateInferRequest();

		// obtain the RemoteContext pointer from the executable network object
		//从ExecutableNetwork中获取cldnn的cl_context
		auto cldnn_context = executable_network.GetContext();
		cl_context ctx = std::dynamic_pointer_cast<gpu::ClContext>(cldnn_context)->get();

		cl::Context _context;
		cl::Device _device;
		cl::CommandQueue _queue;
		// user-supplied context handle
		// 基于这个cl_context创建cl::Context/Device/Queue
		_context = cl::Context(ctx, true);
		_device = cl::Device(_context.getInfo<CL_CONTEXT_DEVICES>()[0].get(), true);

		cl_command_queue_properties props = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE;
		_queue = cl::CommandQueue(_context, _device, props);

		auto dims = network.getInputsInfo().begin()->second->getTensorDesc().getDims();
		size_t imSize = dims[1] * dims[2] * dims[3];
		cout << "imSize = " << imSize << " dims[1]=" << dims[1] << " dims[2]=" << dims[2] << " dims[3]=" << dims[3] << endl << endl;

		size_t num_channels = dims[1];
		size_t image_size = dims[3] * dims[2];

		//prepare input image data
		/** Iterate over all pixel in image (b,g,r) **/
		//将输入图像的RGB数据放到ImageBuffer里
		unsigned char *ImageBuffer;
		ImageBuffer = (unsigned char *)malloc(imSize);
		unsigned char* pixels = (unsigned char*)(jpg.data);
		for (size_t pid = 0; pid < image_size; pid++) {
			/** Iterate over all channels **/
			for (size_t ch = 0; ch < num_channels; ++ch) {
				/**          [images stride + channels stride + pixel id ] all in bytes            **/
				ImageBuffer[ch * image_size + pid] = pixels[pid*num_channels + ch];
				//set input data to 0
				//ImageBuffer[ch * image_size + pid] = 0;
			}
		}

		//创建cl::Buffer shared_buffer作为输入Buffer,并将ImageBuffer的数据放入shared_buffer内
		cl_int err;
		cl::Buffer shared_buffer(_context, CL_MEM_READ_WRITE, imSize, NULL, &err);
		{
			void *buffer = ImageBuffer;
			_queue.enqueueWriteBuffer(shared_buffer, true, 0, imSize, buffer);
		}

		//将cl::Buffer封装进InferenceEngine::Blob
		Blob::Ptr shared_blob = gpu::make_shared_blob(network.getInputsInfo().begin()->second->getTensorDesc(), cldnn_context,
			shared_buffer);

		//将网络输入层的数据指针指向存有输入图像RGB值的Blob
		inf_req_shared.SetBlob(network.getInputsInfo().begin()->first, shared_blob);

		//这里创建2个内存块, 块大小和推理网络输出层的数据块大小一致
		//SqueezeNet输出是1000个FP32的值,所以每个块的大小为4KB
		//块的大小可以通过getOutputsInfo()来获取,这里偷懒,手工写成4KB
		//C[]全填0,用来将推理输出层的Blob全清0; D[]用来存放从推理输出的cl::Buffer中读取的数据
		size_t outputSize = 1000 * 4;
		float *C = new float[1000];
		float *D = new float[1000];
		for (int i = 0; i < 1000; i++)
		{
			C[i] = 0;
			D[i] = 0;
		}

		//创建cl::Buffer shared_output_buffer作为输出Buffer,并将输出Buffer清0
		cl::Buffer shared_output_buffer(_context, CL_MEM_READ_WRITE, outputSize, NULL, &err);
		{
			void *buffer = ImageBuffer;
			_queue.enqueueWriteBuffer(shared_output_buffer, true, 0, sizeof(float)*1000, C);
		}

		//将cl::Buffer封装进InferenceEngine::Blob shared_output_blob
		Blob::Ptr shared_output_blob = gpu::make_shared_blob(network.getOutputsInfo().begin()->second->getTensorDesc(), cldnn_context,
			shared_output_buffer);
		//将网络输出层的数据指针指向shared_output_blob
		inf_req_shared.SetBlob(network.getOutputsInfo().begin()->first, shared_output_blob);


		inf_req_shared.Infer();

		// Copy the output data back to the host
		//将shared_output_buffer内的数据读到D[]中
		_queue.enqueueReadBuffer(shared_output_buffer, CL_TRUE, 0, sizeof(float) * 1000, D);

		//这里只打印每个class的confidence大于0.0001的class的信息
		for (int i = 0; i < 1000; i++)
		{
			if (D[i] > 0.0001)
			{
				cout << "C[" << i << "] = " << C[i] << " - D[" << i << "] = " << D[i] << endl;
			}
		}

		//输出数据在调用Infer()的时候已经放到shared_output_blob里了, 所以不需要通过调用GetBlob获得输出数据
		//auto outputBlob_shared = inf_req_shared.GetBlob(network.getOutputsInfo().begin()->first);

		free(ImageBuffer);
		if (C != NULL)
		{
			delete C;
		}
		if (D != NULL)
		{
			delete D;
		}

		//cout << "Processing output shared blobs" << endl;
		//ClassificationResult classificationResult_shared(outputBlob_shared, validImageNames,
		//	batchSize, FLAGS_nt,
		//	labels);
		//classificationResult_shared.print();

 

运行程序,可以得到结果

输出数据放在cl::Buffer shared_output_blob->shared_output_buffer中,这里偷懒了, 没有做输出数据的排序,而是输出了每个class分类confidence>0.0001的所有类的index和confidence

可以看到

top 1是 D[817] = 0.6386719

top 2是 D[479] = 0.2141113

top 3是 D[511] = 0.1010742

和前半部分的GPU标准推理流程的输出一致。

classid probability label
------- ----------- -----
817     0.6386719   sports car, sport car
479     0.2141113   car wheel
511     0.1010742   convertible
436     0.0251923   beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon
751     0.0065804   racer, race car, racing car
656     0.0047264   minivan
717     0.0031071   pickup, pickup truck
581     0.0026569   grille, radiator grille
468     0.0014210   cab, hack, taxi, taxicab
661     0.0010548   Model T

 

整个流程的实现顺序如图

个人感受:

Remote Blob API主要用在OpenVINO GPU推理与其他GPU加速代码的集成这种场景。在OpenCL Kernel Execution on a Shared Buffer的例子里,OCL的context是从OpenVINO的ExecutableNetwork里获取的,所以必须要先创建OV的对象,再在这个基础上添加运行自己的OCL的处理代码,个人感觉比较适合从零开始开发自己的AI推理项目,或者只用OCL做推理前后数据的预处理和后处理的评估预研这种场景。如果要是把OpenVINO集成到自己已经有的OpenCL项目里,可能就需要官网文档里提到的另一种方法(Running GPU Plugin Inference within User-Supplied Shared Context)了。

PS: 因为我不是很懂OCL的开发, 所以以上为纯个人感受,欢迎拍砖 :P

 

最后完整项目奉上,仅供参考

https://gitee.com/tisandman/cl_ov_sharing

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值