01 概述

在自动驾驶感知算法中 BEV 感知成为热点话题,BEV 感知可以弥补 2D 感知的缺陷构建 3D “世界”,更有利于下游任务和特征融合。

地平线集成了基于 bev 的纯视觉算法,目前已支持 ipm-based 、lss-based、 transformer-based(Geometry-guided Kernel Transformer、detr3d、petr) 的多种 bev 视觉转换方法。

本文为 camera calibration free 的 transformer-based 的 BEV 感知算法的介绍和使用说明。

该示例为参考算法,仅作为在 征程 5 上模型部署的设计参考,非量产算法

02 性能精度指标

模型配置:

图片

性能精度表现:

图片

注:Nuscenes 数据集官方介绍:Nuscenes

03 模型介绍

3.1 模型框架

图片

bev_cft 模型结构图

bev_cft 使用多视图的当前帧的 6 个 RGB 图像作为输入。输出是目标的 3D Box 结果。多视角图像首先使用 2D 主干获取 2D 特征。然后投影到 3D BEV 视角。接着对 BEV feature 编码获取深层 BEV 特征。最后,接上任务特定的 head,输出检测结果。

模型主要包括以下部分:

  • Part1—2D Image Encoder:图像特征提取层。使用 2D 主干网络(efficientnet)和 FastSCNN 输出不同分辨率的特征图。返回最后一层–上采样至 1/128 原图大小层,用于下一步投影至 3D 坐标系中。

  • Part2—View transformer:采用 CFT 方式完成 img 2D 到 BEV 3D 的转换。

  • Part3—Bev transforms:对 BEV 特征做数据增强,仅发生在训练阶段。

  • Part4—3D BEV Encoder:BEV 特征提取层。

  • Part5—BEV Decoder

    使用 DepthwiseSeparableCenterPointHead 进行 3D 目标检测任务,检测的类别为 [“car”,“truck”,“bus”,“barrier”,“bicycle”,“pedestrian”]。

3.2 源码说明

config文件

**configs/bev/bev_cft_efficientnetb3_nuscenes.py** 为该模型的配置文件,定义了模型结构、数据集加载,和整套训练流程,所需参数的说明在算子定义中会给出。

配置文件主要内容包括:

#基础参数配置
task_name = "bev_cft_efficientnetb3_nuscenes"
batch_size_per_gpu = 2
device_ids = [0]
#bev参数配置
resize_shape = (3, 792, 1408)
data_shape = (3, 512, 1408)
grid_size = (64, 64)

# 模型结构定义
model = dict(
    type="ViewFusion",
    backbone=dict(
        type="efficientnet",
        model_type="b3",
        ...
    ),
    neck=dict(
        type="BiFPN",
        ...
    ),
    view_transformer=dict(
        type="CFTTransformer", #cft transform
        ...
    ),
    bev_transforms=[...],
    bev_encoder=dict(
        type="BevEncoder",
        ...
    ),
    bev_decoders=[
         dict(
            type="BevDetDecoder",
            ...
        )
    ],
)

deploy_model = dict(
...
)
...
# 数据加载 
data_loader = dict(
    type=torch.utils.data.DataLoader,
    ...
)

val_data_loader = dict(...)

#不同step的训练策略配置
float_trainer=dict(...)
calibration_trainer=dict(...)
qat_trainer=dict(...)
int_infer_trainer=dict(...)
#不同step的验证
float_predictor=dict(...)
calibration_predictor=dict(...)
qat_predictor=dict(...)
int_infer_predictor=dict(...)
#编译配置
compile_cfg = dict(
    march=march,
    ...
)

注:如果需要复现精度,config 中的训练策略最好不要修改。否则可能会有意外的训练情况出现。

img_encoder

来自 6 个 view 的 image 作为输入通过共享的 backbone(efficientnet-b3)和 neck(BiFPN)输出经过 encoder 后的 feature,feature_shape 为(6*B,C,1/128H,1/128W)。

encoder 即对多个 view 的 img_feature 做特征提取,过程见下图:

图片

对应代码:**hat/models/backbones/efficientnet.py** hat/models/necks/bifpn.py

view_transformer

view_transformer 采用 CFT(camera free transformer)映射的方法,把图像视角的 img_features 转换到 bev_features。

BEV_shape 为[H’,W’]为[64,64],其转换过程见下图:

图片

cft 框架图

view_transformer 对应代码:

**hat/models/task_modules/view_fusion/cft_transformer.pyCFTTransformer**。

class CFTTransformer(ViewTransformer):
    ...
    def forward(self, feats: Tensor, data: Tensor,...):
        query_pos, key_pos, ref_h_embed, ref_h = self._position_embed(feats)

        bs = feats.shape[0] // self.num_views
        key_pos = key_pos.repeat(bs, 1, 1, 1)
        tgt = (
            self.query_embed.weight.view(
                self.grid_size[0], self.grid_size[1], -1
            )
            .repeat(bs, 1, 1, 1)
            .permute(0, 3, 1, 2)
            .contiguous()
        )

        key_pos = self.key_pos_quant(key_pos)
        feats = self.encoder(feats, pos=key_pos)
        tgt = self.tgt_quant(tgt)
        query_pos = self.query_pos_quant(query_pos)
        ref_h_embed = self.ref_h_quant(ref_h_embed)
        feats = self.decoder(
            feats,
            tgt=tgt,
            query_pos=query_pos,
            key_pos=key_pos,
            ref_h_embed=ref_h_embed,
        )
        return feats, ref_h

根据框架图,在 view_transformer 流程中可以分为两部分:

  • position-Aware Enhancement:对位置编码进行强化,对 BEV 2D 和 content 编码,并通过 PA 网络实现特征增强
  • view-Aware Attention:对图像融合坐标位置编码,增强特征
position-Aware Enhancement

Step 1 : postition_embedding

该部分为 BEV 2D 坐标的编码,编码为可学习、参数可更新的**PositionEmbeddingLearned2D**。

class PositionEmbeddingLearned2D(nn.Module):
    ...
    def forward(self, patch: Tensor) -> Tensor:
        """
        Forward pass of the PositionEmbeddingLearned2D module.

        Args:
            patch: Input tensor.

        Returns:
            pos: Output tensor.
        """
        hw, _ = patch.shape
        hw = torch.tensor(hw)
        h = w = torch.sqrt(hw).int()
        i = torch.arange(h, device=patch.device)
        j = torch.arange(w, device=patch.device)
        x_emb = self. col_embed(i)
        y_emb = self.row_embed(j)
        pos = x_emb.unsqueeze(1).repeat(1, w, 1) + y_emb.unsqueeze(0).repeat(
            h, 1, 1
        )
        return pos.permute(2, 0, 1).contiguous().unsqueeze(0)

Step 2 : reference height embedding

该步骤为对高度 reference height 的编码。根据位置编码 query_pos 来做高度的预测 ref_h ,然后对高度 ref_h 做正弦函数编码。计算公式为:

图片

对应代码为:

def _position_embed(
        self, feats: Tensor
    ) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
        ...        
        height_range = [self.position_range[2], self.position_range[5]]
        ref_h = self.ref_h_head(query_pos)
        ref_h = (
            ref_h.sigmoid() * (height_range[1] - height_range[0])
            + height_range[0]
        )
        ref_h_embed = gen_sineembed_for_position(
            ref_h, height_range, self.embed_dims
        )

ref_h_head 为一个输出 channel 为 1 的 mlp:

self.ref_h_head = MLP(
            input_channels=embed_dims,
            output_channels=1,
            feedforward_channels=embed_dims,
        )

**gen_sineembed_for_position实现在hat/models/task_modules/view_fusion/cft_transformer.py**。

Step 3:结合 BEV 的 content query,细化目标的 height

图片

为了细化高度,引入 BEV 的 content 来提取目标的高度信息:

图片

BEV 的 content 为预设的 query。num_query 为 bevsize 大小。

num_queries = self.grid_size[0] * self.grid_size[1]
self.query_embed = nn.Embedding(num_queries, self.embed_dims)

tgt = (
    self.query_embed.weight.view(
        self.grid_size[0], self.grid_size[1], -1
    )
    .repeat(bs, 1, 1, 1)
    .permute(0, 3, 1, 2)
    .contiguous()
)

Content query 经过 MLP 后与 Ref_h 做 mul,然后与 query_pos 做 add。代码:

class Decoder(nn.Module):
    ...
    def forward(
        self,
        x: Tensor,
        tgt: Tensor,
        query_pos: Tensor,
        key_pos: Tensor,
        ref_h_embed: Tensor,
    ) -> Tensor:
        ...
        for i, decoder in enumerate(self.decoders):
            if i > 0:
                pos_transformation = self.query_trans_pos(tgt)
                ref_h_embed = self.mul.mul(ref_h_embed, pos_transformation)
            ref_h_embed = ref_h_embed + query_pos
            tgt = decoder(
                x,
                tgt=tgt,
                query_pos=query_pos,
                key_pos=key_pos,
                ref_h_embed=ref_h_embed,
            )
        return tgtclass Decoder(nn.Module):    ...    def forward(        self,        x: Tensor,        tgt: Tensor,        query_pos: Tensor,        key_pos: Tensor,        ref_h_embed: Tensor,    ) -> Tensor:        ...        for i, decoder in enumerate(self.decoders):            if i > 0:                pos_transformation = self.query_trans_pos(tgt)                ref_h_embed = self.mul.mul(ref_h_embed, pos_transformation)            ref_h_embed = ref_h_embed + query_pos            tgt = decoder(                x,                tgt=tgt,                query_pos=query_pos,                key_pos=key_pos,                ref_h_embed=ref_h_embed,            )        return tgt
view-Aware Attention

该层对图像做 encoder。融合 position 经过一个 self-attention 模块做特征增强。

class CFTTransformer(ViewTransformer):
    ...
    def forward(self, feats: Tensor, data: Tensor,...):
        ... 
        query_pos, key_pos, ref_h_embed, ref_h = self._position_embed(feats)
        bs = feats.shape[0] // self.num_views
        key_pos = key_pos.repeat(bs, 1, 1, 1)
        ...
        key_pos = self.key_pos_quant(key_pos)         
        feats = self.encoder(feats, pos=key_pos)
        ...

其中位置编码 key_pos 的方式为:

self.pos_embedding = PositionEmbeddingLearned(
    num_pos_feats=[100, 100, 56], num_pos=num_pos
)

详细实现见 PositionEmbeddingLearned。

图像的 encoder 操作为:

class Encoderlayer(nn.Module):
    ...    
    def forward(self, x: Tensor, pos: Tensor) -> Tensor:
        x = self.norm1(x)
        q = k = self.pos_add.add(x, pos)
        tgt, _ = self.self_attns(query=q, key=k, value=x)
        tgt = self.dropout1_add.add(x, self.dropout1(tgt))
        tgt2 = self.norm2(tgt)
        tgt2 = self.ffn(tgt2)
        tgt2 = self.dropout2_add.add(tgt, self.dropout2(tgt2))
        return tgt2

在公版中,为了减少计算量和内存消耗,在 Decoder 的自注意力计算中做了分组的 Attention,在做 J5 部署时该部分会用到大量的 slice,IO 操作导致带宽资源紧张,因此,地平线版本未做 part attention。

class Decoder(nn.Module):
    ...
    def forward(
        self,
        x: Tensor,
        tgt: Tensor,
        query_pos: Tensor,
        key_pos: Tensor,
        ref_h_embed: Tensor,
    ) -> Tensor:

        for i, decoder in enumerate(self.decoders):
            if i > 0:
                pos_transformation = self.query_trans_pos(tgt)
                ref_h_embed = self.mul.mul(ref_h_embed, pos_transformation)
            ref_h_embed = ref_h_embed + query_pos
            tgt = decoder(
                x,
                tgt=tgt,
                query_pos=query_pos,
                key_pos=key_pos,
                ref_h_embed=ref_h_embed,
            )
        return tgt

decoder 为 cross-attention 操作,num_layers 为 2:

class Decoderlayer(nn.Module):
    ...
    def forward(
        self,
        feat: Tensor,
        tgt: Tensor,
        query_pos: Tensor,
        key_pos: Tensor,
        ref_h_embed: Tensor,
    ):

        n, c, h, w = feat.shape
        bs = n // self.num_views
        feat = feat.view(-1, self.num_views, c, h, w)
        key_pos = key_pos.view(-1, self.num_views, c, h, w)

        feat = feat.permute(0, 2, 1, 3, 4).contiguous().view(bs, c, -1, w)
        key_pos = (
            key_pos.permute(0, 2, 1, 3, 4).contiguous().view(bs, c, -1, w)
        )
        query = self.Qadd.add(tgt, query_pos)

        query = self.Qadd2.add(query, ref_h_embed)
        key = self.Kadd.add(feat, key_pos)
        tgt2, _ = self.cross_attns(query=query, key=key, value=feat)

        tgt = self.dropout1_add.add(tgt, self.dropout1(tgt2))
        tgt = self.norm1(tgt)
        tgt2 = self.ffn(tgt)
        tgt = self.dropout2_add.add(tgt, self.dropout2(tgt2))
        tgt = self.norm2(tgt)
        return tgt
bev_head

检测为多 task 检测,主要分为:

tasks = [
    dict(
        name="bbos",
        num_class=10,
        class_names=[
            "car",
            "truck",
            "construction_vehicle",
            "bus",
            "trailer",
            "barrier",
            "motorcycle",
            "bicycle",
            "pedestrian",
            "traffic_cone",
        ],
    )
]

在 nuscenes 数据集中,目标的类别一共被分为了 6 个大类,网络给每一个类都分配了一个 head,装在 headlist 中,而每个 head 内部都为预测的参数。

bev_det 的 head 为**DepthwiseSeparableCenterPointHead**

对应代码:**hat/models/task_modules/centerpoint/head.py**

class DepthwiseSeparableCenterPointHead(CenterPointHead):
    def _make_conv(
        self,
        ...
    ):
        pw_norm_layer = nn.BatchNorm2d(in_channels, **self.bn_kwargs)
        pw_act_layer = nn.ReLU(inplace=True)

        return SeparableConvModule2d(
            in_channels=in_channels,
            ...
        )

    def _make_task(self, **kwargs):
        return DepthwiseSeparableTaskHead(**kwargs)

class CenterPointHead(nn.Module):
    def __init__(self,...):
        self.shared_conv = nn.Sequential(
            *(
                self._make_conv(
                    in_channels=in_channels if i == 0 else share_conv_channels,
                    ...
                )
                for i in range(share_conv_num)
            )
        )  
        #head module  
        for num_cls in num_classes:
            heads = copy.deepcopy(common_heads)
            heads.update({"heatmap": (num_cls, num_heatmap_convs)})
            task_head = self._make_task(
                ...,
            )
            self.task_heads.append(task_head)
 
    def forward(self, feats):
        rets = []
        feats = feats[0]
        feats = self.shared_conv(feats)
        for task in self.task_heads:
            rets.append(task(feats))

forward 时,经过共享的 SeparableConv 后,将 feature 再分别传入 task_heads 做 task_pred。

在**hat/models/task_modules/centerpoint/head.py**的 TaskHead 对不同的 task 定义 conv_layers:

class DepthwiseSeparableTaskHead(TaskHead):
    def _make_conv(
        self,
        in_channels,
        ...
    ):
        return SeparableConvModule2d(
            in_channels=in_channels,
            ...
        )

class TaskHead(nn.Module):
    def __init__(...):
         ...    
         for head in self.heads:
            classes, num_conv = self.heads[head]
            ...
            #head_conv
            for _ in range(num_conv - 1):
                conv_layers.append(
                    self._make_conv(
                    ...
                    )
                )
                c_in = head_conv_channels
            #cls_layer
            conv_layers.append(
                ConvModule2d(
                    in_channels=head_conv_channels,
                    out_channels=classes,
                    ...
                )
            )
            conv_layers = nn.Sequential(*conv_layers)
    
    def forward(self, x):
        ret_dict = {}
        for head in self.heads:
            ret_dict[head] = self.dequant(self.__getattr__(head)(x))
        return ret_dict
bev_decoder

在检测任务中使用 CenterPointDecoder,具体实现流程见下图:

图片

对应代码:**hat/models/task_modules/centerpoint/decoder.py**

04 浮点模型训练

4.1 Before Start

4.1.1 发布物及环境部署***

Step 1:获取发布物

下载 OE 包:

**horizon_j5_open_explorer_v$version$.tar.gz**,获取方式见地平线开发者社区 OpenExplorer 算法工具链 版本发布

Step 2:解压发布包

tar -xzvf horizon_j5_open_explorer_v$version$.tar.gz

解压后文件结构如下:

|-- bsp
|-- ddk
|   |-- package
|   `-- samples
|       |-- ai_benchmark
|       |-- ai_forward_view_sample
|       |-- ai_toolchain
|       |   |-- ...
|       |   |-- horizon_model_train_sample
|       |   `-- model_zoo
|       |-- model_zoo
|       `-- vdsp_rpc_sample
|-- README-CN
|-- README-EN
|-- resolve_all.sh
`-- run_docker.sh

其中**horizon_model_train_sample为**参考算法模块,包含以下模块:

|-- horizon_model_train_sample  #参考算法示例
|   |-- plugin_basic  #qat 基础示例
|   `-- scripts  #模型配置文件、运行脚本

Step 3:拉取 docker 环境

docker pull openexplorer/ai_toolchain_ubuntu_20_j5_gpu:v$version$
#启动容器,具体参数可根据实际需求配置
#-v 用于将本地的路径挂载到 docker 路径下
nvidia-docker run -it --shm-size="15g" -v `pwd`:/WORKSPACE openexplorer/ai_toolchain_ubuntu_20_j5_gpu:v$version$

4.1.2 数据集准备***

4.1.2.1 数据集下载

进入nuscenes 官网,根据提示完成账户的注册,下载 Full dataset(v1.0)、CAN bus expansion 和 Map expansion(v1.3)这三个项目下的文件。下载后的压缩文件为:

|-- nuScenes-map-expansion-v1.3.zip
|-- can_bus.zip
|-- v1.0-mini.tar
|-- v1.0-trainval01_blobs.tar
|-- ...
|-- v1.0-trainval10_blobs.tar
`-- v1.0-trainval_meta.tar

Full dataset(v1.0)包含多个子数据集,如果不需要进行 v1.0-trainval 数据集的浮点训练和精度验证,可以只下载 v1.0-mini 数据集进行小场景的训练和验证。

将下载完成的 v1.0-trainval01_blobs.tar~v1.0-trainval10_blobs.tar、v1.0-trainval_meta.tar 和 can_bus.zip 进行解压,解压后的目录如下所示:

|--nuscenes
    |-- can_bus #can_bus.zip解压后的目录
    |-- samples #v1.0-trainvalXX_blobs.tar解压后的目录
    |   |-- CAM_BACK
    |   |-- ...
    |   |-- CAM_FRONT_RIGHT
    |   |--  ...
    |   `-- RADAR_FRONT_RIGHT
    |-- sweeps
    |   |-- CAM_BACK
    |   |-- ...
    |   |-- CAM_FRONT_RIGHT
    |   |--  ...
    |   `-- RADAR_FRONT_RIGHT
    |-- v1.0-trainval #v1.0-trainval_meta.tar解压后的数据
        |-- attribute.json
        |    ...
        `-- visibility.json
4.1.2.2 数据集打包***

进入 horizon_model_train_sample/scripts 目录,使用以下命令将训练数据集和验证数据集打包,格式为 lmdb:

#pack train_Set
python3 tools/datasets/nuscenes_packer.py --src-data-dir /WORKSPACE/nuscenes/ --pack-type lmdb --target-data-dir /WORKSPACE/tmp_data/nuscenes/v1.0-trainval --version v1.0-trainval --split-name train
#pack val_Set
python3 tools/datasets/nuscenes_packer.py --src-data-dir /WORKSPACE/nuscenes/ --pack-type lmdb --target-data-dir /WORKSPACE/tmp_data/nuscenes/v1.0-trainval --version v1.0-trainval --split-name val

–src-data-dir 为解压后的 nuscenes 数据集目录;–target-data-dir 为打包后数据集的存储目录;

–version 选项为[“v1.0-trainval”, “v1.0-test”, “v1.0-mini”],如果进行全量训练和验证设置为 v1.0-trainval,如果仅想了解模型的训练和验证过程,则可以使用 v1.0-mini 数据集;

v1.0-test 数据集仅为测试场景,未提供注释。

全量的 nuscenes 数据集较大,打包时间较长。每打包完 100 张会在终端有打印提示,其中 train 打包约 28100 张,val 打包约 6000 张。

数据集打包命令执行完毕后会在**target-data-dir下生成train_lmdbval_lmdbtrain_lmdbval_lmdb就是打包之后的训练数据集和验证数据集为 config 中的data_rootdir**。

|-- tmp_data 
|   |-- nuscenes 
|   |   |-- v1.0-trainval
|   |   |   |-- train_lmdb  #打包后的train数据集
|   |   |   |   |-- data.mdb
|   |   |   |   `-- lock.mdb
|   |   |   `-- val_lmdb   #打包后的val数据集
|   |   |   |   |-- data.mdb
|   |   |   |   `-- lock.mdb
4.1.2.3 meta 文件夹构建

在**tmp_data/nuscenes** 下创建 meta 文件夹,将**v1.0-trainval_meta.tar压缩包解压至 meta,得到meta/maps文件夹,再将nuScenes-map-expansion-v1.3.zip压缩包解压至meta/maps**文件夹下,解压后的目录结构为:

|-- tmp_data 
|   |-- nuscenes 
|   |   |-- meta
|   |   |   |-- maps        #nuScenes-map-expansion-v1.3.zip解压后的目录
|   |   |   |   |-- 36092f0b03a857c6a3403e25b4b7aab3.png
|   |   |   |   |-- ...
|   |   |   |   |-- 93406b464a165eaba6d9de76ca09f5da.png
|   |   |   |   |-- prediction
|   |   |   |   |-- basemap
|   |   |   |   |-- expansion
|   |   |   |-- v1.0-trainval  #v1.0-trainval_meta.tar解压后的目录
|   |   |       |-- attribute.json
|   |   |           ...
|   |   |       |-- visibility.json
|   |   `-- v1.0-trainval 
|   |   |   |-- train_lmdb  #打包后的train数据集
|   |   |   `-- val_lmdb   #打包后的val数据集

4.1.3 config 配置

在进行模型训练和验证之前,需要对 configs 文件中的部分参数进行配置,一般情况下,我们需要配置以下参数:

  • device_ids、batch_size_per_gpu:根据实际硬件配置进行 device_ids 和每个 gpu 的 batchsize 的配置;
  • ckpt_dir:浮点、calib、量化训练的权重路径配置,权重下载链接在 config 文件夹下的 README 中;
  • data_rootdir:2.1.2.2 中打包的数据集路径配置;
  • meta_rootdir :2.1.2.3 中创建的 meta 文件夹的路径配置;
  • float_trainer 下的 checkpoint_path:浮点训练时 backbone 的预训练权重所在路径,可以使用 README 的# Backbone Pretrained ckpt 中 ckpt download 提供的 float-checkpoint-best.pth.tar 权重文件。

4.2 浮点模型训练

config 文件中的参数配置完成后,使用以下命令训练浮点模型:

python3 tools/train.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --stage float

float 训练后模型 ckpt 的保存路径为 config 配置的 ckpt_callback 中 save_dir 的值,默认为 ckpt_dir。

4.3 浮点模型精度验证

浮点模型训练完成以后,可以使用以下命令验证已经训练好的浮点模型精度:

python3 tools/predict.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --stage float

验证完成后,会在终端打印浮点模型在验证集上检测精度,如下所示:

Per-class results:
Object Class    AP      ATE     ASE     AOE     AVE     AAE
car     0.458   0.552   0.157   0.188   1.263   0.230
...
2023-12-19 17:47:02,796 INFO [nuscenes_metric.py:349] Node[0] NDS: 0.3280, mAP:0.2481
...
2023-06-06 18:24:10,513 INFO [mean_iou.py:170] Node[0] ~~~~ MeanIOU Summary metrics ~~~~
car_AP: [0.5]:0.1182  [1.0]:0.3794  [2.0]:0.6097  [4.0]:0.7232
...
2023-12-19 17:47:03,046 INFO [metric_updater.py:360] Node[0] Epoch[0] Validation bev_cft_efficientnetb3_nuscenes: NDS[0.3280] 
2023-12-19 17:47:03,058 INFO [logger.py:176] Node[0] ==================================================END PREDICT==================================================
2023-12-19 17:47:03,058 INFO [logger.py:176] Node[0] ==================================================END FLOAT PREDICT==================================================

05 模型量化和编译

完成浮点训练后,还需要进行量化训练和编译,才能将定点模型部署到板端。地平线对该模型的量化采用 horizon_plugin 框架,经过 Calibration+QAT 量化训练后,使用**compile的工具将量化模型编译成可以上板运行的hbm**文件。

5.1 Calibration

模型完成浮点训练后,便可进行 Calibration。calibration 在 forward 过程中通过统计各处的数据分布情况,从而计算出合理的量化参数。通过运行下面的脚本就可以开启模型的 Calibration 过程:

python3 tools/train.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --stage calibration

5.2 Calibration 模型精度验证

Calibration 完成以后,可以使用以下命令验证经过 calib 后模型的精度:

python3 tools/predict.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --stage calibration

验证完成后,会在终端输出 calib 模型在验证集上检测精度,格式见 2.3。

5.3 量化模型训练

Calibration 完成后,就可以加载 calib 权重开启模型的量化训练。量化训练其实是在浮点训练基础上的 finetue,具体配置信息在 config 的 qat_trainer 中定义。

量化训练的时候,初始学习率设置为浮点训练的十分之一,训练的 epoch 次数也大大减少。和浮点训练的方式一样,将 checkpoint_path 指定为训好的 calibration 权重路径。

通过运行下面的脚本就可以开启模型的 qat 训练:

python3 tools/predict.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --stage qat

5.4 量化模型精度验证

Calibration 完成以后,可以使用以下命令验证经过 calib 后模型的精度:

#qat模型精度验证python3 tools/predict.py --stage qat--config configs/bev/bev_cft_efficientnetb3_nuscenes.py

验证完成后,会在终端输出 calib 模型在验证集上检测精度,格式见 2.3。

5.5 量化模型精度验证

指定 calibration-checkpoint 后,通过运行以下命令进行量化模型的精度验证:

python3 tools/predict.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --stage int_infer

qat 模型的精度验证对象为插入伪量化节点后的模型(float32);quantize 模型的精度验证对象为定点模型(int8),验证的精度是最终的 int8 模型的真正精度,这两个精度应该是十分接近的。

5.6 仿真上板精度验证

除了上述模型验证之外,我们还提供和上板完全一致的精度验证方法,可以通过下面的方式完成:

python3 tools/align_bpu_validation.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py

5.7 量化模型编译

在量化训练完成之后,可以使用**compile_perf.py脚本将量化模型编译成可以板端运行的hbm**模型,同时该工具也能预估在 BPU 上的运行性能,compile_perf 脚本使用方式如下:

python3 tools/compile_perf.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --out-dir ./ --opt 3

opt 为优化等级,取值范围为 0~3,数字越大优化等级越高,编译时间更长,但部署性能更好。compile_perf 脚本将生成。html 文件和。hbm 文件(compile 文件目录下),。html 文件为 BPU 上的运行性能,。hbm 文件为上板实测文件。

运行后,ckpt_dir 的 compile 目录下会产出以下文件。

|-- compile 

|   |-- .html #模型在bpu上的静态性能数据 

|   |-- .json  

|   |-- model.hbm  #板端部署的模型

|   |-- model.hbir #编译过程的中间文件

​   `-- model.pt   #模型的pt文件

06 其他工具

6.1 结果可视化

如果你希望可以看到训练出来的模型对于单帧的检测效果,我们的 tools 文件夹下面同样提供了预测及可视化的脚本,你只需要运行以下脚本即可:

python3 tools/infer.py --config configs/bev/bev_cft_efficientnetb3_nuscenes.py --save-path ./

可视化结果将会在 save-path 路径下输出。

avatar

07 板端部署

7.1 上板性能实测

使用**hrt_model_exec perf工具将生成的。hbm 文件上板做 BPU 性能 FPS 实测,hrt_model_exec perf**参数如下:

hrt_model_exec perf --model_file {model}.hbm \     
                    --thread_num 8 \
                    --frame_count 2000 \
                    --core_id 0 \
                    --profile_path '.'
Logo

加入社区

更多推荐