地平線靜態目標檢測 MapTR 參考演算法-V1.0

地平线智能驾驶开发者發表於2024-09-27

1.簡介

高畫質地圖是自動駕駛系統的重要元件,提供精確的駕駛環境資訊和道路語義資訊。傳統離線地圖構建方法成本高,維護複雜,使得依賴車載感測器的實時感知建圖成為新趨勢。早期實時建圖方法存在侷限性,如處理複雜地圖元素的能力不足、缺乏例項級資訊等,在實時性和後處理複雜度上存在挑戰。

為了解決這些問題,基於 Transformer 的 MapTR 模型被提出,它採用端到端結構,僅使用影像資料就能實現高精度建圖,同時保證實時性和魯棒性。MapTRv2 在此基礎上增加了新特性,進一步提升了建圖精度和效能。

地平線面向智駕場景推出的 征程6 系列(J6)晶片,在提供強大算力的同時帶來了極致的價效比,征程6 晶片對於 Transformer 模型的高效支援助力了 MapTR 系列模型的端側部署。本文將詳細介紹地平線演算法工具鏈在 征程6 晶片部署 MapTR 系列模型所做的最佳化以及模型端側的表現。

2.效能精度指標

模型配置:

image

效能精度表現:

image

  1. 預測的地圖元素:“divider”,“ped_crossing”,“boundary”;
  2. 預設使用 Lidar 座標系,和公版保持一致。同時適配 ego 座標系;
  3. 量化配置 TopK:前 K 個量化敏感的運算元。

3.公版模型介紹

3.1 MapTR

image

MapTR 模型的預設輸入是車載攝像頭採集到的 6 張相同解析度的環檢視像,使用 nuScenes 資料集,同時也支援擴充為多模態輸入例如雷達點雲。模型輸出是向量化的地圖元素資訊,其中地圖元素為人行橫道、車道分隔線和道路邊界 3 種。模型主體採用 encoder-decoder 的端到端結構:

  • Map Encoder 透過 CNN Backbone+BEV Encoder 負責提取 2D 影像特徵並轉換到統一的 BEV 視角。MapTR-nano 預設使用 ResNet18 作為 Backbone,MapTR-tiny 預設使用 ResNet50。MapTR 相容多種 BEV Encoder 實現方式例如 GKT、LSS 和 IPM 等並且表現穩定,鑑於 GKT 的部署高效性以及在消融實驗中的精度表現更好,公版 MapTR 使用 GKT 作為預設 BEV Encoder 實現方式。
  • Map Decoder 採用 Hierarchical Query Embedding Scheme,即從 point-level(位置)和 instance-level(輪廓)顯式地編碼地圖元素,point-level queries 被所有 instances 共享並融合進 instance-level queries 從而生成 hierarchical queries,hierarchical queries 經過級聯的 decoder layers(預設是 6 層)不斷更新。每個 decoder layer 首先使用多頭自注意力(MHSA)做 inter-instance 和 intra-instance 的資訊互動,接著會使用 Deformable Attention 來與 Map Encoder 輸出的 BEV 特徵做資訊互動。point-level 的資訊被所有 instance 共享,所以對於每個 instance 而言,對映到 BEV 空間的多個參考點 reference points 是靈活且動態分佈的,這對於提取 long-range context information 預測隨機形狀的地圖元素是有益的。
  • MapTR Head 由分類分支和迴歸分支構成。分類分支預測 instances 的類別,迴歸分支預測 points 集合的位置。Head 輸出的預測值和真值 GT 之間採用 Hierarchical Bipartite Matching 實現監督學習,分為 Instance-level Matching 和 Point-level Matching,因此損失函式為三個部分的加權和:分類 Classification Loss、點對點位置 Point2point Loss 和連線邊方向 Edge Direction Loss。

3.2 MapTRv2

image

MapTRv2 在 MapTR 的基礎上增加了新的特性:

  1. 針對層次化 query,引入解耦自注意力,極大地減少了計算量和視訊記憶體消耗;對於和輸入特徵互動的 cross-attention 部分,則引入了 BEV、PV 和 BEV+PV 三種變體;
  2. 引入輔助 one-to-many 集合預測分支,增加了正樣本數,加速了訓練收斂;
  3. 引入輔助 dense supervision,引入深度估計預測頭、PV 和 BEV 視角下的分割頭,進一步提升模型精度。由於引入深度資訊做監督學習,為了顯式地提取深度資訊,公版 MapTRv2 選擇基於 LSS 的 BEVPoolv2 來作為 BEV 視角轉換方式;
  4. 引入新的地圖元素車道中心線(centerline);
  5. 增加 3D 地圖元素預測能力,並提供 Argoverse2 資料集上的指標。

4.地平線部署說明

地平線參考演算法使用流程請參考附錄《TCJ6007-J6 參考演算法使用指南》;對應高效模型設計建議請參考附錄《TCJ6005-J6 平臺演算法設計建議》

模型對應的程式碼路徑:

模組 程式碼路徑
Config {oe_path}/samples/ai_toolchain/horizon_model_train_sample/scripts/configs/map/maptrv2_resnet50_bevformer_nuscenes.py
Model Structure /usr/local/lib/python3.10/dist-packages/hat/models/structures/maptr/maptrv2.py: class MapTRv2(nn.Module)
Backbone /usr/local/lib/python3.10/dist-packages/hat/models/backbones/resnet.py: class ResNet50(ResNet)
Neck /usr/local/lib/python3.10/dist-packages/hat/models/necks/fpn.py: class FPN(nn.Module)
View Transformer /usr/local/lib/python3.10/dist-packages/hat/models/task_modules/bevformer/view_transformer.py: class SingleBevFormerViewTransformer(BevFormerViewTransformer)其中包含的BEV Encoder模組:/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/bevformer/encoder.py: class SingleBEVFormerEncoder(BEVFormerEncoder)
BEV Decoder /usr/local/lib/python3.10/dist-packages/hat/models/task_modules/maptr/decoderv2.py: class MapTRPerceptionDecoderv2(nn.Module)其中具體包含的BEV Decoder模組:/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/maptr/decoder.py: class MapTRDecoder(nn.Module)
Criterion /usr/local/lib/python3.10/dist-packages/hat/models/task_modules/maptr/criterion.py: class MapTRCriterion(nn.Module)其中的Assigner模組:/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/maptr/assigner.py: class MapTRAssigner(nn.Module)
Post Process /usr/local/lib/python3.10/dist-packages/hat/models/task_modules/maptr/postprocess.py: class MapTRPostProcess(nn.Module)

4.1 效能最佳化

Neck

Neck 部分採用了地平線內部實現的 FPN,相比公版 FPN 實現,在 征程6 平臺上效能更加友好。

View Transformer

地平線參考演算法版本將基於 LSS 的視角轉換方式替換為 Bevformer 的 View Transformer 部分。

  1. BEV Grid 尺寸:對於 Dense BEV 而言,BEV Grid 的尺寸大小實際地影響模型效能。征程6 平臺增強了頻寬能力,但仍需注意 BEV 網格過大導致訪存壓力過大而對效能帶來負面影響,建議考慮實際部署情況選擇合適的 BEV 網格大小來設計模型。相比公版 MapTRv2 模型使用 200x100 的網格,地平線部署模型使用 100x50 的網格來實現效能和精度的平衡。
  2. BEV 特徵編碼:
    1. 預設 prev_bev 由 cur_bev 改為全 0;
    2. 取消 can_bus 資訊的使用,前一幀 bev 特徵 prev_bev 和當前幀 cur_bev 的對齊方式由使用 can_bus 資訊正向校準改為使用 GridSample 運算元反向取樣校準;
    3. 取消了 bev_query 初始化部分和 can_bus 的融合;
    4. 取消了公版的 TemporalSelfAttention,改為 HorizonMSDeformableAttention,提升速度。
公版模型
class MapTRPerceptionTransformer(BaseModule):
    ...
    def attn_bev_encode(...):
        ...
        if prev_bev is not None:
            if prev_bev.shape[1] == bev_h * bev_w:
                prev_bev = prev_bev.permute(1, 0, 2)
            if self.rotate_prev_bev:
                for i in range(bs):
                    # num_prev_bev = prev_bev.size(1)
                    rotation_angle = kwargs['img_metas'][i]['can_bus'][-1]
                    tmp_prev_bev = prev_bev[:, i].reshape(
                        bev_h, bev_w, -1).permute(2, 0, 1)
                    tmp_prev_bev = rotate(tmp_prev_bev, rotation_angle,
                                          center=self.rotate_center)
                    tmp_prev_bev = tmp_prev_bev.permute(1, 2, 0).reshape(
                        bev_h * bev_w, 1, -1)
                    prev_bev[:, i] = tmp_prev_bev[:, 0]

        # add can bus signals
        can_bus = bev_queries.new_tensor(
            [each['can_bus'] for each in kwargs['img_metas']])  # [:, :]
        can_bus = self.can_bus_mlp(can_bus[:, :self.len_can_bus])[None, :, :]
        bev_queries = bev_queries + can_bus * self.use_can_bus
        ...
地平線參考演算法
class BevFormerViewTransformer(nn.Module):
    ...
    def 
__init__
(...):
        ...
        self.prev_frame_info = {
            "prev_bev": None,
            "scene_token": None,
            "ego2global": None,
        }
        ...
    def get_prev_bev(...):
        if idx == self.queue_length - 1 and self.queue_length != 1:
            prev_bev = torch.zeros(
                (bs, self.bev_h * self.bev_w, self.embed_dims),
                dtype=torch.float32,
                device=device,
            )
            ...
        else:
            prev_bev = self.prev_frame_info["prev_bev"]
            if prev_bev is None:
                prev_bev = torch.zeros(
                    (bs, self.bev_h * self.bev_w, self.embed_dims),
                    dtype=torch.float32,
                    device=device,
                ) # 對應改動 2.a
                ...
    def bev_encoder(...):
        ...
        tmp_prev_bev = prev_bev.reshape(
            bs, self.bev_h, self.bev_w, self.embed_dims
        ).permute(0, 3, 1, 2)
        prev_bev = F.grid_sample(
            tmp_prev_bev, norm_coords, "bilinear", "zeros", True
        ) # 對應改動 2.b
        ...
class SingleBevFormerViewTransformer(BevFormerViewTransformer):
    ...
    def get_bev_embed(...):
        ...
        bev_query = self.bev_embedding.weight
        bev_query = bev_query.unsqueeze(1).repeat(1, bs, 1) # 對應改動 2.c
        ...
公版模型 Config
model = dict(
    ...
    pts_bbox_head=dict(
        type='MapTRHead',
        ...
        transformer=dict(
            type='MapTRPerceptionTransformer',
            ...
            encoder=dict(
                type='BEVFormerEncoder',
                ...
                transformerlayers=dict(
                    type='BEVFormerLayer',
                    attn_cfgs=[
                        dict(
                            type='TemporalSelfAttention',
                            embed_dims=
_dim_
,
                            num_levels=1),
                            ...
                    ]
                )
            )
        )
    )
)
地平線參考演算法 Config
model = dict(
    ...
    view_transformer=dict(
        type="SingleBevFormerViewTransformer",
        ...
        encoder=dict(
            type="SingleBEVFormerEncoder",
            ...
            encoder_layer=dict(
                type="SingleBEVFormerEncoderLayer",
                ...
                selfattention=dict(
                    type="HorizonMSDeformableAttention", # 對應改動 2.d
                    ...
                ),
            )
        )
    )
)

Attention

模型中用到的 attention 操作均使用地平線提供的運算元,相比 PyTorch 提供的公版運算元,地平線 attention 運算元在保持運算元邏輯等價的同時在效率上進行了最佳化

from hat.models.task_modules.bevformer.attention import (
    HorizonMSDeformableAttention,
    HorizonMSDeformableAttention3D,
    HorizonSpatialCrossAttention,
)

4.2 精度最佳化

量化精度

  1. 對模型中量化敏感的 Top30 個運算元採用 Int16 精度量化:
Config 檔案
if os.path.exists(pts_path):
    pts_table = torch.load(pts_path)
    cali_qconfig_setter = (
        sensitive_op_calibration_8bit_weight_16bit_act_qconfig_setter(
            pts_table,
            topk=30,
            ratio=None,
        ),
        default_calibration_qconfig_setter,
    )
    qat_qconfig_setter = (
        sensitive_op_qat_8bit_weight_16bit_fixed_act_qconfig_setter(
            pts_table,
            topk=30,
            ratio=None,
        ),
        default_qat_fixed_act_qconfig_setter,
    )
  1. QAT 訓練採用固定較小的 learning rate 來 fine-tune,這裡固定也即取消 LrUpdater Callback 的使用,配置如下:
Config 檔案
float_lr = 6e-4
qat_lr = 1e-6
  1. 取消了公版模型 MapTRHead 中對於量化不友好的 inverse_sigmoid 操作;此外部署模型取消了 MapTRHead 中 reg_branches 輸出和 reference 相加後再 sigmoid 的操作(該操作可以轉移到部署後處理中完成):
公版模型
class MapTRHead(DETRHead):
    ...
    def forward(...):
        ...
        for lvl in range(hs.shape[0]):
            if lvl == 0:
                # import pdb;pdb.set_trace()
                reference = init_reference
            else:
                reference = inter_references[lvl - 1]
            reference = inverse_sigmoid(reference)
            ...
            tmp = self.reg_branches
[lvl](...)
            tmp[..., 0:2] += reference[..., 0:2]
            tmp = tmp.sigmoid() # cx,cy,w,h
            
地平線參考演算法
class MapTRPerceptionDecoderv2(nn.Module):
    ...
    def get_outputs(...):
        ...
        for lvl in range(len(outputs_classes)):
            reference = reference_out[lvl].float()
            # reference = inverse_sigmoid(reference)
            ...
            tmp = bbox_outputs[lvl].float()
            tmp[..., 0:2] += reference[..., 0:2]
            tmp = tmp.sigmoid()
    ...

    def forward(...):
        outputs = self.bev_decoder(...)
        if self.is_deploy:
            return outputs
        ...
        outputs = self.get_outputs(...)
        ...
        return self._post_process(data, outputs)

4.3 其他最佳化

設計最佳化

  1. 在 View Transformer,使用 Bevformer 替換地平線支援不友好的公版 MapTRv2 基於 LSS 的 BEVPoolv2 來作為 PV 視角轉 BEV 視角的方式;
  2. 在 View Transformer 的 BEV Encoder 模組取消了 BEV 特徵的時序融合,也取消了 Bevformer 時序自注意力模組,模型整體精度不低於公版基於 Bevformer 的精度。

5.總結與建議

5.1 部署建議

  1. 遵循硬體對齊規則,一般的 tensor shape 對齊到 2 的冪次,conv-like 的運算元 H 維度對齊到 8、W 維度對齊到 16、C 維度對齊到 32,若設計尺寸不滿足對齊規則時會對 tensor 自動進行 padding,造成無效的算力浪費;
  2. 合理選擇 BEV Grid 尺寸,征程6 平臺的頻寬得到增強,但仍需考慮 BEV Grid 尺寸對模型效能的影響,並且綜合衡量模型精度預期,選擇合適的 BEV Grid 尺寸以獲得模型效能和精度的平衡;
  3. 優先選擇 征程6 平臺高效 Backbone 來搭建模型,高效 Backbone 經過在 征程6 平臺的反覆最佳化和驗證,相比其他 Backbone 的選擇,在效能和精度上可以同時取得出眾的效果,因此選取 征程6 平臺高效 Backbone 來搭建模型可以對整個場景模型帶來效能和精度的增益。

5.2 總結

本文透過對 MapTRv2 進行地平線量化部署的最佳化,使得模型在 征程 6 計算平臺上用較低的量化精度損失,獲得單核 26.66 FPS 的部署效能。同時,MapTRv2 的部署經驗可以推廣到其他相似結構或相似使用場景模型的部署中。

對於地平線 MapTR 參考演算法模型,Backbone 和 BEV 中融合方式等的最佳化仍在探索和實踐中,Stay Tuned!

6.附錄

  1. 公版論文:MapTR
  2. 公版模型原始碼:GitHub-MapTR

相關文章