优化指南

简介

本文档的目的是让您在性能方面深入了解网络部署过程的每个步骤。

有关一般工作流程的信息,请参阅另见中的文档。有关示例推理引擎 API 片段,请参阅基于请求的 API 和“GetBlob”习语

深度学习推理引擎概述

深度学习推理引擎是 OpenVINO™ 工具套件的一部分。推理引擎通过提供统一、与设备无关的 API 来促进深度学习解决方案的部署。

下面是部署过程的三个主要步骤:

  1. 转换
    经过训练的模型从特定框架(如 TensorFlow*)或格式(如 ONNX*)转换为与框架无关的中间表示 (IR) 格式。
    • 性能流程:这是一个离线步骤,一般拓扑级优化会自动发生(参见与性能相关的模型优化器旋钮)。
    • 工具:OpenVINO™ 具有模型优化器,可实现从训练环境到部署环境的自动和无缝过渡。
  2. 模型推理/执行
    转换后,推理引擎消耗 IR 进行推理。虽然推理引擎 API 本身与目标无关,但在内部,它有一个插件的概念,这些插件是特定于设备的库,可促进硬件辅助加速。
    • 性能流程 : 转换为 IR 后,从现有的推理引擎样本开始执行,以测量和调整不同设备上的网络性能。
      > :在使用相同的 IR 时,每个插件在加载时执行额外、特定于设备的优化,因此结果的准确性可能会有所不同。此外,启用和优化自定义内核容易出错(请参阅优化自定义内核)。
    • 工具:除了样本报告的推理性能(请参阅延迟与吞吐量),您还可以使用推理引擎性能计数器英特尔® VTune™获得进一步的设备和内核级计时。
  3. 集成到产品
    模型推理经过样本验证之后,推理引擎代码通常集成到真实的应用程序或管道中。
    • 性能流程:最重要的一点是保持独立模型执行所达到的持续性能。与其他 API 结合时要采取预防措施,并仔细测试每个集成步骤的性能。
    • 工具:除了跟踪您应用程序的实际挂钟时间之外,请参阅英特尔® VTune™ 示例了解应用程序级和系统级信息。

收集性能数据

性能数据有多种形式。例如,最常见的性能指标之一是延迟,它表示完成一个工作单元所需的时间(例如,单个图像的推理时间)。在以下部分中,您将看到衡量性能的重要建议。

衡量正确的操作集合

在使用推理引擎评估模型的性能时,您必须衡量正确的操作集合。为此,请考虑以下提示:

  • 避免包括模型加载等一次性成本。例如,请参阅推理引擎样本
  • 单独跟踪推理引擎之外发生的操作,如视频解码。

:一些图像预处理可以被烘焙到 IR 并加速。欲知详情,请参阅与性能有关的模型优化器旋钮

延迟与吞吐量

在异步情况下(参阅基于请求的 API 和 “GetBlob” 习语),单个推理请求的性能往往不太受关注。相反,您通常异步执行多个请求,并通过将处理的图像数量除以处理时间来测量每秒图像的吞吐量。相比之下,对于面向延迟的任务,单帧的时间更为重要。

请参阅基准测试应用程序样本,该样本可以测量延迟与吞吐量。

基准测试应用程序样本还支持批处理,即自动将多个输入图像打包到单个请求中。但是,大批量会导致延迟损失。因此,对于更加面向实时的用途,通常使用低至单个输入的批量大小。尽管如此,诸如 CPU、英特尔® Movidius™ Myriad™ 2 视觉处理器、英特尔® Movidius™ Myriad™ X 视觉处理器或采用英特尔® Movidius™ 视觉处理器的英特尔® Vision Accelerator Design 等设备需要大量并行请求而不是批处理来利用性能。运行多个请求应与配置为相应数量流的设备相结合。请参阅 CPU 流详情查看示例。

OpenVINO™ 深度学习工作台工具 为不同数量的流、请求和批处理大小提供吞吐量与延迟图表,以找到性能最佳点。

将性能与原生/框架代码进行比较

在将推理引擎性能与框架或其他参考代码进行比较时,请确保两个版本尽可能相似:

  • 完全打包推理执行(请参阅基准测试应用程序样本以查看示例)。
  • 单独跟踪模型加载时间。
  • 确保推理引擎和框架的输入相同。例如,Caffe* 允许您使用随机值自动填充输入。请注意,它可能会提供与真实图像不同的性能。
  • 同样,为了实现正确的性能比较,请确保访问模式(例如输入布局)对于推理引擎(目前是 NCHW)是最佳的。
  • 任何用户端预处理都应单独跟踪。
  • 确保尝试框架开发人员推荐的相同环境设置,例如,用于 TensorFlow*。在许多情况下,对机器更友好的事情,比如尊重 NUMA(请参阅 CPU 清单),也可能适用于推理引擎。
  • 如果适用,请使用推理引擎进行批处理。
  • 如果可能,应要求达到相同的精度。例如,TensorFlow 允许 FP16 支持,因此在进行比较时,确保也使用 FP16 来测试推理引擎。

获得可信的性能数据

您需要根据可重现的数据建立性能结论。对同一例程的大量调用进行性能测量。由于第一次迭代几乎总是比后续迭代慢得多,因此您对于最终预测的执行时间可以使用聚合值:

  • 如果热身运行没有帮助或执行时间仍然变化,您可以尝试运行大量迭代,然后对结果求平均值。
  • 对于范围太大的时间值,请使用几何平均值。

请参阅基准测试应用程序查看性能测量的代码示例。几乎每个样本(迭代演示)除外,都有 -ni 选项来指定迭代数量。

与性能相关的模型优化器旋钮

网络训练通常在高端数据中心完成,使用流行的训练框架,如 Caffe*、TensorFlow* 和 MXNet*。模型优化器将原始专有格式的训练模型转换为描述拓扑的 IR。IR 伴随着一个带有权重的二进制文件。这些文件依次由推理引擎使用并用于评分。

模型优化器指南所述,该工具执行了许多与设备无关的优化。例如,某些基元(如线性运算(BatchNorm 和 ScaleShift))会自动融合到卷积中。通常,这些层不应出现在生成的 IR 中:

上图显示了 Caffe* Resnet269* 拓扑。左边的模型是原始模型,右边(转换后)是模型优化器生成的结果模型,其中 BatchNorm 和 ScaleShift 层融合到卷积权重中,而不是构成单独的层。

如果您仍然看到这些操作,请在搜索警告(例如工具无法融合)时,仔细检查模型优化器的输出。例如,卷积和线性操作之间的非线性操作(如激活)可能会阻止融合。如果性能受到关注,请尝试更改(并可能重新训练)拓扑。请参阅模型优化器指南以了解更多优化。

请注意,激活 (_relu) 不受模型优化器的影响,虽然它也可以合并到卷积中,但这是一种特定于设备的优化,在模型加载期间由推理引擎覆盖。我们鼓励您检查插件的性能计数器,这些计数器应表明这些特定层未执行(“优化”)。有关更多信息,请参阅内部推理性能计数器

此外:

  • 图像平均/扩展参数
    当您需要预处理时,确保使用模型优化器的输入图像平均/扩展参数(--scale–mean_values)。它可以让工具将预处理烘焙到 IR 中,以便推理引擎加速。
  • RGB 与 BGR 输入
    例如,如果您的网络采用 RGB 输入,则模型优化器可以使用 --reverse_input_channels 命令行选项交换第一个卷积中的通道,因此您无需在每次获取 BGR 图像时都将输入转换为 RGB,例如,从 OpenCV*。
  • 更大的批处理大小
    请注意,像 GPU 这样的设备在更大的批大小下表现更好。虽然可以使用推理引擎形状推理功能,在运行时中设置批大小。
  • 产生的 IR 精度
    产生的 IR 精度,例如,FP16FP32,会直接影响性能。由于 CPU 现在支持FP16(同时在内部升级到 FP32)并且因为这是 GPU 目标的最佳精度,所以您可能希望始终将模型转换为 FP16。请注意,这是英特尔® Movidius™ Myriad™ 2 和英特尔® Myriad™ X VPU 支持的唯一精度。

多设备执行

OpenVINO™ 工具套件支持自动多设备执行,请参阅多设备插件描述。在下一章中,您可以找到特定于设备的提示,而本节涵盖了针对多设备执行的一些建议:

  • 当在设备列表中首先指定最快的设备时,多设备通常表现最佳。当并行度不够时(例如,执行中的请求数量不足以使所有设备饱和),这一点尤其重要。
  • 强烈建议直接从 ExecutionNetwork 的实例中查询最佳推理请求数(由 LoadNetwork 调用产生,以特定的多设备配置为参数)。请参阅基准测试应用程序样本代码以了解详情。
  • 请注意,例如 CPU+GPU 的执行配合某些旋钮会表现更好,您可以在相同的基准测试应用程序样本代码中找到这些旋钮。一个具体的例子是禁用 GPU 驱动程序轮询,这反过来需要多个 GPU 流(这已经是 GPU 的默认设置)来分摊从设备到主机的较慢推理完成。
  • 多设备逻辑总是尝试节省在设备无关的、面向用户的推理请求与实际在幕后调度、特定于设备的“工作程序”请求之间的(例如,输入)数据副本。为了便于节省副本,建议按创建顺序启动请求(使用 ExecutableNetwork 的 CreateInferRequest)。

特定于设备的优化

推理引擎支持多种目标设备(CPU、GPU、英特尔® Movidius™ Myriad™ 2 VPU、英特尔® Movidius™ Myriad™ X VPU、采用英特尔® Movidius™ 视觉处理器 (VPU) 和 FPGA 的英特尔® Vision Accelerator Design),它们每个都有一个相应的插件。如果要优化特定设备,请记住以下提示以提高性能。

CPU 清单

CPU 插件完全依赖于面向深度神经网络的英特尔® 数学核心函数库(英特尔® MKL-DNN)来实现主要原语加速,例如卷积或全连接。

您可以从中获得的唯一提示是主要原语如何加速(并且您无法更改这一点)。例如,在采用英特尔® 酷睿™ 处理器的机器上,在检查内部推理性能计数器(以及 int8 推理的额外 '_int8' 后缀)时,您应当会看到 jit_avx2的变化。如果您是高级用户,则可以使用以下命令进一步跟踪 CPU 执行情况(请参阅英特尔® VTune™)。

在内部,推理引擎有一个线程抽象级别,可以使用现在默认的英特尔® 线程构建模块(英特尔® TBB)或 OpenMP*(作为替代并行解决方案)编译开源版本。在 CPU 上使用推理时,尤其重要的是将线程模型与应用程序的其余部分(以及您使用的任何第三方库)协调一致,以避免过度订阅。有关更多信息,请参阅应用程序级线程注释部分。

自 R1 2019 起,OpenVINO™ 工具套件使用英特尔 TBB 进行预编译,因此任何 OpenMP* API 或环境设置(如 OMP_NUM_THREADS) 没有影响。某些调整(例如 CPU 上用于推理的线程数)仍然可以通过 CPU 配置选项来实现。最后,OpenVINO CPU 推理是 NUMA 感知的,请参阅NUMA 系统推理提示部分。

其他一般建议:

  • 通常,批处理会提高 CPU 性能。但是,需要在批处理中收集帧可能会使应用程序逻辑复杂化。相反,您可以为每个摄像头或其他输入源保留一个单独的推理请求,并并行处理这些请求。有关更多信息,请参阅下一部分。
  • 如果您的应用程序在同一个 CPU 上同时执行多个模型的推理,请确保您没有超额订阅机器。请参阅同时运行多个请求的性能方面以了解更多信息。
  • 请注意,异构执行可能会隐式加载 CPU。有关详情,请参阅异构部分。
  • 可以考虑 CPU 上的 8 位整数推理

CPU 吞吐量模式

与大多数加速器不同,CPU 被视为本质上是面向延迟的设备。事实上,OpenVINO 确实支持 CPU 的“吞吐量”模式,这使得推理引擎可以高效地同时在 CPU 上运行多个推理请求,大大提高了整体吞吐量。

在内部,执行资源被拆分/固定到执行“流”中。此功能通常为网络提供比批处理更好的性能。对于多核服务器机器尤其如此:

与批处理相比,并行性有些转置(即在输入上执行,而在 CNN 操作中则少得多):

尝试使用基准测试应用程序样本,并操作并行运行的流的数量。根据经验,绑定到机器上的多个 CPU 内核。例如,在 8 核 CPU 上,将 -nstreams 1 (这是一个传统、面向延迟的场景)与 2、4 和 8 流进行比较。请注意,在多套接字机器上,延迟场景的最小流数等于套接字数。

此外,您可以使用批量大小来找到吞吐量的最佳点。

如果您的应用程序很难或不可能按照多请求逻辑进行更改,请考虑使用“多实例”技巧来提高吞吐量:

  • 对于多套接字执行,建议将 `KEY_CPU_THREADS_NUM` 设置为每个套接字的核心数,您有多少套接字,就运行多少个应用程序实例。
  • 同样,对于极其轻量级的网络(运行速度超过 1 毫秒)和/或多核机器(16 核以上),尝试将 CPU 推理线程的数量限制为仅 #‍phys 个核心,同时试图通过运行多个应用程序实例来使机器饱和。

GPU 清单

推理引擎依赖于深度神经网络的计算库 (clDNN),在英特尔® GPU 上实现卷积神经网络加速。在内部,clDNN 使用 OpenCL™ 来实施内核。因此,许多一般提示适用:

  • 优先使用 FP16 而非 FP32,因为模型优化器可以生成两种变体,而 FP32 是默认值。
  • 尝试使用批处理对单个推理作业进行分组。
  • 请注意,使用 GPU 会引入编译 OpenCL 内核的一次性开销(几秒的数量级)。编译发生在将网络加载到 GPU 插件时,不会影响推理时间。
  • 如果您的应用程序同时在 CPU 上使用推理或以其他方式重载主机,请确保 OpenCL 驱动程序线程不会饿死。您可以使用 CPU 配置选项限制 CPU 插件的推理线程数。
  • 在仅 GPU 的情况下,GPU 驱动程序可能会占用 CPU 内核,并通过自旋循环轮询来完成。如果担心 CPU 利用率,可以考虑 KEY_CLDND_PLUGIN_THROTTLE 配置选项。

:参阅基准测试应用程序样本代码查看用法示例。

请注意,在禁用轮询时,此选项可能会降低 GPU 性能,因此通常此选项与多个 GPU 流一起使用。

英特尔® Movidius™ Myriad™ X 视觉处理单元和采用英特尔® Movidius™ VPU 的英特尔® Vision Accelerator Design

由于英特尔® Movidius™ Myriad™ X 视觉处理单元(英特尔® Movidius™ Myriad™ 2 VPU)通过 USB 与主机通信,因此建议在运行中至少有四个推理请求以隐藏数据传输成本。请参阅基于请求的 API 和 “GetBlob” 习语基准测试应用程序样本以了解更多信息。

采用英特尔® Movidius™ VPU 的英特尔® Vision Accelerator Design 需要至少保持运行 32 个推理请求,才能使设备完全饱和。

FPGA

下面列出了有效使用 FPGA 的最重要提示:

  • 就像英特尔® Movidius™ Myriad™ VPU 风格一样,对于 FPGA,通过并行运行多个推理请求来隐藏通信开销是非常重要的。例如,请参阅基准测试应用程序样本
  • 由于使用 FPGA 的第一次推理迭代总是明显慢于后续迭代,因此请确保运行多次迭代(除基于 GUI 的演示外,所有示例都具有 -ni或 'niter' 选项来做到这一点)。
  • FPGA 性能在很大程度上取决于比特流。
  • 每个可执行网络的推理请求数量限制为五个,因此“通道”并行性(保持每个摄像头/视频输入的单个推理请求)在超过五个输入时不起作用。相反,您需要将输入多路复用到某个队列中,该队列将在内部使用一个有 (5) 个请求的池。
  • 在大多数场景中,通过异构执行来利用 FPGA 加速,并提供进一步的特定提示。
  • 对于多设备 FPGA 执行,请参阅 FPGA 插件文档

异构

异构执行(由专用推理引擎“异构” 插件构成)能够安排对多个设备的网络推理。

值得关注的典型异构场景

在异构模式下执行网络的要点如下:

  • 使用加速器计算网络中最重的部分,同时对于加速器不支持的层,回退到 CPU。
    当某些自定义(用户)内核仅为 CPU 实施(并且为加速器实施则更难甚至不可能)时,这尤其有用。
  • 更有效地使用所有可用的计算设备,例如,通过在不同设备上运行网络分支。

异构流程

通过异构插件的执行分为三个不同的步骤:

  1. 为各层应用亲和性设置,即,将其绑定到设备。
    • 这可以使用回退优先事项或者在每层的基础上,自动完成。
    • 将网络加载到(异构)插件之前,进行亲和性设置,因此对于执行而言,这始终是静态设置。
  2. 将网络加载到异构插件,在内部将网络拆分为子图。
    您可以检查插件所做的决定,请参阅分析异构执行
  3. 执行推理请求。从用户的角度来看,这看起来与单设备情况相同,而在内部,子图由实际的插件/设备执行。

异构执行的性能优势在很大程度上取决于设备之间的通信粒度。如果将数据从一个部分设备传输/转换到另一部分设备需要比执行更多的时间,则异构方法几乎没有意义或没有意义。使用英特尔® VTune™ 有助于在时间线上可视化执行流程(参阅英特尔® VTune™ 示例)。

同样,如果子图太多,同步和数据传输可能会消耗全部性能。在某些情况下,您可以手动定义(较粗略)亲和性,以避免在一次推理过程中多次来回发送数据。

亲和性的一般经验法则是将计算密集型内核保留在加速器上,并将“粘合”或辅助内核保留在 CPU 上。请注意,这包括粒度考虑。例如,在 CPU 上运行一些自定义激活(在每个配备加速器的卷积之后)可能会由于过多的数据类型和/或布局转换而导致性能下降,即使激活本身可能非常快。在这种情况下,考虑为加速器实施内核可能是合情合理的(参阅优化自定义内核)。转换通常表现为出色的(与仅 CPU 执行相比)“重新排序”条目(参阅内部推理性能计数器)。

有关异构插件的一般详情,请参阅推理引擎开发人员指南中的相应部分

使用推理引擎样本尝试异构插件

每个推理引擎样本支持 -d(设备)选项。

例如,以下是运行 对象检测样本 SSD 样本的命令:

./object_detection_sample_ssd -m <path_to_model>/ModelSSD.xml -i <path_to_pictures>/picture.jpg -d HETERO:FPGA,CPU

在此:

  • HETERO 代表异构插件。
  • FPGA,CPU指向回退策略,优先考虑 FPGA,并进一步回退到 CPU。

-d HETERO:FPGA,GPU,CPU您可以指向两个以上的设备:

带有 FPGA 的异构场景

由于 FPGA 被视为推理加速器,因此大多数性能问题都与以下事实有关:由于回退,仍然可以大量使用 CPU。

  • 然而在大多数情况下,CPU 只做小型/轻量层,例如后处理(SoftMax在大多数分类模型或DetectionOutput在基于 SSD* 的拓扑中)。在这种情况下,通过 `KEY_CPU_THREADS_NUM` 配置限制 CPU 线程的数量可以进一步降低 CPU 利用率,而不会大幅降低整体性能。
  • 此外,如果您仍在使用早于 R1 2019 的 OpenVINO™ 工具套件版本,或者您已使用 OpenMP 重新编译推理引擎(例如为了向后兼容),请将 KMP_BLOCKTIME环境变量设置为小于默认 200 毫秒(我们建议 1 毫秒)则特别有用。如果 CPU 子图很小,则使用 KMP_BLOCKTIME=0

:一般线程提示(参阅应用程序级线程注释)可以很好地应用,即使整个拓扑适合 FPGA,因为仍然存在用于数据预处理和后处理的主机侧代码。

GPU/CPU 执行的一般提示

以下提示旨在为优化 GPU/CPU 设备上的执行提供一般指导。

  • 通常,GPU 性能在重核(如卷积)和大输入上更好。因此,如果网络推理时间已经太小(约 1 毫秒的执行时间),则使用 GPU 不太可能带来提升。
  • 一个典型的策略是首先测试仅 CPU 和仅 GPU 的场景(使用样本,这是简单 -d CPU-d GPU)。如果有 GPU 不支持的特定内核,最好的选择是 HETERO:GPU,CPU,自动应用默认拆分(基于插件层支持)。然后,您可以使用手动亲和性设置(例如,进一步减少子图的数量)。
  • 亲和性的一般经验法则是将计算密集型内核保留在加速器上,并将(辅助)内核“粘合”在 CPU 上。请注意,这包括粒度考虑。例如,在 CPU 上运行一些(自定义)激活会导致转换过多。
  • 建议进行性能分析以确定“热点”内核,这应该是分流的第一候选。同时,分流一些合理大小的内核序列而不是单个内核通常更有效,从而最大限度地减少调度和其他运行时开销。
  • 请注意,GPU 可能忙于其他任务(如渲染)。同样,CPU 可以负责一般的 OS 例程和其他应用程序线程(参阅应用程序级线程注释)。此外,由于许多子图导致的高中断率可以提高设备的频率并降低另一个设备的频率。
  • 动态频率缩放会影响设备性能。例如,同时在两台设备上运行长内核可能最终会导致一台或两台设备停止使用英特尔® 睿频加速技术。即使与单设备场景相比,这也可能导致整体性能下降。
  • FP16(GPU) 和 FP32(CPU) 执行进行混合会导致转换,以及性能问题。如果您看到大量未完成的重排序(与仅 CPU 执行相比),请考虑实施实际的 GPU 内核。请参阅内部推理性能计数器以了解更多信息。

分析异构执行

有一个专用的配置选项,可以转储由异构插件创建的子图的可视化,请查看异构插件文档中的代码示例。

启用配置键后,异构插件生成两个文件:

  • hetero_affinity.dot- 每层亲和性。只有在执行默认回退策略时才会生成此文件(否则您自己设置了亲和性,因此您了解它们)。
  • hetero_subgraphs.dot- 每个子图的亲和性。在异构流程的 Core::LoadNetwork 执行期间,此文件写入磁盘。

您可以使用 GraphViz* 实用程序或 .dot 转换器(例如,至 .png.pdf),例如 xdot*,可以在带有 sudo apt-get install xdot 的 Linux* 操作系统上可用。下面是修剪到最后两层的输出示例(一个在 FPGA 上执行,另一个在 CPU 上执行):

您也可以使用性能数据(在基准测试应用程序中,是一个选项 -pc)获取每个子图的性能数据。另外,参阅异构插件文档内部推理性能计数器以了解一般计数器信息。

优化自定义内核

一些初始性能考虑因素

推理引擎支持 CPU、GPU 和 VPU 自定义内核。通常,自定义内核用于快速实施新拓扑的缺失层。您不应覆盖标准层实施,尤其是在关键路径上,例如卷积。此外,覆盖现有层可以禁用一些现有的性能优化,例如融合。

通常更容易从 CPU 扩展开始,并在使用 CPU 路径进行调试后切换到 GPU。有时,当自定义层位于管道的最末端时,将它们作为常规后处理在应用程序中实施更容易,而无需将它们包装为内核。对于不适合 GPU 的内核尤其如此,例如输出边界框排序。在许多情况下,您可以在 CPU 上进行此类后处理。

在很多情况下,自定义内核的序列可以作为“超级”内核来实施,从而节省数据访问。

最后,通过异构执行,可以使用加速器执行绝大多数密集计算并将自定义部分保留在 CPU 上。需要权衡不同设备之间通信的粒度/成本。

有关推理引擎中自定义层的更多详细信息,请参阅推理引擎扩展性机制

了解自定义内核的性能贡献

在大多数情况下,在实际为内核实施完整代码之前,您可以通过执行一个什么都不做(因此“无限”快)的简单存根内核来估计最终性能,只是为了让拓扑端到端地执行。当然,只有当内核输出不影响性能时,估计才有效,例如,如果输出没有驱动任何分支或循环。

除此之外,在实施内核时,您可以尝试前一章中的方法来了解实际贡献,如果有任何自定义内核在热点中,则对其进行优化。

几条特定于设备的提示

  • 正如在 CPU 清单中所述,将您在 CPU 内核中使用的线程模型与推理引擎其余部分一起编译的模型调整一致。
  • 对于 CPU 扩展,如果您的内核在热点中,请考虑支持阻塞布局的内核风格(参阅内部推理性能计数器)。由于英特尔 MKL-DNN 在内部对阻塞的布局进行操作,这将为您节省内核张量输入/输出的数据打包(重新排序)。有关阻塞布局支持的示例,请参阅 <OPENVINO_INSTALL_DIR>/deployment_tools/samples/extension/ 目录中的扩展。

将推理引擎插入应用程序

在 NUMA 系统上进行推理的提示

对于 CPU 上的推理,有多个线程绑定选项,请参阅 CPU 配置选项

  • '是'(默认)绑定选项将线程映射到内核,最适用于基准测试等静态/合成场景。它仅使用可用内核(通过进程掩码确定)并以循环方式绑定线程。
  • 'NUMA' 绑定可能在现实场景中表现更好,为操作系统调度留出更多空间。主要的预期用途是角逐场景,例如,在一台机器上同时执行多个 (CPU) 推理繁重的应用程序。

如果您正在使用 GStreamer* 等第三方组件构建应用级管道,则 NUMA 机器的一般指南如下:

  • 只要有可能,每个 NUMA 节点至少使用一个管道实例:
    • 整个管道实例固定到最外级别的特定 NUMA 节点(例如,使用适当设置的 Kubernetes* 和/或 numactl 命令,在使用实际的 GStreamer 命令。)
    • 禁用管道组件的任何单个固定(例如,将 CPU_BIND_THREADS 设置为 'NO')。
    • 根据推理线程的数量,限制每个实例。使用 CPU_THREADS_NUM 或其他方式(例如虚拟化、Kubernetes* 等)以避免过度订阅。
  • 如果整个管道的固定实例化/固定是不可能的或不可取的,请将推理线程固定为“NUMA”。
    • 与默认将线程固定到内核相比,这样限制较少,但避免了 NUMA 惩罚。

应用级线程注释

  • CPU 清单部分所述,默认情况下,推理引擎使用英特尔 TBB 作为并行引擎。因此,任何 OpenVINO 内部线程(包括 CPU 推理)都使用由 TBB 提供的相同线程池。但是您的应用程序中还有其他线程,因此在应用程序级别可能会出现超额订阅:
  • 根据经验,您应该尝试使应用程序中的活动线程总数等于机器中的内核数。请记住,GPU 插件下的 OpenCL 驱动程序可能还需要备用内核。
  • 一种限制推理引擎线程数的特定解决方法是使用 CPU 配置选项
  • 为避免进一步超额订阅,请在您的应用程序使用的所有模块/库中使用相同的线程模型。请注意,第三方组件可能会带来自己的线程。例如,使用现在默认使用 TBB 编译的推理引擎,当在同一个应用程序中与另一个计算密集型库(但使用 OpenMP 编译)混合使用时,可能会导致性能问题。您也可以尝试编译推理引擎的开源版本以使用 OpenMP。但请注意,一般而言,与其他线程解决方案相比,TBB 提供了更好的可组合性。
  • 如果您的代码(或第三方库)使用 GNU OpenMP,则必须首先初始化英特尔® OpenMP(如果您使用它重新编译了推理引擎)。这可以通过将您的应用程序与英特尔 OpenMP 而不是 GNU OpenMP 相关联来实现,或者在 Linux* 操作系统上使用 LD_PRELOAD

让推理引擎加速图像预处理/转换

在许多情况下,网络需要经过预处理的图像,因此请确保不要在代码中执行不必要的步骤:

  • 模型优化器可以有效地将均值和归一化(尺度)值烘焙到模型中(例如,第一个卷积的权重)。请参阅与性能相关的模型优化器旋钮
  • 如果每通道常规 8 位图像是您的原生媒体(例如,解码帧),请不要在您那一边转换为FP32,因为这是插件可以加速的东西。使用 InferenceEngine::Precision::U8 作为您的输入格式:
Core ie;
auto netReader = ie.ReadNetwork("sample.xml");
InferenceEngine::InputsDataMap info(netReader.getInputsInfo());
auto& inputInfoFirst = info.begin()->second;
for (auto& it : info) {
it.second->setPrecision(Precision::U8);
}
std::map< std::string, InputInfo::Ptr > InputsDataMap
A collection that contains string as key, and InputInfo smart pointer as value.
Definition: ie_input_info.hpp:172

请注意,在许多情况下,您可以直接与推理引擎共享(输入)数据。

与其他 API 的基本互操作性

在推理引擎和媒体/图形 API(如英特尔® Media Server Studio(英特尔® MSS)) 之间共享数据的一般方法是基于共享系统内存。也就是说,在您的代码中,您应该首先将数据从 API 映射或复制到 CPU 地址空间。

对于英特尔® Media SDK,建议执行可行的预处理,例如,使用视频处理程序 (VPP)裁剪/调整大小,然后再转换为 RGB。然后锁定结果并在其上创建推理引擎 blob。生成的指针可用于SetBlob

//Lock Intel MSS surface
mfxFrameSurface1 *frame_in; //Input MSS surface.
mfxFrameAllocator* pAlloc = &m_mfxCore.FrameAllocator();
pAlloc->Lock(pAlloc->pthis, frame_in->Data.MemId, &frame_in->Data);
//Inference Engine code

使用 InferenceEngine::NHWC 布局:

1 /* batch, N*/,
(size_t) frame_in->Info.Height /* Height */,
(size_t) frame_in->Info.Width /* Width */,
3 /*Channels,*/,
};
/* wrapping the surface data, as RGB is interleaved, need to pass only ptr to the R, notice that this wouldn’t work with planar formats as these are 3 separate planes/pointers*/
InferenceEngine::TBlob<uint8_t>::Ptr p = InferenceEngine::make_shared_blob<uint8_t>( desc, (uint8_t*) frame_in->Data.R);
inferRequest.SetBlob("input", p);
inferRequest.Infer();
//Make sure to unlock the surface upon inference completion, to return the ownership back to the Intel MSS
pAlloc->Unlock(pAlloc->pthis, frame_in->Data.MemId, &frame_in->Data);
@ U8
Definition: ie_precision.hpp:37
std::shared_ptr< TBlob< T > > Ptr
Smart Pointer to this TBlob object.
Definition: ie_blob.h:499
This class defines Tensor description.
Definition: ie_layouts.h:158
@ NHWC
NHWC layout for input / output blobs.
Definition: ie_common.h:75
std::vector< size_t > SizeVector
Represents tensor size.
Definition: ie_common.h:34

或者,您可以使用英特尔® Media SDK 的 RGBP(平面 RGB)输出。这允许您将(锁定的)结果包装为常规 NCHW。然后可以配合 SetBlob 使用,就像上一个示例一样:

1 /* batch, N*/,
3 /*Channels,*/,
(size_t) frame_in->Info.Height /* Height */,
(size_t) frame_in->Info.Width /* Width */,
};
/* wrapping the RGBP surface data*/
InferenceEngine::TBlob<uint8_t>::Ptr p = InferenceEngine::make_shared_blob<uint8_t>( desc, (uint8_t*) frame_in->Data.R);
inferRequest.SetBlob("input", p);
// …
@ NCHW
NCHW layout for input / output blobs.
Definition: ie_common.h:74

OpenCV* 互操作示例

与使用专用地址空间和/或特殊数据布局(例如,压缩的 OpenGL* 纹理)的 API 不同,常规 OpenCV 数据对象如 cv::Mat驻留在常规系统内存中。也就是说,内存实际上可以与推理引擎共享,并且只有数据所有权要转移。

同样,如果 OpenCV 和推理引擎布局匹配,则数据可以包装为推理引擎(输入/输出)blob。注意,默认情况下,推理引擎接受 NCHW 中的平面非交错输入,因此应明确指定 NHWC(正是交错布局):

警告: InferenceEngine::NHWC 布局原生不受大多数推理引擎插件的支持,因此可能会发生内部转换。

cv::Mat frame(cv::Size(100, 100), CV_8UC3); // regular CV_8UC3 image, interleaved
// creating blob that wraps the OpenCV’s Mat
// (the data it points should persists until the blob is released):
1 /* batch, N*/,
(size_t)frame.rows /* Height */,
(size_t)frame.cols /* Width */,
(size_t)frame.channels() /*Channels,*/,
};
InferenceEngine::TBlob<uint8_t>::Ptr p = InferenceEngine::make_shared_blob<uint8_t>( desc, (uint8_t*)frame.data, frame.step[0] * frame.rows);
inferRequest.SetBlob("input", p);
inferRequest.Infer();
// …
// similarly, you can wrap the output tensor (let’s assume it is FP32)
// notice that the output should be also explicitly stated as NHWC with setLayout
auto output_blob = inferRequest.GetBlob("output");
const float* output_data = output_blob->buffer().as<float*>();
auto dims = output_blob->getTensorDesc().getDims();
cv::Mat res (dims[2], dims[3], CV_32FC3, (void *)output_data);

注意:原始 cv::Mat/blob 不能同时被应用程序和推理引擎使用。或者,可以复制指针引用的数据以解锁原始数据并将所有权归还给原始 API。

基于请求的 API 和“GetBlob”习语

基于推理请求的 API 提供两种类型的请求:同步和异步。下面考虑了同步。异步将(同步)Infer 分割成 StartAsyncWait(参阅推理引擎异步 API)。

更重要的是,推理请求封装了对“可执行”网络和实际输入/输出的引用。现在,当您将网络加载到插件时,您将获得对可执行网络的引用(您可以将其视为队列)。实际的推理请求由可执行网络创建:

auto network = ie.ReadNetwork("Model.xml", "Model.bin");
InferenceEngine::InputsDataMap input_info(network.getInputsInfo());
auto executable_network = ie.LoadNetwork(network, "GPU");
auto infer_request = executable_network.CreateInferRequest();
for (auto & item : input_info) {
std::string input_name = item.first;
auto input = infer_request.GetBlob(input_name);
/** Lock/Fill input tensor with data **/
unsigned char* data = input->buffer().as<PrecisionTrait<Precision::U8>::value_type*>();
// ...
}
infer_request.Infer();
This class represents Inference Engine Core entity.
Definition: ie_core.hpp:31
ExecutableNetwork LoadNetwork(const CNNNetwork &network, const std::string &deviceName, const std::map< std::string, std::string > &config={})
Creates an executable network from a network object.
CNNNetwork ReadNetwork(const std::string &modelPath, const std::string &binPath={}) const
Reads models from IR and ONNX formats.
InferRequest CreateInferRequest()
Creates an inference request object used to infer the network.

GetBlob是与网络通信的推荐方式,因为它在内部为设备分配具有正确填充/对齐的数据。例如,如果使用了GetBlob,GPU 输入/输出 blob 会映射到主机(速度很快)。但是如果调用 SetBlob,将会发生复制(从/到你设置的 blob)到内部 GPU 插件结构的情况。

同时运行多个请求的性能方面

如果您的应用程序同时执行多个推理请求:

  • 对于 CPU,最佳解决方案,您可以使用 CPU "吞吐量" 模式
    • 如果更加关注延迟,则可以尝试EXCLUSIVE_ASYNC_REQUESTS 配置选项,该选项将共享特定设备的所有(可执行)网络的同时执行请求数量限制为一个:
//these two networks go thru same plugin (aka device) and their requests will not overlap.
auto executable_network0 = core.LoadNetwork(network0, "CPU",
auto executable_network1 = core.LoadNetwork(network1, "GPU",
static constexpr auto KEY_EXCLUSIVE_ASYNC_REQUESTS
the key for enabling exclusive mode for async requests of different executable networks and the same ...
Definition: ie_plugin_config.hpp:339
static constexpr auto YES
generic boolean values
Definition: ie_plugin_config.hpp:229


有关可执行网络概念的更多信息,请参阅基于请求的 API 和 “GetBlob” 习语

  • 默认EXCLUSIVE_ASYNC_REQUESTS情况下,异构设备使用。
  • KEY_EXCLUSIVE_ASYNC_REQUESTS选项仅影响单个应用程序的设备队列。
  • 对于 FPGA 和 GPU,实际工作无论如何都是由插件和/或驱动程序序列化的。
  • 最后,对于任何 VPU 风格,使用多个请求是实现良好吞吐量的必要条件。

在推理引擎中,没有请求优先级的概念。留给用户端决定(例如,低优先级推理请求不排队,直到另一个更高优先级正在等待为止)。请注意,需要额外的逻辑在应用程序代码中的可执行网络(队列)之间进行同步。

推理引擎异步 API

推理引擎异步 API 可以提高应用程序的整体帧速率。当加速器忙于推理时,应用程序可以继续在主机上做事,而不是等待推理完成。

在下面的示例中,将推理应用于视频解码的结果。因此可以保持两个并行的推理请求,并且在处理当前请求的同时,正在捕获下一个请求的输入帧。这实质上隐藏了捕获的延迟,因此整体帧速率仅由流水线中最慢的部分(解码 IR 推理)决定,而不是由阶段的总和决定。

您可以比较常规方法和基于异步方法的伪代码:

  • 以常规方式,使用 OpenCV 捕获帧,然后立即处理:
while(true) {
// capture frame
// populate CURRENT InferRequest
// Infer CURRENT InferRequest //this call is synchronous
// display CURRENT result
}

  • 在“真正”异步模式中,NEXT请求填充在主(应用程序)线程中,同时处理 CURRENT 请求:
while(true) {
// capture frame
// populate NEXT InferRequest
// start NEXT InferRequest //this call is async and returns immediately
// wait for the CURRENT InferRequest //processed in a dedicated thread
// display CURRENT result
// swap CURRENT and NEXT InferRequests
}

该技术可以推广到任何可用的并行闲置部分。例如,您可以进行推理并同时对结果帧或先前帧进行编码,或者运行进一步推理,例如在面部检测结果之上进行情感检测。

但是有一些重要的性能警告:例如,并行运行的任务应尽量避免过度订阅共享计算资源。如果推理是在 FPGA 上执行,而 CPU 基本上是空闲的,那么在 CPU 上并行做事是有意义的。但是,多个推理请求可能会超额订阅。注意:异构执行可能会隐式使用 CPU,参阅异构

此外,如果推理是在图形处理单元 (GPU) 上执行,例如在同一 GPU 上并行执行结果视频的编码,几乎没有什么好处,因为设备已经很忙。

参阅对象检测 SSD 演示(面向延迟的异步 API 展示)和基准测试应用程序样本(都有面向延迟和吞吐量的模式)以了解实际操作中异步 API 的完整示例。

使用工具

无论您是第一次调优还是进行高级性能优化,您都需要一个能够提供准确洞察的工具。英特尔® VTune™ Amplifier 为您提供了进行挖掘并解释分析数据的工具。

或者,您可以收集样本报告的原始分析数据,第二章提供了如何解释这些数据的示例。

英特尔® VTune™™ 示例

推理引擎的所有主要性能调用都使用检测和跟踪技术 API 进行检测。这样可以查看英特尔® VTune™ 时间线和聚合上的推理引擎调用,并将它们与底层 API(如 OpenCL)相关联。反过来,这可以实现仔细的每层执行分解。

在英特尔® VTune™ Amplifier 中选择分析类型时,确保选择分析用户任务、事件、和计数器选项:

参阅英特尔® VTune™ Amplifier 用户指南的相应部分以了解详情。

推理引擎调用示例:

  • 在英特尔 VTune Amplifier 时间线上。注意:Task_runNOThrow是一个异步 API 包装器,它在不同的线程中执行并触发英特尔 MKL-DNN 执行:

  • 在英特尔 VTune Amplifier 从上到下视图中,按任务领域来分组。注意 Task_runNoThrowMKLDNN _INFER,它们支撑了实际的英特尔 MKL-DNN 内核的执行:

同样,您可以在英特尔 VTune Amplifier 中使用任何 GPU 分析,并获得与推理引擎 API 的一般关联以及 OpenCL 内核的执行分解。

就像使用常规的原生应用程序一样,可以在计数器中进一步钻取,但是这对于优化自定义内核最有用。最后,借助英特尔 VTune Amplifier,分析不限于您的用户级代码(参阅英特尔® VTune™ Amplifier 用户指南的相应部分)。

内部推理性能计数器

几乎每个样本(对于带有 -h 的特定样本,检查命令行选项) 支持 -pc命令,该命令输出内部执行分解。参阅样本代码了解这背后的实际推理引擎 API。

下面是一个网络的 CPU 插件输出示例(由于该设备是 CPU,各层挂钟 realTimecpu 时间是相同的):

conv1 EXECUTED layerType: Convolution realTime: 706 cpu: 706 execType: jit_avx2
conv2_1_x1 EXECUTED layerType: Convolution realTime: 137 cpu: 137 execType: jit_avx2_1x1
fc6 EXECUTED layerType: Convolution realTime: 233 cpu: 233 execType: jit_avx2_1x1
fc6_nChw8c_nchw EXECUTED layerType: Reorder realTime: 20 cpu: 20 execType: reorder
out_fc6 EXECUTED layerType: Output realTime: 3 cpu: 3 execType: unknown
relu5_9_x2 OPTIMIZED_OUT layerType: ReLU realTime: 0 cpu: 0 execType: undef

这包含层名称(如 IR 中所示)、层类型和执行统计信息。注意 OPTIMIZED_OUT,这表明特定的激活被融合到相邻的卷积中。而且,unknown继续存在,用于推理引擎特定的 CPU(帮助程序)原语,这些原语不是英特尔 MKL-DNN 的一部分。

请注意,在 CPU 执行分解中有一些辅助层,它们在原始拓扑中没有出现。这些是由插件自动添加的。例如,Reorder将英特尔 MKL-DNN 内部(阻塞)布局重新打包到常规普通 NCHW(用户期望作为输出)。如几条设备特定提示中所述,如果您的自定义内核引入了大量未完成/昂贵的重新排序,请考虑为内核阻止实施。

请注意,在异构情况下,将有关于统计数据是关于哪个子图的额外信息(第一个子图是 GPU,因此其 cpu/主机时间与实际 realTime 相比的确很小):

subgraph1: squeeze1x1 EXECUTED layerType: Convolution realTime: 227 cpu:3 execType: GPU
subgraph2: detection_out EXECUTED layerType: DetectionOutput realTime: 121 cpu:121 execType: unknown

如上所述,unknown在此处意味着 CPU 内核,具有未知的 (例如,不是 AVX2 或 AVX512)加速路径。由于 FPGA 执行不分离单个内核,因此只有批量执行/数据传输统计数据可用:

subgraph1: 1. input preprocessing (mean data/FPGA):EXECUTED layerType: preprocessing realTime: 129 cpu: 129
subgraph1: 2. input transfer to DDR:EXECUTED layerType: realTime: 201 cpu: 0
subgraph1: 3. FPGA execute time:EXECUTED layerType: realTime: 3808 cpu: 0 subgraph1: 4. output transfer from DDR:EXECUTED layerType: realTime: 55 cpu: 0
subgraph1: 5. FPGA output postprocessing:EXECUTED layerType: realTime: 7 cpu: 7
subgraph1: 6. softmax/copy: EXECUTED layerType: realTime: 2 cpu: 2
subgraph2: out_prob: NOT_RUN layerType: Output realTime: 0 cpu: 0
subgraph2: prob: EXECUTED layerType: SoftMax realTime: 10 cpu: 10
Total time: 4212 microseconds

softmax/copy是将 FPGA 子图连接到 CPU 子图(并复制数据)的粘合层。

另请参阅