如何让点云3D检测模型在征程5上部署性能更佳?以 PointPillars 模型为例的优化探索

2023/12/20


地平线征程5

地平线“征程5”是专为高阶智能驾驶打造的智能芯片,搭载地平线第三代架构BPU--贝叶斯(Bayes),算力可达128TOPS,是率先实现前装量产的国产百TOPS级大算力智能芯片。基于征程5开发的高等级自动驾驶方案可实现ADAS功能、高速导航智能驾驶、城区导航智能驾驶和智慧泊车的全场景覆盖。针对自动驾驶实际工况,Bayes结合突破性的AI加速计算技术,通过灵活配置的访存计算,极大的优化内存占用及访存,将并行计算发挥到极致!


简介


在自动驾驶应用中,除了在2D图像中检测目标之外,还必须在3D空间中检测某些目标的类别,如汽车、行人、自行车等。3D点云可以提供一种精确、高空间维度、高分辨率的数据,可以弥补对3D空间的距离信息,使得系统能够更准确地理解周围环境的立体结构,更精确地检测和跟踪物体;并且点云的采集在各种环境条件下都具有良好的适应性,不受光照、天气和光线等因素的限制,相比之下,光学传感器(如摄像头)在恶劣天气或低光照条件下可能性能下降,而点云的采集能够在这些情况下保持相对稳定的性能,可以为车辆提供了关键的环境感知和障碍物检测能力,为自动驾驶车辆的感知和决策提供了关键数据,是一个不可或缺的组成部分,是实现安全、智能自动驾驶的关键技术之一。


随着深度学习架构的进步逐渐出现了许多基于3D点云的目标检测器。Pointpillars 提出了一种改进版本的点云表征方法Pillar,可以将点云转换成伪图像,进而通过2D卷积实现基于3D点云的目标检测,是一个能够平衡检测速度和检测精度的检测模型,是最常用于点云推理的模型之一,目前地平线的参考算法已帮助多家用户完成点云模型的部署落地。本文即对如何在地平线征程5芯片上高效部署基于3D点云的PointPillars 检测模型进行介绍。


部署优化


在部署优化之前,首先明确一下 PointPillars 模型的基本结构,然后进一步讨论优化方向和优化思路。

PointPillars 的最大贡献是在 VoxelNet 中 Voxel 的基础上提出了一种改进版本的点云表征方法 Pillar ,可以将点云转换成伪图像,进而通过2D卷积实现目标检测。PointPillars 整个网络结构分为三个部分:


• Pillar Feature Net:将输入的点云转换为稀疏的Pseudo image;

• Backbone:处理Pseudo image得到高层的特征;

• Detection Head:检测和回归3D框。



量化精度优化


在前面有提到,点云数据具有不均匀的特征,这种分布特点的数据使用PTQ量化方式很大可能会有量化精度问题,因此在 PointPillars 的量化过程中,我们使用 Calibration+QAT 的量化方式来保证点云模型的量化精度。需要提到的是地平线的calibration对于大部分模型就可以达到预期的量化精度,少量模型在较小的QAT训练代价下可以达到量化精度。由于 PointPillars 中所有算子的量化在征程5平台上是完全支持的,得到初版的量化精度是非常简单的。


QAT量化方式是根据数据的分布选取量化系数,将fp32的数据截断到int8范围,由于点云数据分布不均匀,其表现在部分数据(点云坐标)的数值范围较大,部分(点到中心的距离)数值范围较小,这种情况对量化是很不友好的,因此在量化训练中,精度掉接近于 7 个点;为了使得数据分布处于均匀范围内,一般都会做归一化处理(归一化对浮点的精度也是有利的),因此,为了提升量化精度,我们增加了对点云特征的归一化处理。


    Pythondef normalize(self, features, norm_dims, norm_range):    # 做归一化    start = norm_range[norm_dims]    end = norm_range[norm_dims + len(norm_range) // 2]    features[:, :, norm_dims] = features[:, :, norm_dims] - start    features[:, :, norm_dims] = features[:, :, norm_dims] / (end - start)    return features


    经归一化处理后,量化精度损失在 1% 以内


    部署性能优化


    在 PointPillars 三个部分结构中:


    • Backbone 和 DetectionHead 中的算子均为普通的 Conv2d、ConvTranspose2d、BarchNorm2d、ReLU 等常规操作,且并非PointPillars部署的性能瓶颈。

    • 而从输入点云数据至 PillarFeatureNet 生成 Pseudo Image Feature 的这一过程中(见下图),涉及到高密度点云数据 Pillar 化 (Voxelization)、点云数据维度扩充增强(VoxelAugment)、点云特征提取(PFNLayer)、特征数据重排布(PillarScatter)等操作,逻辑运算量大且数据存取操作较多,在整个部署耗时中占了很大的比重,是点云模型部署着重优化的内容。


    从输入点云数据到生成 Pseudo Image Feature 的整体流程如下:



    接下来,我们展开对各个部分的优化方法。


    前处理优化


    前处理包括 Voxelization 操作和特征的扩维操作-VoxelAugment。我们将分析公版的实现在J5上部署的困难点,然后基于困难点介绍优化方式以及地平线对点云前处理的部署优化。


    Voxelization:体素化,是把三维空间中的点云数据转换到体素(voxel,即三维空间中的网格)。

    PointPillars中的 Pillars 可以看作是特殊( z 轴没有空间限制)的 voxel,为了与社区名称和代码实现名称相统一,我们下文统一称为 Voxelization、voxel。


    Voxelization部署分析


    假设用 (x, y, z, r) 表示点云数据中的一个点,其中(x, y, z) 为坐标,r 为点云的反射强度。

    PointPillars 中的 Pillar 化(一种特殊的 Voxelization 操作,是把 z 轴看作一个整体,把三维空间离散化为 x-y 平面中均匀间隔的网格)流程如下:



    从上图可以看出,在 Voxelization 过程中,需要依次、逐个对密集点云中的每个点进行判断,并将其划分入对应的 voxel 中,且每个 voxel 都需要存储点云中对应区域的信息。随着点云密度的增加,处理的体素数量也相应增多,导致需要更多的计算和内存资源,其计算复杂度可能导致较长的部署时间


    VoxelAugment部署分析


    继 voxelization 中把每个点云 point 划分到各个 pillar 中之后,公版 PointPillars 中对点云 point 做了特征增强,即前文提到的把每个 4 维点云 point 数据 (x, y, z, r) 根据点到中心的距离扩充到了 9 维 (x, y, z, r, xc, yc, zc, xp, yp) 。然而,这样的处理方式,无论是对量化精度还是部署性能方面,都存在一些不足:


    • 量化精度方面:前文已提到,这里不再赘述;

    • 部署性能方面:  在从 4 维扩充到 9 维时,对中心点距离的求解,增加了计算量和相应的耗时。而在我们的实验中发现,增加的后 5 维数据,实际对模型的浮点精度影响很小

    针对以上两个问题,下面介绍地平线的优化方法。


    VoxelAugment优化


    根据实验,原9维的方案会导致耗时增加,精度收益不大,因此在我们的改进方法中,点云 point 仅使用前4维 (x, y, z, r) ,并结合前文对量化精度的优化,做了归一化处理:


      Python# 伪代码,仅展示处理流程def _extend_dim(self, features, num_voxels, coors):# 直接取 (x, y, z, r) 四维    features[:, :, :3] = features[:, :, :3] - self.pc_range[:3]
        # 对 x, y, z 做归一化    features = normalize(feature, norm_dims=[0,1,2], norm_range=self.pc_range)    return features


      在优化后,该部分耗时减少了 4ms,精度上影响较小


      Voxelization优化


      对于点云的 voxelization 耗时问题,地平线提供了多种部署方式(包括编译器、ARM、DSP)。在 PointPillars 模型中,该部分被编译进模型中,为编译器优化实现。在使用上,plugin提供了外部接口,通过调用该算子就可以实现编译器的加速。


      使用方法:


        Pythonfrom horizon_plugin_pytorch.nn.quantized.functional_impl import (    _voxelization as horizon_voxelization,)voxels, coors, num_points_per_voxel = horizon_voxelization(    points,    voxel_size=self.voxel_size.to(device),    pc_range=self.pc_range.to(device),    max_points_per_voxel=self.max_points_in_voxel,    max_voxels=max_voxels,    use_max=is_deploy,)

        ARM和DSP的实现见AIBenchmark


        前处理集成和部署


        为了便于量化和部署,我们在 horizon_plugin_pytorch 和编译器中实现了 point_pillars_preprocess 算子集成了Voxelization 和 VoxelAugment操作。horizon_plugin_pytorch 中 point_pillars_preprocess 算子的调用方式为:


          Pythonfrom horizon_plugin_pytorch.nn.functional import point_pillars_preprocess
          voxel_features, coords = point_pillars_preprocess(    points_lst,    self.pc_range.to(device),    self.voxel_size.to(device),    self.max_voxels_num,    self.max_points_in_voxel,    is_deploy,    norm_range=self.norm_range.to(device),    norm_dims=self.norm_dims.to(device),)


          在 AIBenchmark 中对点云前处理也提供了ARM和DSP实现的方式。DSP具有强大的并行计算能力,能够同时处理多个数据,且具有快速读写内存的特点,利用 DSP 可以有效加速 Voxelization 过程,提高实时性能。例如,在 8.3 万点云数据量时,经 DSP 优化后,前处理耗时由 17.11 ms 降低至 6.28 ms,性能提升一倍。具体数据可见实验结果章节。


          特征提取层PFNLayer


          PointPillars 的 PFNLayer 做用是将每个包含D维特征的点(由前文可知,公版PointPillars 中 D=9,地平线参考模型中 D=4)用一个 Linear + BatchNorm1d + ReLU + max 的组合来进行特征提取,生成(C,P,N) 的张量。


          而地平线征程5最早针对的是以CNN为基础的图像处理,在编译器内部4d-Tensor是最高效的支持方式。如果不是4d-Tensor的话,编译器内部会主动转成4d(某些维度为1)来做,会多了很多无效的计算。我们可以使用常规的4维算子替换原来任意维度的设置,避免不必要的冗余计算。常见的替换方式如下,中间配合任意维度的reshape,permute来完成等价替换。因此,我们将公版中的 Linear + BatchNorm1d + ReLU + max 分别做了如下替换:


          N dims

          4dims

          nn.Linear

          nn.Conv2d

          nn.BacthNorm1d

          nn.BacthNorm2d

          torch.max

          nn.MaxPool2d


          关键代码


            Python
            # 原来的方式class PFNLayer(nn.Module):    def __init__():        self.linear = nn.Linear(in_channels, self.units, bias=False)        self.norm = nn.BatchNorm1d(self.units, **self.bn_kwargs)
               def forward(self, inputs: torch.Tensor):        x = self.linear(inputs)        x = (            self.norm(x.permute(0, 2, 1).contiguous())            .permute(0, 2, 1)            .contiguous()        )        x = F.relu(x)        x_max = torch.max(x, dim=1, keepdim=True)[0]return x_max # 更改后, 4d 算子的使用方式 class PFNLayer(nn.Module):    def __init__():        self.linear = nn.Conv2d(in_channels, self.units, kernel_size=1, bias=False)        self.norm = nn.BatchNorm2d(self.units, **bn_kwargs)        self.relu = nn.ReLU(inplace=True)        self.max_pool = nn.MaxPool2d(kernel_size=pool_size, stride=pool_size)
               def forward(self, inputs: torch.Tensor):        x = self.linear(inputs)        x = self.norm(x)        x = self.relu(x)        x_max = self.max_pool(x)        x_max = x_max.permute(0, 3, 2, 1).contiguous()  # (1, 1, P, C)        return x_max


            伪图像化


            PillarScatter 是实现伪图像转换的最后一个步骤,该部分将(1, 1, P, C) 的特征映射获得形如(C, H, W) 的伪图像。为了便于量化训练和上板推理优化,我们在 horizon_plugin_pytorch 和编译器中均实现了 point_pillars_scatter 算子。该算子由编译器内部完成,用户不需要感知。


            horizon_plugin_pytorch 中 point_pillars_scatter 算子的调用方式为:


              Pythonfrom horizon_plugin_pytorch.nn.functional import point_pillars_scatter
              pseudo_image_feature = point_pillars_scatter(voxel_features, coords, out_shape)


              优化总结


              相比于公版 PointPillars ,我们主要做了如下更改:


              • voxelization:为了便于量化和推理优化,horizon-plugin-pytorch 和编译器做了优化实现,并且为了进一步提升推理性能,我们也提供了 DSP实现,可以有效地加速点云处理;

              • point encoder(VoxelAugment):仅使用4维(公版9维),并做归一化处理,在浮点精度与公版相比几乎不掉点的前提下,提高了量化精度,并减少推理耗时 4ms;

              • PFNLayer:使用 Conv2d + BatchNorm2d + ReLU + MaxPool2d,替换原有的 Linear + BatchNorm1d + ReLU + torch.max,优化推理性能;

              • PillarScatter:horizon_plugin_pytorch 和编译器中内部集成实现,便于模型量化和推理优化。


              实验结果


              1.  PointPillars 在征程5平台性能数据


              数据集

              Kitti3D

              点云量

              15W

              点云范围

              [0, -39.68, -3, 69.12, 39.68, 1]

              Voxel size

              [0.16, 0.16, 4.0]

              最大点数

              100

              最大pillars数

              12000

              FPS

              116

              latency

              32.8ms(infer)+ 2.6ms(后处理)

              量化精度(浮/定)

              77.32/76.76

              latency数据中infer耗时为点云前处理和模型部分的总耗时。


              2. DSP优化点云前处理在征程5平台性能数据


              地平线 Albenchmark 中提供了点云前处理的DSP实现,可以有效地加速点云处理。本实验选取多组点云数据输入,通过对比前处理运行时长(latency)判断DSP的加速效果。


              模型

              有效数据

              原始方案

              DSP优化

              PointPillars

              83165

              17.11

              6.28

              9896

              5.65

              4.46

              68535

              14.79

              6.06

              71445

              15.49

              6.00

              有效数据:为在点云配置范围内的点云数,即参加实际运算的点云。


              从实验数据可以看出,DSP的并行计算的加速效果在点云处理上发挥显著,特别是在计算量大的情况下优化效率更高。


              3. 点云模型优化通用建议


              • 使用已封装的算子和BPU支持的算子(见附录算子支持列表)。推荐使用地平线实现的 point_pillars_scatter、point_pillars_preprocess 算子,该算子由编译器针对硬件特点做了优化,因此建议在模型搭建阶段使用已经封装好的算子。


              • 板端部署时(原始方案),padding数据设置为-100。板端做数据处理时建议将padding数据配置为-100,遇到[-100,-100,-100,-100]数据时会跳出循环,减少padding数据的遍历。


              • 对于点云数据,pillars_num较大,将大数据放到W维度提升计算效率。基于地平线硬件对齐规则,未对齐的数据会被padding导致算力浪费,为了避免此情况,建议将大数据放到W维度。


              • 若前处理存在性能瓶颈,尝试DSP方案。在工程部署中若发生性能瓶颈,可以考虑将点云处理放置DSP处理,合理利用和分配资源。


              • 对输入数据做归一化,更有利于量化。点云分布不均匀,在量化上大多数需要使用QAT量化,更好的数据分布对量化也会更友好。


              总结


              本文通过对 PointPillars 在地平线征程5上量化部署的优化,使得模型在该平台上用低于1%的量化精度损失,得到latency为32.8ms的部署性能,特别是点云的前处理,提供了多种部署方式以及优化数据。同时,通过 PointPillars 的部署经验,可以推广到所有的点云模型部署,最后给出点云模型在征程5上高效部署的优化建议。


              附录

              算法包获取

              使用文档

              算子支持列表

              .

              分享文章

              欢迎订阅地平线相关资讯,您可以随时取消订阅。

              立即订阅

              同意隐私政策,允许向我推送地平线的新闻、资讯及更多内容。

              提交成功!

              感谢您的订阅, 我们会第一时间推送地平线最新活动与资讯到您邮箱

              6-1.jpg 618db6f9-665a-4ec5-a04a-bb65a3df9030.jpg