MapTROE Training

The MapTROE reference algorithm is developed based on Horizon Algorithm Toolkit (HAT, Horizon's own deep learning algorithm toolkit). The training config for MapTROE is located under configs/map/ path. The following part takes configs/map/maptroe_henet_tinym_bevformer_nuscenes.py as an example to describe how to configure and train MapTROE model.

Training Process

If you just want to simply train the MapTROE model, then you can read this section first.

Similar to other tasks, HAT performs all training tasks and evaluation tasks in the form of tools + config.

After preparing the original dataset, take the following process to complete the whole training process.

Dataset Preparation

Here is an example of the nuscense dataset, which can be downloaded from https://www.nuscenes.org/nuscenes . Also, in order to improve the speed of training, we have done a packing of the original jpg format dataset to convert it to lmdb format. Just run the following script and it will be successful to achieve the conversion.

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

The above two commands correspond to transforming the training dataset and the validation dataset, respectively. In addition, the MapTROE model also requires standand definition map (SD map) for map fusion to improve model accuracy, which can be obtained from the Open Street Map(OSM) https://www.openstreetmap.org. After the packing is completed, the file structure in the data directory should look as follows.

tmp_data
    |-- nuscenes
    |-- meta
    |-- osm
    |-- v1.0-trainval
        |-- train_lmdb
        |-- val_lmdb

The train_lmdb and val_lmdb are the training and validation datasets after packaging, and are the datasets that the network will eventually read. metas contains the map information used during model training and validation. osm contains the sd map information used in map fusion.

Model Training

Once datasets are ready, you can start training the floating-point FCOS-efficientnet detection network.

If you simply want to start such a training task, just run the following command:

python3 tools/train.py --stage float --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py
python3 tools/train.py --stage calibration --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py
python3 tools/train.py --stage qat --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py

The above two commands are respectively for the training of the floating-point model and the fixed-point model. The fixed-point model training needs to be based on the trained fixed-point model. For details on this, please read the Quantized Awareness Training (QAT) section.

Export the Fixed-point Model

Once you've completed your qat training, you can start exporting your quantized model. You can export it with the following command:

python3 tools/export_hbir.py --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py

Model Validation

After completing the training, we get the trained float, calibration, and qat model. Similar to the training method, we can use the same method to complete metrics validation on the trained model and get the metrics of Float, Calibration, and QAT respectively.

python3 tools/predict.py --stage "float" --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py
python3 tools/predict.py --stage "calibration" --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py
python3 tools/predict.py --stage "qat" --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py

Similar to the model training, we can use --stage followed by float, calibration, or qat to validate the specific model respectively.

The following command can be used to verify the accuracy of a quantized model, but it should be noted that hbir must be exported first:

python3 tools/predict.py --stage "int_infer" --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py

Model Inference and Results Visualization

HAT provides the infer_hbir.py script to visualize the inference results for the quantized model:

python3 tools/infer_hbir.py --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py --model-inputs ${model_inputs} --save-path ${save_path} --use-dataset

python3 tools/infer_hbir.py --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py --model-inputs ${model_inputs} --save-path ${save_path}

Fixed-point Model Checking and Compilation

The quantization training toolchain integrated in HAT is mainly prepared for the Horizon computing platform, so it is necessary to check and compile the quantized models. We provide a model checking interface in HAT, which can be used to check whether the defined quantization model can run normally on the BPU before training:

python3 tools/model_checker.py --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py

After the model is trained, you can use the compile_perf_hbir script to compile the quantized model into an HBM file that supports on-board running.

python3 tools/compile_perf_hbir.py --config configs/map/maptroe_henet_tinym_bevformer_nuscenes.py

The above is the whole process from data preparation to the generation of quantized and deployable models.

Training Details

In this note, we explain some things that need to be considered for model training, mainly including settings related to config.

Model Construction

model = dict(
    type="MapTROE",
    out_indices=(-1,),
    sd_map_fusion=True,
    backbone=dict(
        type="HENet",
        in_channels=3,
        block_nums=depth,
        embed_dims=width,
        attention_block_num=attention_block_num,
        mlp_ratios=mlp_ratios,
        mlp_ratio_attn=mlp_ratio_attn,
        act_layer=act_layer,
        use_layer_scale=use_layer_scale,
        layer_scale_init_value=1e-5,
        num_classes=1000,
        include_top=False,
        extra_act=extra_act,
        final_expand_channel=final_expand_channel,
        feature_mix_channel=feature_mix_channel,
        block_cls=block_cls,
        down_cls=down_cls,
        patch_embed=patch_embed,
        stage_out_norm=True,
    ),
    neck=dict(
        type="FPN",
        in_strides=[32],
        in_channels=[384],
        out_strides=[32],
        out_channels=[bev_embed_dims],
        bn_kwargs=dict(eps=1e-5, momentum=0.1),
    ),
    view_transformer=dict(
        type="SingleBevFormerViewTransformer",
        bev_h=bev_h_,
        bev_w=bev_w_,
        pc_range=point_cloud_range,
        num_points_in_pillar=4,
        embed_dims=bev_embed_dims,
        queue_length=queue_length,
        in_indices=(-1,),
        single_bev=single_bev,
        use_lidar2img=use_lidar2img,
        max_camoverlap_num=max_camoverlap_num,
        virtual_bev_h=int(bev_sparse_rate * bev_h_),
        virtual_bev_w=bev_w_,
        positional_encoding=dict(
            type="PositionEmbeddingLearned",
            num_pos_feats=bev_embed_dims // 2,
            row_num_embed=bev_h_,
            col_num_embed=bev_w_,
        ),
        encoder=dict(
            type="SingleBEVFormerEncoder",
            num_layers=1,
            return_intermediate=False,
            bev_h=bev_h_,
            bev_w=bev_w_,
            embed_dims=bev_embed_dims,
            encoder_layer=dict(
                type="SingleBEVFormerEncoderLayer",
                embed_dims=bev_embed_dims,
                selfattention=dict(
                    type="HorizonMultiScaleDeformableAttention",
                    embed_dims=bev_embed_dims,
                    num_levels=1,
                    grid_align_num=10,
                    batch_first=True,
                    feats_size=[[bev_w_, bev_h_]],
                ),
                crossattention=dict(
                    type="HorizonSpatialCrossAttention",
                    max_camoverlap_num=max_camoverlap_num,
                    bev_h=bev_h_,
                    bev_w=bev_w_,
                    deformable_attention=dict(
                        type="HorizonMultiScaleDeformableAttention3D",
                        embed_dims=bev_embed_dims,
                        num_points=8,
                        num_levels=_num_levels_,
                        grid_align_num=20,
                        feats_size=[[25, 15]],
                    ),
                    embed_dims=bev_embed_dims,
                ),
                dropout=0.1,
            ),
        ),
    ),
    osm_encoder=dict(
        type="ConvDown",
        in_dim=1,
        mid_dim=hidden_dim // 2,
        out_dim=hidden_dim,
        quant_input=True,
    ),
    bev_fusion=dict(
        type="MapFusion",
        input_dim=bev_embed_dims,
        embed_dims=hidden_dim,
        bev_h=bev_h_,
        bev_w=bev_w_,
        bev_down=dict(
            type="ConvDown",
            in_dim=bev_embed_dims,
            mid_dim=bev_embed_dims,
            out_dim=bev_embed_dims,
            quant_input=False,
        ),
        fusion_up=dict(
            type="ConvUp",
            in_dim=2 * hidden_dim,
            mid_dim=bev_embed_dims,
            out_dim=bev_embed_dims,
        ),
    ),
    bev_decoders=[
        dict(
            type="MapInstanceDetectorHead",
            in_channels=bev_embed_dims,
            num_cam=6,
            bev_h=bev_h_,
            bev_w=bev_w_,
            embed_dims=head_embed_dims,
            num_vec_one2one=num_vec_one2one,
            num_vec_one2many=num_vec_one2many,
            k_one2many=6,
            num_pts_per_vec=fixed_ptsnum_per_pred_line,
            num_pts_per_gt_vec=fixed_ptsnum_per_gt_line,
            transform_method="minmax",
            gt_shift_pts_pattern="v2",
            code_size=2,
            num_classes=num_map_classes,
            aux_seg=aux_seg_cfg,
            decoder=dict(
                type="MapInstanceDecoder",
                num_layers=6,
                return_intermediate=True,
                decoder_layer=dict(
                    type="DetrTransformerDecoderLayer",
                    embed_dims=head_embed_dims,
                    crossattention=dict(
                        type="HorizonMultiPointDeformableAttention",
                        embed_dims=head_embed_dims,
                        num_levels=1,
                        grid_align_num=2,
                        num_points=fixed_ptsnum_per_pred_line,
                        feats_size=[[bev_w_, bev_h_]],
                    ),
                    dropout=0.1,
                ),
            ),
            criterion=dict(
                type="MapTRCriterion",
                dir_interval=1,
                num_classes=num_map_classes,
                code_weights=[1.0, 1.0, 1.0, 1.0],
                sync_cls_avg_factor=True,
                pc_range=point_cloud_range,
                num_pts_per_vec=fixed_ptsnum_per_pred_line,  # one bbox
                num_pts_per_gt_vec=fixed_ptsnum_per_gt_line,
                gt_shift_pts_pattern="v2",
                aux_seg=aux_seg_cfg,
                assigner=dict(
                    type="MapTRAssigner",
                    cls_cost=dict(type="FocalLossCost", weight=4.0),
                    pts_cost=dict(
                        type="OrderedPtsL1Cost", weight=2.5, beta=0.01
                    ),
                    pc_range=point_cloud_range,
                ),
                loss_cls=dict(
                    type="FocalLoss",
                    loss_name="cls",
                    num_classes=num_map_classes + 1,
                    alpha=0.25,
                    gamma=2.0,
                    loss_weight=4.0,
                    reduction="mean",
                ),
                loss_pts=dict(type="PtsL1Loss", loss_weight=2.5, beta=0.01),
                loss_dir=dict(type="PtsDirCosLoss", loss_weight=0.005),
                loss_seg=dict(
                    type="SimpleLoss", pos_weight=4.0, loss_weight=1.0
                ),
                loss_pv_seg=dict(
                    type="SimpleLoss", pos_weight=1.0, loss_weight=2.0
                ),
            ),
            post_process=dict(
                type="MapTRPostProcess",
                post_center_range=post_center_range,
                pc_range=point_cloud_range,
                max_num=50,
                num_classes=num_map_classes,
            ),
        ),
    ],
)

Where type under model indicates the name of the defined model, and the remaining variables indicate the other components of the model. The advantage of defining the model this way is that we can easily replace the structure we want. For example, if we want to train a model with a backbone of resnet18, we just need to replace backbone under model .

Data Augmentation

Like the definition of model, the data augmentation process is implemented by defining two dicts data_loader and val_data_loader in the config file as follows, corresponding to training set and the processing flow of the validation set.

data_loader = dict(
    type=torch.utils.data.DataLoader,
    dataset=dict(
        type="NuscenesMapDataset",
        data_path=os.path.join(data_rootdir, "train_lmdb"),
        map_path=meta_rootdir,
        sd_map_path=sd_map_path,
        pc_range=point_cloud_range,
        test_mode=False,
        bev_size=(bev_h_, bev_w_),
        fixed_ptsnum_per_line=fixed_ptsnum_per_gt_line,
        padding_value=-10000,
        map_classes=map_classes,
        queue_length=queue_length,
        aux_seg=aux_seg_cfg,
        with_bev_bboxes=False,
        with_ego_bboxes=False,
        with_bev_mask=False,
        use_lidar_gt=use_lidar_gt,
        transforms=[
            dict(type="MultiViewsImgResize", size=(450, 800)),
            dict(
                type="MultiViewsImgTransformWrapper",
                transforms=[
                    dict(
                        type="TorchVisionAdapter",
                        interface="ColorJitter",
                        brightness=0.4,
                        contrast=0.4,
                        saturation=0.4,
                        hue=0.1,
                    ),
                    dict(type="PILToNumpy"),
                    dict(
                        type="GridMask",
                        use_h=True,
                        use_w=True,
                        rotate=1,
                        offset=False,
                        ratio=0.5,
                        mode=1,
                        prob=0.7,
                    ),
                    dict(type="ToTensor", to_yuv=False),
                    dict(type="Pad", divisor=32),
                    dict(type="BgrToYuv444", rgb_input=True),
                    dict(type="Normalize", mean=128.0, std=128.0),
                ],
            ),
        ],
    ),
    sampler=dict(type=torch.utils.data.DistributedSampler),
    shuffle=False,
    batch_size=batch_size_per_gpu,
    num_workers=2,
    pin_memory=True,
    collate_fn=collate_nuscenes_sequencev2,
)

val_data_loader = dict(
    type=torch.utils.data.DataLoader,
    dataset=dict(
        type="NuscenesMapDataset",
        data_path=os.path.join(data_rootdir, "val_lmdb"),
        map_path=meta_rootdir,
        sd_map_path=sd_map_path,
        pc_range=point_cloud_range,
        test_mode=True,
        bev_size=(bev_h_, bev_w_),
        fixed_ptsnum_per_line=fixed_ptsnum_per_gt_line,
        padding_value=-10000,
        map_classes=map_classes,
        queue_length=test_queue_length,
        with_bev_bboxes=False,
        with_ego_bboxes=False,
        with_bev_mask=False,
        use_lidar_gt=use_lidar_gt,
        transforms=[
            dict(type="MultiViewsImgResize", size=(450, 800)),
            dict(
                type="MultiViewsImgTransformWrapper",
                transforms=[
                    dict(type="PILToTensor"),
                    dict(type="Pad", divisor=32),
                    dict(type="BgrToYuv444", rgb_input=True),
                    dict(type="Normalize", mean=128.0, std=128.0),
                ],
            ),
        ],
    ),
    sampler=None,
    batch_size=1,
    shuffle=False,
    num_workers=2,
    pin_memory=True,
    collate_fn=collate_nuscenes_sequencev2,
)

Where type directly uses the interface torch.utils.data.DataLoader that comes with pytorch, which represents the combination of batch_size size images together. The only thing to be concerned about here is probably the dataset variable, data_path means the packed lmdb dataset path, map_path is the map information path, and sd_map_path is the sd map information path as we mentioned in the first part of the dataset preparation. transforms contains a series of data augmentation. You can also achieve your own data augmentation by inserting a new dict in transforms.

Training Strategy

In order to train a model with high accuracy, a good training strategy is essential. For each training task, the corresponding training strategy is defined in the config file as well, which can be seen from the variable float_trainer.

float_trainer = dict(
    type="distributed_data_parallel_trainer",
    model=model,
    model_convert_pipeline=dict(
        type="ModelConvertPipeline",
        converters=[
            dict(
                type="LoadCheckpoint",
                checkpoint_path=(pretrained_model_dir),
                allow_miss=True,
                ignore_extra=True,
                verbose=True,
            ),
        ],
    ),
    data_loader=data_loader,
    optimizer=dict(
        type=torch.optim.AdamW,
        params={
            "backbone": dict(lr_mult=0.1),
        },
        lr=float_lr,
        weight_decay=0.1,
    ),
    batch_processor=batch_processor,
    device=None,
    num_epochs=30,
    callbacks=[
        stat_callback,
        loss_show_update,
        grad_callback,
        dict(
            type="CosineAnnealingLrUpdater",
            warmup_len=500,
            warmup_by="step",
            warmup_lr_ratio=1.0 / 3,
            warmup_lr_begin2ratio=True,
            step_log_interval=500,
            stop_lr=3e-3 * float_lr,
        ),
        ckpt_callback,
        val_callback,
    ],
    sync_bn=True,
    train_metrics=dict(
        type="LossShow",
    ),
    val_metrics=[
        val_map_metric,
    ],
)

The float_trainer defines our training approach in the big picture, including the use of distributed_data_parallel_trainer, the number of epochs for model training, and the choice of optimizer. Also, the callbacks reflect the small strategies used by the model during training and the operations that you want to implement, including the way to transform the learning rate (CosineAnnealingLrUpdater), the metrics to validate the model during training (Validation), and the operations to save (Checkpoint) the model. Of course, if you have operations that you want the model to implement during training, you can also add them in this dict way.

Note

If reproducibility accuracy is needed, the training strategy in config is best not modified. Otherwise, unexpected training situations may occur.

Quantized Model Training

By using float_trainer, we can get a float model with high accuracy. Thus we can start to train the corresponding calibration and qat model. The corresponding training strategies are defined as follows.

calibration_trainer = dict(
    type="Calibrator",
    model=calib_model,
    model_convert_pipeline=dict(
        type="ModelConvertPipeline",
        qat_mode=qat_mode,
        converters=[
            dict(
                type="LoadCheckpoint",
                checkpoint_path=os.path.join(
                    ckpt_dir, "float-checkpoint-best.pth.tar"
                ),
                ignore_extra=True,
                verbose=True,
                check_hash=False,
            ),
            dict(
                type="RepModel2Deploy",
            ),
            dict(
                type="Float2Calibration",
                convert_mode=convert_mode,
                example_data_loader=calibration_example_data_loader,
                qconfig_setter=cali_qconfig_setter,
            ),
            dict(
                type="FixWeightQScale",
            ),
        ],
    ),
    data_loader=calibration_data_loader,
    batch_processor=calibration_batch_processor,
    num_steps=calibration_step,
    device=None,
    callbacks=[
        stat_callback,
        calibration_ckpt_callback,
        calibration_val_callback,
    ],
    val_metrics=[
        val_map_metric,
    ],
    log_interval=calibration_step / 10,
)

qat_trainer = dict(
    type="distributed_data_parallel_trainer",
    model=model,
    model_convert_pipeline=dict(
        type="ModelConvertPipeline",
        qat_mode=qat_mode,
        converters=[
            dict(
                type="RepModel2Deploy",
            ),
            dict(
                type="Float2QAT",
                convert_mode=convert_mode,
                example_data_loader=copy.deepcopy(
                    calibration_example_data_loader
                ),
                qconfig_setter=qat_qconfig_setter,
            ),
            dict(
                type="FixWeightQScale",
            ),
            dict(
                type="LoadCheckpoint",
                checkpoint_path=os.path.join(
                    ckpt_dir, "calibration-checkpoint-best.pth.tar"
                ),
                allow_miss=True,
                ignore_extra=True,
                verbose=True,
            ),
        ],
    ),
    data_loader=data_loader,
    optimizer=dict(
        type=torch.optim.AdamW,
        params={
            "backbone": dict(lr_mult=0.1),
        },
        lr=qat_lr,
        weight_decay=0.1,
    ),
    batch_processor=qat_batch_processor,
    device=None,
    num_epochs=3,
    callbacks=[
        stat_callback,
        loss_show_update,
        grad_callback,
        qat_val_callback,
        qat_ckpt_callback,
    ],
    sync_bn=True,
    train_metrics=dict(
        type="LossShow",
    ),
    val_metrics=[
        val_map_metric,
    ],
)

Quantized training is in fact finetuning based on float training, so during quantization training, our qat learning rate is much smaller than the float learning rate. The number of epochs for quantization training is largely reduced, most importantly, when defining the model , our pretrained needs to be set to the path of the previous model checkpoint.

The Settings of Qconfig

Before we begin quantization training, we need to set qconfig of the model. Float model will be converted into a quantized model by the settings of qconfig as follows.

q_templates = [
    ModuleNameTemplate({"": qint8}),
    MatmulDtypeTemplate(
        input_dtypes=[qint8, qint8],
    ),
    ConvDtypeTemplate(
        input_dtype=qint8,
        weight_dtype=qint8,
    ),
]
cali_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(
        observer=(observer_v2.MSEObserver)
    ),
    templates=q_templates,
    enable_optimize=True,
    save_dir=ckpt_dir,
    custom_qconfig_mapping=None,
)
qat_qconfig_setter = QconfigSetter(
    reference_qconfig=get_qconfig(
        observer=(observer_v2.MinMaxObserver), fix_scale=True
    ),
    templates=q_templates,
    enable_optimize=True,
    save_dir=ckpt_dir,
    custom_qconfig_mapping=None,
)

cali_qconfig_setter and qat_qconfig_setter are qconfig settings for calibration model and qat model specifically. For qconfig settings, such as default templates and sensitivity templates, please read the Quantized Awareness Training (QAT) Qconfig in Detail section.