3、核心概念:模型、设备、推理请求、张量、精度、延迟与吞吐量的基本定义

在正式使用 OpenVINO Runtime API 进行开发之前,我们必须先建立一套统一的术语体系。无论是调用 compile_model 还是执行 infer,背后都涉及对模型、设备、推理请求、张量、精度、延迟与吞吐量这几个核心概念的准确理解。这些概念不仅是 API 的参数和返回值,更是衡量系统性能、定位瓶颈、设计架构的基石。本章将逐一剖析这些概念的本质、它们在 OpenVINO 中的具体表现形式,以及它们之间的相互关系。

OpenVINO 核心概念关系图 模型 (Model) IR / ONNX / Paddle 设备 (Device) CPU / GPU / NPU 编译模型 (Compiled Model) compile_model() 产物 输入 绑定 推理请求 (InferRequest) 异步/同步执行单元 create_infer_request() 张量 (Tensor) 输入/输出数据容器 填充/读取 精度 (Precision) FP32/FP16/INT8 约束 延迟 (Latency) / 吞吐量 (Throughput) 性能评估与调优目标 测量 ● 模型描述计算图 ● 设备决定执行后端 ● 张量承载实际数据 ● 精度影响速度与准确率

3.1 模型与设备:从静态定义到运行时绑定

核心原理

模型(Model)在 OpenVINO 中是一个静态的计算图描述,它包含了网络的结构、各层的权重参数以及输入输出的元信息(如形状、精度)。它本身不具备执行能力,只是一个“蓝图”。模型可以来自 OpenVINO IR 格式(.xml + .bin)、ONNX 格式或 PaddlePaddle 格式。而设备(Device)则是指实际执行计算的硬件单元,OpenVINO 支持 CPU、GPU、NPU 以及组合设备(如“CPU_GPU”)。设备的选择直接决定了指令集、内存带宽和并行策略。

将模型与设备绑定是通过 ov::Core::compile_model() 完成的。这一步会触发模型优化、层融合、内核代码生成等编译流程,最终生成一个编译模型(CompiledModel)。编译模型是设备相关的,同一个模型在不同设备上编译会得到不同的内部表示。

判断方法或操作流程

在实际开发中,选择设备需要遵循以下判断流程:

  1. 确认可用设备:调用 core.available_devices 获取当前环境支持的设备列表。
  2. 评估模型特性:如果模型包含自定义算子,需确认设备是否支持;如果模型较大,需考虑设备内存容量。
  3. 性能测试:使用 benchmark_app 工具对候选设备进行快速基准测试,比较延迟和吞吐量。
  4. 动态形状支持:如果输入形状可变,需确认设备是否支持动态形状,否则需固定形状。

案例拆解:多设备部署策略

假设我们有一个边缘计算盒子,内含 Intel CPU 和集成 GPU。目标是在视频流分析场景中同时运行两个模型:一个用于目标检测(YOLOv8),一个用于图像分类(ResNet50)。

  • 初始现象:两个模型都放在 CPU 上,CPU 占用率 95%,GPU 空闲,整体帧率仅 15 FPS。
  • 分析判断:CPU 成为瓶颈,GPU 资源未利用。
  • 操作步骤:将 YOLOv8 编译到 GPU(compile_model("GPU")),ResNet50 保留在 CPU。同时为 GPU 设置 GPU_THROUGHPUT_STREAMS=2 以增加并行度。
  • 结果对比:调整后 CPU 占用率降至 60%,GPU 占用率 70%,整体帧率提升至 38 FPS。
  • 复盘经验:设备选择不是非此即彼,而是根据模型计算特性和设备能力进行合理分配。GPU 适合计算密集、并行度高的卷积层;CPU 适合逻辑复杂、小批量请求。
经验提醒:使用 ov::Core::set_property() 可以设置设备专属属性,例如 CPU 的线程数、GPU 的流数量。这些属性对性能影响显著,建议在部署前进行网格搜索调优。

3.2 推理请求与张量:数据流动的载体

核心原理

推理请求(InferRequest)是编译模型的具体执行实例。它负责管理输入输出的内存缓冲区、执行推理并返回结果。OpenVINO 提供同步(infer())和异步(start_async() + wait())两种模式。异步模式允许在等待推理完成的同时执行其他任务,是提高吞吐量的关键。

张量(Tensor)是 OpenVINO 中存储数据的基本单元,它封装了数据指针、形状(Shape)、精度(Precision)和布局(Layout)。张量可以指向用户分配的内存(零拷贝),也可以由 OpenVINO 内部管理。理解张量的内存布局(如 NCHW vs NHWC)对于避免不必要的数据拷贝至关重要。

判断方法或操作流程

正确使用推理请求和张量需要遵循以下步骤:

  1. 获取输入输出信息:通过 compiled_model.inputs 和 compiled_model.outputs 获取端口信息。
  2. 创建推理请求:调用 compiled_model.create_infer_request()。
  3. 填充输入张量:使用 request.get_input_tensor() 获取输入张量,然后通过 tensor.data() 获取可写指针,直接写入数据。
  4. 执行推理:同步调用 request.infer() 或异步调用 request.start_async()。
  5. 读取输出:通过 request.get_output_tensor() 获取结果张量,解析数据。

案例拆解:异步流水线优化

在实时视频处理中,单线程同步推理会导致帧率低下。我们设计一个双缓冲异步流水线:

  • 背景与约束:摄像头采集帧率为 30 FPS,单帧推理耗时 25ms,同步模式只能达到 20 FPS。
  • 关键操作:创建两个推理请求(Request A 和 Request B)。主循环中,先启动 Request A 的异步推理,同时采集下一帧并填充到 Request B 的输入张量,然后启动 Request B,再等待 Request A 完成并读取结果。如此交替。
  • 结果对比:通过重叠采集和推理时间,帧率提升至 28 FPS,接近理论极限。
  • 风险与代价:内存占用翻倍(两个请求的输入输出缓冲区),且需要处理帧顺序错乱的问题(通过时间戳对齐)。
风险说明:张量数据默认是行主序(row-major)存储。如果输入数据是列主序(如某些图像库的输出),必须进行转置,否则推理结果将完全错误。建议在数据预处理阶段统一布局。

3.3 精度、延迟与吞吐量:性能三角

核心原理

精度(Precision)指数据存储和计算的数值格式,常见的有 FP32(32位浮点)、FP16(16位浮点)、INT8(8位整数)以及 BF16(脑浮点)。降低精度可以显著减少内存带宽和计算量,但可能带来精度损失。OpenVINO 支持通过 ov::Core::compile_model() 的配置参数或模型优化工具(如 NNCF)进行量化。

延迟(Latency)是指从提交单个推理请求到获得结果所经过的时间,通常以毫秒(ms)为单位。它反映的是系统的响应速度,对实时交互场景至关重要。吞吐量(Throughput)是指单位时间内系统能处理的推理请求数量,通常以 FPS(帧/秒)或 inferences/sec 为单位。它反映的是系统的并行处理能力。

判断方法或操作流程

性能调优需要明确目标:

  • 延迟敏感型(如自动驾驶、工业控制):优先保证单次推理延迟最低,通常选择同步模式,并关闭 CPU 的线程池竞争。
  • 吞吐量优先型(如离线批量处理、视频分析):优先保证单位时间处理量最大,通常使用异步模式 + 多请求并行,并开启 PERFORMANCE_HINT="THROUGHPUT"。
  • 精度选择:先用 FP32 建立基线,然后尝试 FP16(通常无损),最后尝试 INT8(需验证准确率下降是否在可接受范围内)。

案例拆解:INT8 量化实战

我们有一个训练好的 BERT 文本分类模型,FP32 精度下在 CPU 上延迟为 12ms,吞吐量为 80 inferences/sec。目标是提升吞吐量。

  • 初始现象:CPU 占用率 100%,但吞吐量不达标。
  • 分析判断:模型计算量大,内存带宽受限。
  • 操作步骤:使用 NNCF 工具对模型进行 INT8 量化,校准数据集为 500 条验证样本。量化后模型大小从 400MB 降至 100MB。
  • 结果对比:INT8 模型延迟降至 5ms,吞吐量提升至 220 inferences/sec,准确率从 91.2% 降至 90.5%,下降 0.7% 在可接受范围内。
  • 复盘经验:INT8 量化并非总是无损,对于敏感模型(如关键点检测)可能需要混合精度(部分层保持 FP16)。量化后务必在完整验证集上评估。
核心要点:延迟和吞吐量往往相互制约。追求极低延迟时,系统会倾向于用更多资源快速响应单个请求;追求高吞吐量时,系统会通过批处理和流水线让所有硬件单元保持忙碌,但这会增加单个请求的排队时间。OpenVINO 的 PERFORMANCE_HINT 属性允许你向运行时声明优化目标,它会自动调整内部参数。

3.4 常见误区与总结复盘

误区一:混淆模型与编译模型

初学者常将 ov::Model 直接用于推理,这是错误的。模型只是静态结构,必须经过 compile_model() 生成设备可执行的编译模型。编译模型是设备特定的,不能跨设备复用。

误区二:忽视张量的生命周期

如果使用 request.get_input_tensor() 获取张量,并在推理完成后继续持有该张量,需要注意其内存可能被内部复用。正确的做法是在推理完成后立即处理输出数据,或者将数据拷贝到自己的缓冲区。

误区三:认为精度越低越好

INT8 虽然快,但并非所有算子都支持 INT8 加速。如果模型中包含大量不支持 INT8 的算子(如某些激活函数),实际加速效果会大打折扣。建议使用 OpenVINO 的 query_model 功能检查算子支持情况。

总结复盘表

概念 本质 关键 API 性能影响 常见错误
模型 静态计算图 ov::Core::read_model() 模型大小影响加载时间 直接对模型调用 infer
设备 硬件执行后端 compile_model(device) 决定计算峰值和内存带宽 忽略设备属性设置
推理请求 执行实例 create_infer_request() 异步模式提升吞吐量 同步阻塞主线程
张量 数据容器 get_input_tensor() 零拷贝可减少延迟 忽略内存布局
精度 数值格式 模型转换工具 低精度提升速度 盲目量化导致精度崩坏
延迟 单次响应时间 request.infer() 受批大小和排队影响 用吞吐量指标评估延迟
吞吐量 单位时间处理量 PERFORMANCE_HINT 受并行度影响 忽略 CPU 线程数设置