PointPillars 在 Intel OpenVINO 上的性能优化

2024年5月24日 419点热度 0人点赞 0条评论

PointPillars 是一种快速 E2E DL 网络,用于 3D 点云中的对象检测。它利用 PointNet 来学习以垂直列(支柱)组织的点云的表示。大量实验表明,PointPillars 在速度和准确性方面都大大优于以前的方法 。在本文档中,我们介绍了如何使用英特尔® OpenVINO在英特尔平台上优化PointPillars。

PointPillars

下图展示了PointPillars网络的主要步骤(组件):

图1 PointPillars网络概述
  • PFN
    • 预处理:将点云原始数据转换为 stacked pillars 和 pillar index;
    • PFE:它使用 stacked pillars 来学习一组特征;
    • 散射:它将 pillars 特征散射回 CNN 的 2D 伪图像。
  • Backbone(2D CNN)
    • RPN:它利用 CNN 从 2D 伪图像中提取特征。
  • SSD
    • 3D 边界框预测:SSD 使用来自 Backbone 的特征来预测对象的 3D 边界框;
    • 后处理:使用 NMS 算法过滤边界。

OpenVINO™ 工具套件

OpenVINO 是英特尔的 AI 开发工具包,用于快速开发应用程序和解决方案,解决各种任务,包括人类视觉仿真、自动语音识别、自然语言处理、推荐系统等。

该工具包基于最新一代的人工神经网络(包括 CNN、循环网络和基于注意力的网络),可跨英特尔®硬件扩展计算机视觉和非视觉工作负载,从而最大限度地提高性能。它通过从边缘到云端部署的高性能、AI 和 DL 推理来加速应用程序。

本工作中使用的 OpenVINO™ 工具套件的发行版(版本)为 2021.3,https://docs.openvinotoolkit.org/2021.3/openvino_docs_install_guides_installing_openvino_linux.html

第 11 代英特尔®酷睿™ 处理器 (Tiger Lake)

本工作使用了第 11 代英特尔®酷睿™ 处理器 (Tiger Lake) 的两个 SKU:

  • Intel® Core  i7-1185GRE 处理器
  • Intel® Core  i7-1165G7 处理器

本文档中提供的所有测试结果均来自以下 2 个平台:

性能评估结果

通过以下章节中介绍的优化方法,可以在 Intel® Core™ i7-1185GRE 处理器(禁用 Turbo 模式)上实现 11.1 FPS 的吞吐量和 154.7 ms 的延迟。

不同硬件平台上的性能评估结果如表2所示。

PFE FP16、RPN INT8* – 请参阅“量化为 INT8”部分
平衡模式( Balanced Mode )** – 请参阅“平衡模式( Balanced Mode )”部分
430%*** – 我们通过 Ubuntu*20.04 上的“top”工具获取 CPU 负载。 ‘top’ 将 cpu 负载显示为单个 CPU 的百分比。在多核系统上,百分比可以大于 100%。在 Intel® Core™ i7-1165G7 或 Intel® Core™ i7-1185GRE 中,有 4 个物理核心,每个核心有 2 个线程,因此总共有 8 个逻辑核心,因此最高负载为 8×100% = 800 %。

源代码迁移

我们利用开源项目 OpenPCDet ,它是 OpenMMLab 的子项目。 OpenPCDet 框架支持多种 3D 点云(例如,激光雷达生成的点云)中的对象检测模型,包括 PointPillars。

原始的 OpenPCDet 由官方发布的 PyTorch* 实现(我们在 PyTorch 1.7.1+CPU 上进行了验证)。并且,在PointPillars管道中,它包含以下组件:

图2 . OpenPCDet PointPillars 管道

原始源代码无法在Intel ®架构上运行,因为它们是在CUDA 环境中开发的。因此,为了在没有CUDA环境的通用CPU上支持OpenPCDet,我们对源代码进行了迁移,如下所述。

PyTorch* 源代码的迁移

如下例所示,“.cuda()”出现在原始源代码中。它们需要在迁移中删除。

- model.cuda()
+   device = 'cpu'
+   model.to(device)
-   self.anchors = [x.cuda() for x in anchors]
+   self.anchors = [x for x in anchors]
-   batch_dict[key] = torch.from_numpy(val).float().cuda()
+   batch_dict[key] = torch.from_numpy(val).float()

迁移到 C++ 源代码

原始源代码中的CUDA内核需要替换为标准C++。

CUDA编译器被C++编译器取代。

-  from torch.utils.cpp_extension import BuildExtension, CUDAExtension
+  from torch.utils.cpp_extension import BuildExtension, CppExtension

CUDA源代码从编译配置文件中删除。

sources=[
                     'src/pointnet2_api.cpp',
                     'src/ball_query.cpp',
-                    'src/ball_query_gpu.cu',
                     'src/group_points.cpp',
-                    'src/group_points_gpu.cu',
                     'src/sampling.cpp',
-                    'src/sampling_gpu.cu',
                     'src/interpolate.cpp',
-                    'src/interpolate_gpu.cu',
                   ],

删除了对 CUDA 内核的调用。

- nmsLauncher(boxes_data, mask_data, boxes_num, nms_overlap_thresh);
- nmsNormalLauncher(boxes_data, mask_data, boxes_num, nms_overlap_thresh);

在PointPillars中,只有NMS算法被实现为CUDA内核。我们只需要用C++手动重写即可。

+void nmsLauncher(const float *boxes, bool *mask,
+                 const int boxes_num, const float nms_overlap_thresh){
+     int col_idx = 0;
+     int row_idx = 0;+
+     float block_boxes[7];
+     mask[0] = false;
+     for (col_idx = 0; col_idx < boxes_num; col_idx++){
+         if (mask[col_idx]==true)
+         continue;
+     block_boxes[0] = boxes[col_idx * 7 + 0];
+     block_boxes[1] = boxes[col_idx * 7 + 1];
+     block_boxes[2] = boxes[col_idx * 7 + 2];
+     block_boxes[3] = boxes[col_idx * 7 + 3];
+     block_boxes[4] = boxes[col_idx * 7 + 4];
+     block_boxes[5] = boxes[col_idx * 7 + 5];
+     block_boxes[6] = boxes[col_idx * 7 + 6];
+
+     for (row_idx = col_idx + 1; row_idx < boxes_num; row_idx++){
+         const float *cur_box = boxes + row_idx * 7;
+         bool drop = 0;
+
+         if (iou_bev(cur_box, block_boxes) > nms_overlap_thresh){
+            drop = true;
+            mask[row_idx] = drop;
+         }
+      }
+   }
+}

Intel® Core™ 处理器 (Tiger Lake)的分析

源代码迁移完成后,我们在Intel® Core™ i7-1165G7处理器上运行并收集PointPillars网络的性能数据,软硬件配置如表2所示。
如图3所示,两种NN模型(RPN 和 PFE)是最耗时的组件,占总延迟的 92%。因此,我们需要重点关注这些模型的优化。

图3 .原始管道的延迟

使用 OpenVINO™ 工具套件实施

作为 OpenVINO™ 工具包的一部分,MO 是一个跨平台命令行工具,可促进训练和部署环境之间的转换、执行静态模型分析并调整 NN 模型以在端点目标设备上实现最佳执行。在Intel ®架构处理器上运行之前,神经网络模型可以通过 MO 进行优化。

MO将NN模型转换为IR格式,可以用IE读取、加载和推断。 IE 提供跨多种受支持的 Intel ®架构处理器的统一 API。 IR 格式使用一对文件来描述 NN 模型:

  • 描述神经网络拓扑的 .xml 文件;
  • .bin 文件包含权重和偏差的二进制数据。
图4 . MO将NN模型转换为IR格式

NN 模型到 IR 的转换

在本节中,我们将展示如何将 PointPillars 中使用的 NN 模型从 PyTorch* 转换为 IR 格式,并将它们集成到处理管道中。

由于 MO 不支持从 PyTorch* 直接转换为 IR 格式,因此我们需要将模型从 PyTorch* 转换为 ONNX 格式作为中间步骤(如图 5 所示)。

ONNX 是一个开放的生态系统,使人工智能开发人员能够随着项目的发展选择正确的工具。 ONNX 为 AI 模型(包括深度学习和传统机器学习)提供开源格式。它定义了可扩展的计算图模型,以及内置运算符和标准数据类型的定义。

图5 . NN模型的转换

正如前面章节中提到的,PointPillars 中使用了两种神经网络模型:PFE 和 RPN。我们需要将它们转换为 IR 格式。

从 PyTorch* 到 ONNX 的转换

我们利用开源项目 SmallMunich ,按照 https://github.com/SmallMunich/nutonomy_pointpillars#onnx-ir-generate中的说明将神经网络模型从 PyTorch* 转换为 ONNX 。

SmallMunich 也是 PointPillars 基于 Pytorch* 的代码库。此外,它还实现了 ONNX 转换。通过运行 onnx_model_generate(),我们从 PyTorch* 模型获取 ONNX 模型(pfe.onnx 和 rpn.onnx)。

图6 . Pointpillars 的处理管道

从 ONNX 到 IR 的转换

运行 MO 将pfe.onnx转换为 IR 格式 (FP16):

$ cd <install_folder>/openvino_2021/deployment_tools/model_optimizer

$ python mo.py --input_model <input_folder>/pfe.onnx --input pillar_x,pillar_y,pillar_z,pillar_i,num_points_per_pillar,x_sub_shaped,y_sub_shaped,mask --

input_shape=[1,1,12000,100],[1,1,12000,100],[1,1,12000,100],[1,1,12000,100],[1,12000],[1,1,12000,100],[1,1,12000,100],[1,1,12000,100] -o <output_folder> --data_type=FP16

输出如下:

Model Optimizer arguments:
Common parameters:

                  - Path to the Input Model: <your_input_folder>/pfe.onnx
                  - Path for generated IR: <your_output_folder>
                  - IR output name: pfe
                  - Log level: ERROR
                  - Batch: Not specified, inherited from the model
                  - Input layers: pillar_x,pillar_y,pillar_z,pillar_i,num_points_per_pillar,x_sub_shaped,y_sub_shaped,mask
                  - Output layers: Not specified, inherited from the model
                  - Input shapes: [1,1,12000,100],[1,1,12000,100],[1,1,12000,100],[1,1,12000,100],[1,12000],[1,1,12000,100],[1,1,12000,100],[1,1,12000,100]
                  - Mean values: Not specified
                  - Scale values: Not specified
                  - Scale factor: Not specified
                  - Precision of IR: FP16
                  - Enable fusing: True
                  - Enable grouped convolutions fusing: True
                  - Move mean values to preprocess section: None
                  - Reverse input channels: False
ONNX specific parameters:
                  - Inference Engine found in: <install_folder>/openvino_2021/python/python3.8/openvino
Inference Engine version: 2.1.2021.3.0-2787-60059f2c755-releases/2021/3
Model Optimizer version: 2021.3.0-2787-60059f2c755-releases/2021/3
[ SUCCESS ] Generated IR version 10 model.
[ SUCCESS ] XML file: <your_output_folder>/pfe.xml
[ SUCCESS ] BIN file: <your_output_folder>/pfe.bin
[ SUCCESS ] Total execution time: 3.67 seconds.
[ SUCCESS ] Memory consumed: 281 MB.

运行 MO 将rpn.onnx转换为 IR 格式 (FP16):

$ cd <install_folder>/openvino_2021/deployment_tools/model_optimizer
$ python mo.py --input_model <input_folder>/rpn.onnx -o <output_folder> --input_shape=[1,64,496,432] --data_type=FP16

输出如下:

Model Optimizer arguments:
Common parameters:
           - Path to the Input Model: <your_intput_folder>/rpn.onnx
           - Path for generated IR: <your_output_folder>
           - IR output name: rpn
           - Log level: ERROR
           - Batch: Not specified, inherited from the model
           - Input layers: Not specified, inherited from the model
           - Output layers: Not specified, inherited from the model
           - Input shapes: [1,64,496,432]
           - Mean values: Not specified
           - Scale values: Not specified
           - Scale factor: Not specified
           - Precision of IR: FP16
           - Enable fusing: True
           - Enable grouped convolutions fusing: True
           - Move mean values to preprocess section: None
           - Reverse input channels: False
ONNX specific parameters:
           - Inference Engine found in: <install_folder>/openvino_2021/python/python3.8/openvino
Inference Engine version: 2.1.2021.3.0-2787-60059f2c755-releases/2021/3
Model Optimizer version: 2021.3.0-2787-60059f2c755-releases/2021/3
[ SUCCESS ] Generated IR version 10 model.
[ SUCCESS ] XML file: <your_output_folder>/rpn.xml
[ SUCCESS ] BIN file: <your_output_folder>/rpn.bin
[ SUCCESS ] Total execution time: 7.25 seconds.
[ SUCCESS ] Memory consumed: 384 MB.

最后,我们得到 IR 格式文件作为 MO 的输出,用于 ​​PointPillars 中使用的两个 NN 模型。

  • PFE模型:pfe.xmlpfe.bin
  • RPN 模型:rpn.xmlrpn.bin

使用基准工具验证

我们使用 Benchmark Tool 通过运行每个 NN 模型来评估吞吐量(以 FPS 为单位)和延迟(以毫秒为单位)。评估结果为进一步优化提供指导。

Benchmark Tool 是 OpenVINO™ 工具套件提供的软件。它创建模拟输入来评估神经网络模型的性能。该软件支持两种操作模式:“同步”和“异步”(默认模式)。

我们使用以下命令评估 Intel® Core™ i7-1165G7 处理器中 CPU 和 iGPU 的吞吐量和延迟:

$ cd ~/intel/openvino_2021/deployment_tools/tools/benchmark_tool/

$ python3 benchmark_app.py -m <folder>/pfe.xml -d CPU -api sync
$ python3 benchmark_app.py -m <folder>/rpn.xml -d CPU -api sync

$ python3 benchmark_app.py -m <folder>/pfe.xml -d GPU -api sync
$ python3 benchmark_app.py -m <folder>/rpn.xml -d GPU -api sync

从表3和表4的评价结果​​可以看出:

  • 对于两种神经网络模型,iGPU 在吞吐量和延迟方面均优于 CPU;
  • 由 MO 转换和优化的 NN 模型(IR 格式)的处理速度可以显着加快。

量化为 INT8

为了进一步加速推理,我们使用 POT 将 RPN 模型从 FP16 转换为 INT8 分辨率(而 PFE 模型的工作仍在进行中)。

POT 旨在通过特殊过程(例如训练后量化)加速 NN 模型的推理,而无需重新训练或微调 NN 模型。因此,不需要额外的训练数据集或进一步处理。

POT 的示例代码可以在 OpenVINO™ 工具包中找到:

<install_folder>/openvino_2021/deployment_tools/tools/post_training_optimization_toolkit/sample

附件 A显示了我们工作中使用的 Python* 脚本。

在以下脚本中,我们选择算法“DefaultQuantization”(没有精度检查器)和预设参数“性能”(权重和激活的对称量化)。

algorithms = [
   {
      'name': 'DefaultQuantization',
      'params': {
          'target_device': 'CPU',
          'preset': 'performance',
          'stat_subset_size': 100
                }
   }
]

在以下脚本中,_getitem_()是最重要的函数,POT 调用它来处理输入数据集。

img输入数据集。我们从 SmallMunich 评估管道中获得 RPN 模型的输入(输入是 [1, 64, 496, 432] 的矩阵)。

准确性检查器使用注释来验证预测结果是否与注释相同。然而,当我们选择算法‘DefaultQuantization’时,准确性检查器会跳过比较。因此,我们只需要提供与 RPN 推理的输出形状完全相同的全零注释。

def __getitem__(self, index):
    ...
    img = np.fromfile(image_path, dtype=np.float32).reshape(64,496,432)
    annotation_np = {'184': np.zeros((1, 248, 216, 14), dtype=np.float32),
                     '185': np.zeros((1,248,216,2), dtype=np.float32),
                     '187': np.zeros((1,248,216,4), dtype=np.float32)}
    annotation = (index, annotation_np)
    return annotation, img

运行以下 POT 脚本后,我们将获得具有 INT8 分辨率的 IR 格式的 RPN 模型(rpn.xmlrpn.bin)。

INFO:compression.statistics.collector:Start computing statistics for algorithms : DefaultQuantization
INFO:compression.statistics.collector:Computing statistics finished
INFO:compression.pipeline.pipeline:Start algorithm: DefaultQuantization
INFO:compression.algorithms.quantization.default.algorithm:Start computing statistics for algorithm : ActivationChannelAlignment
INFO:compression.algorithms.quantization.default.algorithm:Computing statistics finished
INFO:compression.algorithms.quantization.default.algorithm:Start computing statistics for algorithms : MinMaxQuantization,FastBiasCorrection
INFO:compression.algorithms.quantization.default.algorithm:Computing statistics finished
INFO:compression.pipeline.pipeline:Finished: DefaultQuantization

表 5 显示 量化可以将 RPN 模型权重的文件大小从 9.2 MB (FP16) 显着减小到 5.2 MB (INT8)。

使用 Benchmark Tool,我们在 Intel® Core™ i7-1165G7 处理器的 CPU 和 iGPU 上评估了两种不同格式和分辨率的 NN 模型,性能结果如表 6 所示。 PFE 模型的研究仍在进行中,我们决定在 PointPillars 网络的处理管道中使用 PFE (FP16) 和 RPN (INT8) 模型。

我们还在 Intel® Core™ i7-1165G7 处理器的 iGPU 上并行运行 PFE (FP16) 和 RPN (INT8) 模型的基准工具。表 7 中显示的结果可以为我们提供有关并行化管道的一些指导。

将 IE 集成到 PointPillars Pipeline

如图 7 所示,我们将两个 NN 模型的 IR 文件集成到 SmallMunich 管道中。

  • PFE模型:pfe.xmlpfe.bin
  • RPN 模型:rpn.xmlrpn.bin
图 7 .将 IR 文件移植到 SmallMunich 管道中

除了 C++ API 之外,OpenVINO™ 工具套件还提供 Python* API 来调用 IE。
将 IE 集成到 PointPillars 管道中包括图 8 中所示的以下步骤。

图 8 . IE集成流程
  • 创建IE Core来管理可用设备并读取网络对象;
  • 读取MO创建的IR格式的NN模型(支持.xml格式);
  • 配置输入和输出;
  • 将NN模型加载到设备上;
  • 创建推理请求;
  • 准备输入;
  • 推理;
  • 处理输出。

IE的初始化

我们创建 IE 核心实例来处理 PFE 和 RPN 推理。

ie = IECore()

加载NN模型并推理

集成的主要思想是将PyTorch* 的forward()函数替换为OpenVINO™ 工具套件的infer()函数(Python* API)。

PFE模型

首先,将网络的拓扑和权重读取到内存中。

model_file_pfe = "<your_folder>/pfe.xml"
model_weight_pfe = "<your_folder>/pfe.bin"
net = openvino_ie.read_network(model_file_pfe, model_weight_pfe)

然后,我们需要告诉IE输入blob的信息并加载网络到设备。
对于 PFE 模型,每个点云帧的输入形状都是可变的,因为每个帧中的柱子数量不同。因此,有两种对输入进行整形的方法:静态输入(Static Input Shape) 和动态输入 (Dynamic Input Shape )。我们的工作中使用的是前一种方法。

  • 静态输入

调用load_network()将模型加载到GPU。在 pfe.xml 文件中,它将输入形状定义为 [[1,1,12000,100], [1,1,12000,100], [1,1,12000,100],[1,1,12000 ,100],[1,12000],[1,1,12000,100],[1,1,12000,100],[1,1,12000,100]]

exec_net = openvino_ie.load_network(network=self.net_pfe, device_name="GPU")

然后,我们利用零填充将输入 blob 的形状设置为 12000。根据源代码中的原始配置,这意味着我们预计最多有 12000 个柱子作为网络的输入。

len_padding = 12000 - pillar_len
pillarx_pad = np.pad(pillarx, ((0,0),(0,0),(0,len_padding),(0,0)),'constant',constant_values=0)
pillary_pad = np.pad(pillary, ((0,0),(0,0),(0,len_padding),(0,0)),'constant',constant_values=0)
pillarz_pad = np.pad(pillarz, ((0,0),(0,0),(0,len_padding),(0,0)),'constant',constant_values=0)
pillari_pad = np.pad(pillari, ((0,0),(0,0),(0,len_padding),(0,0)),'constant',constant_values=0)

例如,填充前的点云帧形状如下:

pillar_x:              1,1,6815,100
pillar_y:              1,1,6815,100
pillar_z:              1,1,6815,100
pillar_i:              1,1,6815,100
num_points_per_pillar: 1,6815
x_sub_shaped:          1,1,6815,100
y_sub_shaped:          1,1,6815,100
mask:                  1,1,6815,100

padding后点云框的形状变成如下:

pillar_x:              1,1,12000,100
pillar_y:              1,1,12000,100
pillar_z:              1,1,12000,100
pillar_i:              1,1,12000,100
num_points_per_pillar: 1,12000
x_sub_shaped:          1,1,12000,100
y_sub_shaped:          1,1,12000,100
mask:                  1,1,12000,100

最后,我们将每帧的静态形状的输入 blob 传递给infer() 。

res = exec_net.infer(inputs={'pillar_x': pillar_x,
                             'pillar_y': pillar_y,
                             'pillar_z': pillar_z,
                             'pillar_i': pillar_i,
                             'num_points_per_pillar': num_points,
                             'x_sub_shaped': x_sub_shaped,
                             'y_sub_shaped': y_sub_shaped,
                             'mask': mask})
  • 动态输入

与静态输入形状不同,我们需要在每一帧上调用load_network(),因为输入 blob 的 形状逐帧变化。

图 9 . IE对于动态输入形状的集成流程

在调用infer()之前,我们需要重塑点云每一帧的输入参数,如果输入形状发生变化,则调用load_network() 。 load_network()每帧需要相当长的时间(在 iGPU 中通常为 3~4 秒),因为它需要动态 OpenCL 编译处理。

exec_net.reshape({'pillar_x':pillar_x.shape,
               'pillar_y':pillar_y.shape,
               'pillar_z':pillar_z.shape,
               'pillar_i':pillar_i.shape,
               'num_points_per_pillar':num_points.shape,
               'x_sub_shaped':x_sub_shaped.shape,
               'y_sub_shaped':y_sub_shaped.shape,
               'mask':mask.shape})

exec_net = ie.load_network(network=net, device_name="GPU")

res = exec_net.infer(inputs={'pillar_x': pillar_x,
                             'pillar_y': pillar_y,
                             'pillar_z': pillar_z,
                             'pillar_i': pillar_i,
                             'num_points_per_pillar': num_points,
                             'x_sub_shaped': x_sub_shaped,
                             'y_sub_shaped': y_sub_shaped,
                             'mask': mask})

与动态输入相比,静态输入可以显着减少 NN 模型加载时间,特别是对于 iGPU,因为在初始化时只需要load_network()一次。

然而,静态输入可能会导致更长的推理时间,因为神经网络模型的尺寸大于动态输入的尺寸。

RPN模型

对于 RPN 模型,其输入形状固定为 [1, 64, 496, 432],因此我们只需要在初始化时调用load_network()一次。

model_file = "<your folder>/rpn.xml"
model_weight = "<your folder>/rpn.bin"
net = ie.read_network(model_file, model_weight)

exec_net = ie.load_network(network=ie.net, device_name="GPU")

下一步是为每一帧调用infer() 。

input_blob = next(iter(net.input_info))
res = exec_net.infer(inputs={input_blob: spatial_features})

准确性评估

我们使用 KITTI 3D 对象检测数据集来评估 NN 模型的准确性。如表 8 所示,该数据集共有三个难度级别。

将 IE 集成到 SmallMunich 的 PointPillars 管道中后,我们可以评估三个难度级别的准确性。如表 10 所示,与 Pytorch* 原始模型相比,使用 IR 模型和静态输入形状的准确性没有损失。还表明,将 RPN 模型量化为 INT8 仅导致不到 1% 的精度损失。

表 9 显示了 KITTI 测试 3D 检测基准上的汽车 AP 结果。

从 SmallMunich 迁移到 OpenPCDet

OpenPCDet 用作演示应用程序。由于我们使用 SmallMunich 生成 ONNX 模型,因此我们不仅需要将 NN 模型(PFE 和 RPN)迁移,还需要将非 DL 处理从 SmallMunich 代码库迁移到 OpenPCDet 代码库。

SmallMunich 和 OpenPCDet 大约 90% 的源代码是相同的。它们的主要区别在于功能,包括锚点生成、边界框生成、后处理中的框过滤和 NMS。

将源代码从 SmallMunich 迁移到 OpenPCDet 后,OpenPCDet 管道可以生成与 SmallMunich 相同的结果。

图 10 .从 SmallMunich 迁移到 OpenPCDet

管道剖析

我们在 KITTI 3D 对象检测数据集上运行管道。 3D 点由Velodyne HDL-64E(64 通道激光雷达)捕获。它以每秒 10 帧的速度旋转,每帧捕获大约 100K 点。我们通过基于 SmallMunich [9] 代码库的create_reduced_point_cloud()函数将点数减少到每帧 20K 左右。

图 11 .管道延迟

我们评估了第 5.3 节优化的管道在英特尔®酷睿™ i7-1165G7 处理器上的延迟,结果总结在表 10 中。

我们通过图 11 中的条形图进一步可视化总延迟。它表明 OpenVINO™ 工具套件的 NN 模型优化和量化可以显着加速管道处理。

三种管道模式

如图 12 所示,与原始实现相比,PFE 和 RPN 推断的延迟均显着降低。接下来,我们考虑根据不同的性能目标进一步优化管道。

图 12 .优化管道的延迟。

我们开发了以下提到的管道模式来满足不同的性能目标:

  • 延迟模式( Latency Mode):实现最短的延迟;
  • 吞吐量模式(Throughput Mode):达到最大吞吐量;
  • 平衡模式(Balanced Mode):实现延迟和吞吐量之间的权衡。

延迟模式( Latency Mode)

在这种模式下,管道中的每个步骤在上一个步骤完成后立即开始运行,即这些步骤按以下顺序无缝执行:

  • T0时,开始预处理;
  • T1时,调用infer()让IE对PFE模型进行推理;
  • T2时,Scattering将3D特征图映射到2D伪图像;
  • T3时,调用infer()让IE对RPN模型进行推理;
  • 在T4,开始后处理。
图 13 .延迟模式

如图 13 所示,在一个特定时间,管道仅使用 CPU 或 iGPU。这两个处理器专门用于一帧的处理。这些步骤按顺序无缝执行,从而实现最短的延迟。

吞吐量模式(Throughput Mode)

OpenVINO™ 工具套件中的 IE 能够使用API async_infer()进行异步处理。这意味着一帧的步骤可以与另一帧的步骤并行完成。

在此模式下,主线程在 CPU 上运行,处理预处理、散射和后处理。使用 PFE 和 RPN 模型的推理在 IE 使用async_infer()自动创建的独立线程上运行,并且这些线程在 iGPU 中运行。

我们以第N帧为例来解释图14中的处理。

  1. T0时,主线程开始处理第N帧的预处理;
  2. 在T1时刻,一旦IE通知第(N-1)帧的PFE推理完成,主线程就启动2个作业:
    • 第N帧的PFE推断;
    • 第(N-1)帧的散射;
  3. 在 T2 时,当第 (N-1) 帧的散射完成时,主线程启动 3 个作业:
    • 第(N-1)帧的RPN推断;
    • 对第(N-2)帧进行后处理;
      根据分析,此时第 (N-2) 帧的 RPN 推理已经完成,这意味着同一帧的后处理可以开始。
    • 对第(N+1)帧进行预处理;
      第 (N-2) 帧的后处理完成后开始。
  4. 在T4,一旦IE通知第N帧的PFE推理完成,主线程就开始2个作业:
    • 第 (N+1) 帧的 PFE 推断;
    • 第N帧的散射;
  5. 在 T5,当第 N 帧 的散射完成时,主线程启动 3 个作业:
    • 第N帧的RPN推断;
    • 第(N-1)帧的后处理
      根据分析,此时第(N-1)帧的RPN推断已经完成,这意味着同一帧的后处理可以开始。
    • 对第(N+2)帧进行预处理;
      第 (N-1) 帧的后处理完成后开始。
  6. T7时,当IE通知第(N+1)帧的PFE推理完成后,主线程开始对同一帧进行散射,随后在T8时对第N帧进行后处理。
图 14 .吞吐量模式

与延迟模式相比,吞吐量模式的主要思想是最大化 iGPU 中 PFE 和 RPN 推理的并行化,以实现最大吞吐量。 iGPU的负载相当高,平均在95%左右。

然而,这会增加每帧(例如,第 N 帧)的延迟,因为:

  • 当 PFE 和 RPN 在 iGPU 中并行运行时,它们的推理延迟都会增加;
  • 在第 N 帧的管道中,有两个等待期:
    • PFE推理必须等待第(N-1)帧的PFE推理完成,从T1到T2;
    • 后处理必须等待第(N+1)帧的散射完成,从T7到T9。

增加的延迟是 PFE 和 RPN 推理并行化所付出的代价。

平衡模式(Balanced Mode)

该模式用于实现延迟和吞吐量之间的权衡。
我们以第N帧为例来解释图15中的处理。

  1. T0时,主线程开始处理第N帧的预处理;
  2. 在T1时,主线程调用async_infer()要求IE对第N帧运行PFE推理;
  3. T2时,一旦通知第(N-1)帧RPN推理完成,主线程就开始第(N-1)帧的后处理;
  4. T3时,一旦通知第N帧的PFE推理完成,主线程就开始第N帧的分散;
  5. 在T4,分散完成后,主线程启动2个作业:
    • 第N帧的RPN推断;
    • 对第(N+1)帧进行预处理;
  6. 在T6,一旦通知第N帧的RPN推理完成,主线程就开始对第N帧的后处理。
图 15 .平衡模式

由于 PFE 和 RPN 推理的并行化程度较低,iGPU 的负载为 86%(吞吐量模式下为 95%)。

性能评估结果

我们评估了在英特尔® 酷睿™ i7-1185GRE 处理器上通过 OpenVINO™ 工具套件优化的 PointPillars 管道的三种模式。结果总结于表11中。

不同的管道模式是根据PointPillars管道的每个步骤(组件)的分析结果来设计的。结果表明,更高的并行化可以获得更好的吞吐量,但延迟更差。

分析结果显示 PFE 推理花费的时间最长,因此我们针对吞吐量模式和平衡模式将管道的其余步骤与 PFE 推理并行。

图 16 .物体检测的 3D 框

予人玫瑰,手有余香。如果您觉得本文对您有帮助,请点赞或打赏。

文章评论

您需要 登录 之后才可以评论