4、HETERO插件核心概念:设备优先级、负载分配、网络划分、同步与异步执行

HETERO插件是OpenVINO推理引擎中用于异构执行的调度核心。它并非简单地“把模型拆开分给多个设备”,而是通过一套可配置的机制,将模型按层(Layer)或按子图(Subgraph)划分到不同计算设备上,并协调它们之间的数据依赖与执行时序。理解设备优先级、负载分配、网络划分以及同步/异步执行这四大核心概念,是正确使用HETERO插件、避免性能倒退或推理错误的前提。本章将逐一拆解这些概念,并结合实际配置案例说明其工程含义。

HETERO插件核心概念关系图 输入模型 (IR / ONNX) 网络划分 按层/子图切分 设备优先级 列表顺序决定 负载分配 自动/手动指定 执行阶段 每个设备上的子图 同步执行 阻塞等待结果 异步执行 非阻塞 + 回调 结果汇总 合并输出张量 核心:划分决定“在哪算”,优先级决定“谁先挑”,负载分配决定“算多少”,同步/异步决定“怎么等”

上图展示了HETERO插件工作的整体逻辑链路:输入模型首先经过网络划分,将计算图拆分为多个子图;随后根据设备优先级列表,将子图依次分配给最合适的设备;负载分配则进一步细化每个设备上的计算量;最终通过同步或异步接口驱动执行。下面逐一深入。

4.1 设备优先级:决定“谁先挑”

核心原理

设备优先级是HETERO插件配置中最直观的入口。它通过一个以逗号分隔的设备列表字符串来定义,例如 "GPU,CPU" 或 "NPU,GPU,CPU"。列表的从左到右顺序代表了优先级从高到低。HETERO插件在划分网络时,会优先尝试将计算量最大的子图(或最耗时的层)分配给排在前面的设备。如果某个设备不支持某些算子(例如GPU不支持某些非标准激活函数),插件会自动将该算子“降级”到列表中的下一个设备。

优先级列表不是“硬性分配”,而是“软性偏好”。HETERO插件会结合算子支持矩阵和性能开销进行综合判断,最终分配可能与你写的顺序不完全一致,但总体趋势是“高优先级设备承担更多计算”。

判断方法

在实际工程中,判断优先级设置是否合理,不能只看设备名称,还要关注以下三点:

  • 设备能力边界:查阅OpenVINO对应版本的设备支持算子列表,确认哪些层可以放在GPU/NPU上,哪些必须回退到CPU。
  • 数据搬运开销:如果两个设备之间需要频繁交换中间张量,优先级顺序应尽量让数据依赖紧密的层放在同一设备上。
  • 内存带宽瓶颈:对于大分辨率输入,CPU与GPU之间的PCIe带宽可能成为瓶颈,此时即使GPU优先级高,也可能因为拷贝开销导致整体变慢。

案例拆解:YOLOv5s在GPU+CPU上的优先级调优

某安防项目使用YOLOv5s模型,目标设备为集成显卡(iGPU)与CPU。初始配置为 "GPU,CPU",但实测发现推理延迟为45ms,其中GPU占用率仅60%,CPU占用率却高达90%。分析日志发现,模型中的Focus层和SPP层在GPU上执行效率极低,导致大量时间花在GPU与CPU之间的张量拷贝上。

操作步骤:

  1. 使用 ov::query_model 接口查询各层在GPU上的支持情况,发现Focus层被标记为“不支持”,自动回退到CPU。
  2. 将优先级改为 "CPU,GPU",让CPU优先处理Focus层和SPP层,GPU处理卷积层。
  3. 重新测量,延迟降至28ms,GPU占用率提升至85%,CPU占用率降至40%。

复盘经验:优先级列表的顺序应基于实际算子性能画像,而非设备“名义上的强弱”。集成GPU与独立GPU的算子效率差异巨大,必须实测。

不要盲目将高性能设备放在第一位。如果该设备对模型中大量算子支持不佳,反而会引发频繁回退和拷贝,性能可能比单设备更差。

4.2 负载分配:从“自动”到“手动”的精细控制

核心原理

负载分配解决的是“每个设备具体承担多少计算”的问题。HETERO插件默认采用自动负载均衡策略,即根据设备的计算能力(通过基准测试获得)和算子耗时估算,自动决定每个子图的归属。但在某些场景下,自动策略并不理想,例如:

  • 模型中有少量层特别耗时,自动分配将它们放到了慢速设备上。
  • 两个设备性能接近,自动分配导致负载不均,一个设备空闲等待。

此时可以通过 ov::device::priorities 属性结合 ov::device::properties 进行手动干预,或者使用 ov::hint::model_priority 来影响分配权重。

操作流程

配置方式 API/属性 适用场景
自动负载均衡 仅设置 HETERO:GPU,CPU 设备性能差异大,且模型结构规整
手动指定层归属 ov::device::properties("HETERO", ov::device::priorities("GPU", "CPU")) + 使用 ov::pass::AssignToDevice 手动标记层 模型中有明显性能瓶颈层,需要强制放到特定设备
性能提示 ov::hint::performance_mode(ov::hint::PerformanceMode::LATENCY) 需要低延迟,希望HETERO优先考虑延迟而非吞吐

实战场景:分割模型UNet的负载不均问题

某医疗影像项目使用UNet模型,输入为512x512x3。自动分配后,GPU承担了80%的层,CPU承担20%,但整体延迟高达120ms。分析发现,CPU承担的是靠近输出的上采样层,这些层虽然计算量小,但需要等待GPU的编码器输出,导致流水线空闲。

解决方案:通过手动标记,将最后两个上采样层也强制放到GPU上,虽然增加了GPU的显存占用(约200MB),但消除了跨设备等待,延迟降至85ms。代价是GPU显存接近上限,后续无法同时运行多个实例。

负载分配的核心指标是“设备空闲率”。如果某个设备在推理过程中出现明显的空闲等待,说明负载分配不合理,需要手动调整。

4.3 网络划分:子图切分的粒度与策略

核心原理

网络划分是HETERO插件内部的核心算法。它将原始计算图(Graph)切分为多个子图(Subgraph),每个子图对应一个设备。划分算法主要考虑三个约束:

  1. 算子支持约束:某个设备不支持的算子必须划分到其他设备。
  2. 数据依赖约束:切分点只能选在数据依赖的边界上,不能切断有依赖关系的层。
  3. 性能开销约束:切分点越多,跨设备数据传输越频繁,开销越大。因此HETERO倾向于生成尽可能少的子图。

判断方法

要判断网络划分是否合理,可以启用OpenVINO的调试日志:

export OPENVINO_LOG_LEVEL=4
./your_app

日志中会输出类似 [HETERO] Assign layer "conv2d_3" to device "GPU" 的信息。通过分析这些日志,可以确认每个层被分配到了哪个设备,从而发现不合理的切分点。

案例拆解:BERT模型在NPU+CPU上的划分

某自然语言处理项目使用BERT-base模型,目标设备为NPU(神经网络处理单元)与CPU。NPU对Transformer结构支持良好,但Embedding层和LayerNorm层不支持。初始配置 "NPU,CPU" 后,发现NPU利用率仅50%,CPU利用率100%。

分析日志发现,HETERO将Embedding层和LayerNorm层划分给了CPU,但这两个层在BERT中位于模型头部和每个Transformer块内部,导致NPU与CPU之间需要频繁交换中间结果(每个Transformer块交换一次)。

优化方案:使用 ov::pass::AssignToDevice 手动将全部LayerNorm层强制放到CPU,但将Embedding层也放到CPU,同时将多个Transformer块合并为一个子图,减少切分点。最终NPU利用率提升至80%,延迟从15ms降至9ms。

网络划分的粒度并非越细越好。切分点过多会导致“通信开销”超过“计算收益”。经验法则是:每个子图至少包含5-10个连续层,除非有特殊原因。

4.4 同步与异步执行:控制流与资源利用

核心原理

HETERO插件本身不直接执行推理,而是将子图分发给底层设备插件(如GPU插件、CPU插件)。同步与异步执行是HETERO对外暴露的两种调用模式:

  • 同步执行:调用 infer() 方法,阻塞等待所有设备上的子图执行完毕,返回最终结果。简单但浪费设备并行能力。
  • 异步执行:调用 start_async() 方法,立即返回,通过回调或轮询获取结果。允许CPU在GPU执行时并行处理其他任务。

操作流程

异步执行的标准流程如下:

  1. 创建 ov::InferRequest 对象。
  2. 设置输入张量。
  3. 调用 request.start_async()。
  4. 调用 request.wait() 或注册回调 request.set_callback()。
  5. 获取输出张量。

实战场景:视频流处理中的异步流水线

某视频分析系统需要同时处理4路1080p视频流,每路使用YOLOv5s模型。如果使用同步执行,每路视频串行处理,总吞吐量仅为10 FPS。改为异步执行后,将4路视频的推理请求全部提交到HETERO,HETERO内部自动调度GPU和CPU并行处理不同子图,总吞吐量提升至25 FPS。

关键参数:

  • 使用 ov::InferRequest::set_callback 在GPU子图完成时立即启动下一帧的预处理。
  • 使用 ov::CompiledModel::create_infer_request() 创建多个请求对象,实现多路并行。
异步执行的核心收益是“隐藏延迟”。当GPU执行卷积时,CPU可以同时进行图像解码和预处理。但要注意线程安全,回调函数中不要直接操作共享数据。

4.5 常见误区与总结复盘

误区 后果 正确做法
认为优先级列表越靠前,设备承担越多就一定越快 设备不支持算子导致频繁回退,性能反而下降 先查询算子支持矩阵,再设置优先级
忽略数据拷贝开销 跨设备张量传输成为瓶颈 尽量将数据依赖紧密的层放在同一设备
同步执行与异步执行混用 资源竞争导致延迟抖动 统一使用异步模式,配合回调机制
手动划分时切分点过多 通信开销超过计算收益 保持子图粒度在5-10层以上

总结本章,HETERO插件的四大核心概念是一个有机整体:设备优先级定义了“偏好”,网络划分决定了“结构”,负载分配细化了“数量”,同步/异步控制了“节奏”。在实际项目中,建议按照“先自动配置跑通 → 分析日志定位瓶颈 → 手动调整优先级和划分 → 切换异步模式提升吞吐”的路径进行优化。每一步都需要结合具体的模型结构、设备特性和业务指标(延迟/吞吐/功耗)来综合判断,切忌生搬硬套。