前一阵尝试了一下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 blobauto inf_req_shared = executable_network.CreateInferRequest();// obtain the RemoteContext pointer from the executable network object//从ExecutableNetwork中获取cldnn的cl_contextauto 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::BlobBlob::Ptr shared_blob = gpu::make_shared_blob(network.getInputsInfo().begin()->second->getTensorDesc(), cldnn_context,shared_buffer);//将网络输入层的数据指针指向存有输入图像RGB值的Blobinf_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清0cl::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_blobBlob::Ptr shared_output_blob = gpu::make_shared_blob(network.getOutputsInfo().begin()->second->getTensorDesc(), cldnn_context,shared_output_buffer);//将网络输出层的数据指针指向shared_output_blobinf_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