本文仅介绍基础推理流程如需进一步进行复杂开发请自行翻阅接口说明目录一、 Vdevice 相关1.1 创建 Device 设备1.2 创建模型二、InferModel 相关2.1 配置模型为可推理对象2.2 获取模型基本信息三、ConfiguredInferModel 相关3.1 绑定输入输出缓冲区并执行推理四、 自定义补充函数五、写在后面的话一、 Vdevice 相关1.1 创建 Device 设备注意如果只有一张 Hailo 算力卡是不支持一个进程内创建多个 Vdevice 设备的hailo_vdevice_params_t params{}; hailo_init_vdevice_params(params); std::shared_ptrhailort::VDevice vdevice_ hailort::VDevice::create_shared(params).expect(Failed to create VDevice);如果有多个模型或者需要多线程并行一个模型也只需要调用一次上述命令然后每个模型的参数保持独立即可因此可以采用单例模式封装 Vdevice #include hailo/hailort.hpp using namespace hailort; class HailoSharedDevice { public: static HailoSharedDevice instance() { static HailoSharedDevice inst; return inst; } std::shared_ptrVDevice get_device() { std::lock_guardstd::mutex lock(mtx_); if (!vdevice_) { hailo_vdevice_params_t params{}; hailo_init_vdevice_params(params); params.group_id hailo_shared_group; // 用于多进程共享同一个虚拟设备 params.scheduling_algorithm HAILO_SCHEDULING_ALGORITHM_ROUND_ROBIN; vdevice_ VDevice::create(params).expect(Failed to create VDevice); } return vdevice_; } private: HailoSharedDevice() default; std::shared_ptrhailort::VDevice vdevice_; std::mutex mtx_; };补充 Device Create 接口说明接口说明参数返回create(params)创建虚拟设备独占指针vdevice参数unique_ptrcreate_shared(params)创建虚拟设备共享指针vdevice参数shared_ptrcreate()使用默认参数创建无unique_ptrcreate_shared()使用默认参数创建无shared_ptrcreate(device_ids)指定物理设备创建设备ID列表unique_ptrcreate_shared(device_ids)指定物理设备创建设备ID列表shared_ptr1.2 创建模型std::string model_path /test.hef std::shared_ptrhailort::InferModel infer_model_ vdevice_-create_infer_model(model_path).expect(Failed to create infer model);补充 Device Create_infer_model 接口说明接口说明参数返回create_infer_model(hef_path)从文件创建模型HEF路径InferModelcreate_infer_model(buffer)从内存创建模型bufferInferModelcreate_infer_model(Hef)从 Hef 对象创建模型Hef对象InferModel二、InferModel 相关2.1 配置模型为可推理对象只有通过 configure 配置得到的ConfiguredInferModel 才是真正用于执行推理的对象infer_model_-set_batch_size(1); std::unique_ptrhailort::ConfiguredInferModel configured_infer_model_ std::make_uniqueConfiguredInferModel( infer_model_-configure().expect(Failed to create configured infer model) );补充 InferModel 配置相关接口说明接口说明set_batch_size()batch大小configure()创建 ConfiguredInferModel2.2 获取模型基本信息获取模型的基本信息主要是方面后续将模型信息打印出来方便调试与排查, 代码中时用了 hailo_vstream_info_t结构体存储获得的信息如果没有抽象的考虑可以跳过此步骤hailo_vstream_info_t input_attrs_{}; std::vectorhailo_vstream_info_t output_attrs_{}; // 仅考虑单输入 auto input_stream infer_model_-input().value(); memset(input_attrs_, 0, sizeof(hailo_vstream_info_t)); strncpy(input_attrs_.name, input_stream.name().c_str(), sizeof(input_attrs_.name)-1); input_attrs_.format input_stream.format(); input_attrs_.shape input_stream.shape(); output_attrs_.clear(); for (auto output_stream : infer_model_-outputs()) { hailo_vstream_info_t info{}; strncpy(info.name, output_stream.name().c_str(), sizeof(info.name)-1); info.format output_stream.format(); info.shape output_stream.shape(); info.quant_info output_stream.get_quant_infos().empty() ? hailo_quant_info_t{} : output_stream.get_quant_infos()[0]; output_attrs_.push_back(info); }补充1 获取输入/输出 相关接口说明接口说明返回input()获取单个输入 StreamInferStreaminput(const std::string name)获取指定 Name 的输入 StreamInferStreaminputs()获取所有输入 Streamstd::vectoroutput()获取单个输出 StreamInferStreamoutput(const std::string name)获取指定 Name 的输出 StreamInferStreamoutputs()获取所有输出 Streamstd::vector补充2 InferStream 获取信息相关接口接口说明返回name()获取当前 Stream 的 namestd::stringshape()获取当前 Stream 的 shapehailo_3d_image_shape_tformat()获取当前 Stream 的 formathailo_format_tget_frame_szie获取当前 Stream 的 sizeIsize_tget_quant_infos获取当前 Stream 的 量化信息std::vector三、ConfiguredInferModel 相关3.1 绑定输入输出缓冲区并执行推理如前文所属ConfiguredInferModel 是真正用于执行推理的对象所以真正的输入输出绑定都是在 ConfiguredInferModel 上进行的std::unique_ptrhailort::ConfiguredInferModel::Bindings bindings_{} std::make_uniqueConfiguredInferModel::Bindings( configured_infer_model_-create_bindings().expect(Failed to set bindings) ); // 设置输入输出到推理缓冲区 // 仅以单输入为例 假设 input_va 是推理数据存放地址 auto input_name input_attrs_.name; size_t input_size infer_model_-input(input_name)-get_frame_size(); auto status bindings_-input(input_name)-set_buffer(MemoryView(input_va, input_size)); // 设置输出缓冲区 for (auto attr : output_attrs_) { auto name attr.name; size_t sz infer_model_-output(name)-get_frame_size(); auto buf page_aligned_alloc(sz); bindings_-output(name)-set_buffer(MemoryView(buf.get(), sz)); output_buffers_.push_back(buf); output_ptrs_.push_back(buf.get()); } // page_aligned_alloc 页对齐辅助函数 static std::shared_ptruint8_t page_aligned_alloc(size_t size) { auto addr mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (addr MAP_FAILED) throw std::bad_alloc(); return std::shared_ptruint8_t( reinterpret_castuint8_t*(addr), [size](uint8_t* p) { munmap(p, size); } ); } // 推理 auto status configured_infer_model_-run(*bindings_, std::chrono::milliseconds(1000));补充1 ConfiguredInferModel 相关接口说明接口说明返回creata_bindings()创建一个空的 Bindings 对象用于绑定推理的输入输出缓冲区Bindingsrun(const Bindings bindings,std::chrono::millseconds timeout)执行同步阻塞推理调用后会等待推理完成再返回hailo_statusrun_async(const Bindings bindings, std::function callback)执行单次异步非阻塞推理推理完成后通过回调通知hailo_statusrun_async(const Bindings bindings, ...)执行批量异步推理一次性提交多个推理任务hailo_status补充2 Bindings 相关接口说明接口说明返回input()获取模型单个输入对应的 InferStream 对象InferStreaminput(const std::string name)根据流名称获取指定输入流的 InferStream 对象InferStreamoutput()获取模型单个输出对应的 InferStream 对象InferStreamoutput(const std::string name)根据流名称获取指定输出流的 InferStream 对象InferStream补充3 InferStream 相关接口说明需要说明的是虽然 ConfiguredInferModel 也有 InferStream 但是和 InferModel 里面的 InferStream 没有啥关系接口说明返回set_buffer(MemoryView view)为当前输入 / 输出流设置普通内存缓冲区推荐缓冲区按系统页对齐以避免内存拷贝、提升性能hailo_statusget_buffer()获取当前流绑定的普通内存缓冲区MemoryViewset_pix_buffer(const hailo_pix_buffer_t pix_buffer)为输入流设置图像格式缓冲区hailo_statusget_pix_buffer()获取当前流绑定的图像格式缓冲区hailo_pix_buffer_t四、 自定义补充函数为了方便终端输出信息自己定义了打印输出函数如下PrintTensorAttr(uint32_t index, hailo_vstream_info_t *attr) { std::cout index index , name attr-name , dims[ attr-shape.height , attr-shape.width , attr-shape.features ], fmt FormatOrderToStr(attr-format.order) , type FormatTypeToStr(attr-format.type) , zp attr-quant_info.qp_zp , scale attr-quant_info.qp_scale std::endl; } static const char* FormatTypeToStr(hailo_format_type_t type) { switch (type) { case HAILO_FORMAT_TYPE_AUTO: return AUTO; case HAILO_FORMAT_TYPE_UINT8: return UINT8; case HAILO_FORMAT_TYPE_UINT16: return UINT16; case HAILO_FORMAT_TYPE_FLOAT32: return FLOAT32; default: return UNKNOWN; } } static const char* FormatOrderToStr(hailo_format_order_t order) { switch (order) { case HAILO_FORMAT_ORDER_AUTO: return AUTO; case HAILO_FORMAT_ORDER_NHWC: return NHWC; case HAILO_FORMAT_ORDER_NHCW: return NHCW; case HAILO_FORMAT_ORDER_FCR: return FCR; case HAILO_FORMAT_ORDER_F8CR: return F8CR; case HAILO_FORMAT_ORDER_NHW: return NHW; case HAILO_FORMAT_ORDER_NC: return NC; case HAILO_FORMAT_ORDER_BAYER_RGB: return BAYER_RGB; case HAILO_FORMAT_ORDER_12_BIT_BAYER_RGB: return 12BIT_BAYER_RGB; case HAILO_FORMAT_ORDER_HAILO_NMS: return DEPRECATED_NMS; case HAILO_FORMAT_ORDER_RGB888: return RGB888; case HAILO_FORMAT_ORDER_NCHW: return NCHW; case HAILO_FORMAT_ORDER_YUY2: return YUY2; case HAILO_FORMAT_ORDER_NV12: return NV12; case HAILO_FORMAT_ORDER_NV21: return NV21; case HAILO_FORMAT_ORDER_HAILO_YYUV: return HAILO_YYUV; case HAILO_FORMAT_ORDER_HAILO_YYVU: return HAILO_YYVU; case HAILO_FORMAT_ORDER_RGB4: return RGB4; case HAILO_FORMAT_ORDER_I420: return I420; case HAILO_FORMAT_ORDER_HAILO_YYYYUV: return HAILO_YYYYUV; case HAILO_FORMAT_ORDER_HAILO_NMS_WITH_BYTE_MASK: return NMS_WITH_BYTE_MASK; case HAILO_FORMAT_ORDER_HAILO_NMS_ON_CHIP: return NMS_ON_CHIP; case HAILO_FORMAT_ORDER_HAILO_NMS_BY_CLASS: return NMS_BY_CLASS; case HAILO_FORMAT_ORDER_HAILO_NMS_BY_SCORE: return NMS_BY_SCORE; default: return UNKNOWN; } }打印函数输出如下五、写在后面的话如果仅仅只是用 Hailo 进行推理可以跳过这部分了 这部分主要是聊聊 Hailo 算力卡集成到嵌入式设备如 RKNN 上的部分心得。简单集成问题不大当需要在一个进程中灵活切换 RKNN、Hailo 算力卡进行推理并共用同一套前后处理流程代码就有必要了解清楚 RKNN、Hailo hef 模型的输入输出格式以方便抽象这也是为什么多此一举贴出 PrintTensorAttr 的原因方便直接观察模型的输入输出格式信息。废话不多说直接上核心部分模型输入前提RKNN、Hailo hef 模型均将归一化操作放在模型内部RKNN 输入格式默认 NHWCUINT8格式Hailo hef 输入格式NHWCUINT8格式模型输出前提RKNN、Hailo hef 模型均进行默认量化RKNN 输出格式 默认 NCHWINT8格式Hailo hef 输出格式NHWCUINT8格式主要注意模型的输出部分 INT8 和 UINT8 格式如果共用一套处理可能不会报错但是结果肯定会出现较大误差。