地平線靜態(tài)目標檢測 MapTR 參考算法 - V2.0
該示例為參考算法,僅作為在征程 6 上模型部署的設(shè)計參考,非量產(chǎn)算法
一、簡介
高清地圖是自動駕駛系統(tǒng)的重要組件,提供精確的駕駛環(huán)境信息和道路語義信息。傳統(tǒng)離線地圖構(gòu)建方法成本高,維護復(fù)雜,使得依賴車載傳感器的實時感知建圖成為新趨勢。早期實時建圖方法存在局限性,如處理復(fù)雜地圖元素的能力不足、缺乏實例級信息等,在實時性和后處理
復(fù)雜度上存在挑戰(zhàn)。
為了解決這些問題,基于 Transformer 的 MapTR 模型被提出,它采用端到端結(jié)構(gòu),僅使用圖像數(shù)據(jù)就能實現(xiàn)高精度建圖,同時保證實時性和魯棒性。MapTRv2 在此基礎(chǔ)上增加了新特性,進一步提升了建圖精度和性能。
地平線面向智駕場景推出的征程 6 系列(征程 6)芯片,在提供強大算力的同時帶來了極致的性價比,征程 6 芯片對于 Transformer 模型的高效支持助力了 MapTR 系列模型的端側(cè)部署。本文將詳細介紹地平線算法工具鏈在征程 6 芯片部署 MapTR 系列模型所做的優(yōu)化以及模型端側(cè)的表現(xiàn)。
二、性能精度指標
模型配置:
性能精度表現(xiàn):
三、公版模型介紹
3.1 MapTR
MapTR 模型的默認輸入是車載攝像頭采集到的 6 張相同分辨率的環(huán)視圖像,使用 nuScenes 數(shù)據(jù)集,同時也支持拓展為多模態(tài)輸入例如雷達點云。模型輸出是矢量化的地圖元素信息,其中地圖元素為人行橫道、車道分隔線和道路邊界 3 種。模型主體采用 encoder-decoder 的端到端結(jié)構(gòu):
3.2 MapTRv2
MapTRv2 在 MapTR 的基礎(chǔ)上增加了新的特性:
四、地平線部署說明
地平線參考算法使用流程請參考征程 6 參考算法使用指南;對應(yīng)高效模型設(shè)計建議請參考《征程 6 平臺算法設(shè)計建議》
MapTROE 模型引入了 SD map 的前融合結(jié)構(gòu),與圖像視角轉(zhuǎn)換后的 bev feature 進行融合,再通過優(yōu)化后的 MapTR head 生成矢量化的地圖元素。整體結(jié)構(gòu)如下:
因此 maptroe_henet_tinym_bevformer_nuscenes 模型相比之前版本新增了如下優(yōu)化點:
maptroe_henet_tinym_bevformer_nuscenes 模型對應(yīng)的代碼路徑:
4.1 性能優(yōu)化
4.1.1 Backbone
MapTROE 采用基于征程 6 芯片的高效輕量化 Backbone HENet_TinyM(Hybrid Efficient Network, Tiny for J6M),HENet 能更好地利用征程 6 系列芯片的算力,在模型精度和性能上更具優(yōu)勢。HENet_TinyM 采用了純 CNN 架構(gòu),總體分為四個 stage,每個 stage 會進行一次 2 倍下采樣,具體結(jié)構(gòu)配置如下:
# henet-tinym depth = [4, 3, 8, 6] block_cls = ["GroupDWCB", "GroupDWCB", "AltDWCB", "DWCB"] width = [64, 128, 192, 384] attention_block_num = [0, 0, 0, 0] mlp_ratios, mlp_ratio_attn = [2, 2, 2, 3], 2 act_layer = ["nn.GELU", "nn.GELU", "nn.GELU", "nn.GELU"] use_layer_scale = [True, True, True, True] extra_act = [False, False, False, False] final_expand_channel, feature_mix_channel = 0, 1024 down_cls = ["S2DDown", "S2DDown", "S2DDown", "None"] patch_embed = "origin"
4.1.2 Neck
Neck 部分采用了地平線內(nèi)部實現(xiàn)的 FPN,相比公版 FPN 實現(xiàn),在征程 6 平臺上性能更加友好。
4.1.3 View Transformer
地平線參考算法版本將基于 LSS 的視角轉(zhuǎn)換方式替換為深度優(yōu)化后 Bevformer 的 View Transformer 部分。
# 公版模型 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, ) # 對應(yīng)改動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 ) # 對應(yīng)改動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) # 對應(yīng)改動2.c ...
d. 取消了公版的 TemporalSelfAttention,改為 HorizonMSDeformableAttention,保持精度的同時提升速度;
# 公版模型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", # 對應(yīng)改動2.d ... ), ) ) ) )
e. 支持公版 Bevformer 中的 bev_mask,并將涉及到的 gather/scatter 操作,用 gridsample 等價替換,提高模型速度。
# 地平線參考算法Config view_transformer=dict( type="SingleBevFormerViewTransformer", ... max_camoverlap_num=2, # 對應(yīng)根據(jù)bev_mask進行稀疏映射,提高運行效率,對應(yīng)改動2.e virtual_bev_h=int(0.4 * bev_h_), virtual_bev_w=bev_w_, ... )
4.1.4 Head
公版 MapTR 使用分層 query 機制,定義一組 instance queries 和由所有 instance 共享的 point queries,每個地圖元素對應(yīng)一組分層 query(一個 instance query 和共享的 point queries 廣播相加得到),在 decoder layer 中分別使用 self-attention 和 cross-attention 來更新分層 query。
MapTROE 的改進則是為每個地圖元素分配一個 instance query(無直接 point query),每個 query 用于編碼語義信息和地理位置信息,decoder 階段和公版 MapTR 一樣,分別進行 multi-head self-attention 和 deformable cross-attention,最后每個 instance query 通過 MLP 網(wǎng)絡(luò)生成類別信息和元素內(nèi)的點集坐標,相比公版預(yù)測分層 query,改進后直接預(yù)測 instance query 帶來的計算量更少,極大地提高了模型在端側(cè)的運行性能。同時借鑒 StreamMapNet,使用多點注意力方法來適應(yīng)高度不規(guī)則的地圖元素,擴大感知范圍。代碼見/usr/local/lib/python3.10/dist-packages/hat/models/task_modules/maptr/instance_decoder.py: class MapInstanceDetectorHead(nn.Module)
4.1.5 多點注意力
傳統(tǒng)的可變形注意力為每個 query 分配一個參考點,多點注意力則使用前一層預(yù)測的地圖元素的多個點作為當前層 query 的參考點,具體計算方式是在點維度上擴展了一層求和,將一個點變成多個點,分別計算 deformable attention?;貧w的時候并非預(yù)測 offsets,而是直接預(yù)測地圖元素點的坐標位置。
4.1.6Attention
模型中用到的 attention 操作均使用地平線提供的算子,相比 PyTorch 提供的公版算子,地平線 attention 算子在保持算子邏輯等價的同時在效率上進行了優(yōu)化
from hat.models.task_modules.bevformer.attention import ( HorizonMSDeformableAttention, HorizonMSDeformableAttention3D, HorizonSpatialCrossAttention, ... )
4.2 精度優(yōu)化
4.2.1 浮點精度
MapTROE 模型引入 SD Map 前融合,與圖像轉(zhuǎn)換后的 bev feature 進行融合,以提高在線地圖的生成質(zhì)量。模塊結(jié)構(gòu)如下圖所示:
4.2.1.1 SD Map 特征提取
SD Map 從 OpenStreetMap(OSM)中獲取,通過由 GPS 提供的車輛位姿,查詢車輛當前位姿附近的 SD Map,然后將 SD Map 轉(zhuǎn)換到自車坐標系下,與 NuScenes 中的數(shù)據(jù)標注坐標系保持一致。SD Map 會從車道中心骨架線 Polyline 的形式轉(zhuǎn)化為柵格結(jié)構(gòu),大小和 BEV 特征相同,經(jīng)過 CNN 變成特征圖,對應(yīng) SD Map 的先驗信息。
4.2.1.2 SD Map 特征融合
柵格化后的 SD Map 和實際場景可能會出現(xiàn)錯位、不對齊的情況,這種錯位導(dǎo)致直接 Concatenate BEV 特征和 SD Map 特征的效果并不好,為了解決這個問題,引入了特征融合模塊,通過網(wǎng)絡(luò)學(xué)習(xí)來決定最適合的對齊方式,可以有效地利用 SD Map 先驗提升 BEV 特征的效果。關(guān)于特征融合模塊,分別實驗了交叉注意力與 CNN 網(wǎng)絡(luò),通過精度與性能的平衡,最后選擇了 CNN 網(wǎng)絡(luò)模塊。
4.3 量化精度
# Config文件 cali_qconfig_setter = (default_calibration_qconfig_setter,) qat_qconfig_setter = (default_qat_fixed_act_qconfig_setter,)
2.浮點階段采用更大的 weight decay 訓(xùn)練,使浮點數(shù)據(jù)分布范圍更小,浮點模型參數(shù)更有利于量化
# Config文件 float_trainer = dict( ... optimizer=dict( ... weight_decay=0.1, # 相比maptrv2_resnet50_bevformer_nuscenes增大了10倍 ), ... )
3.QAT 訓(xùn)練采用固定較小的 learning rate 來 fine-tune,這里固定也即取消 LrUpdater Callback 的使用,配置如下:
# Config文件 qat_lr = 1e-9
4.取消了公版模型 MapTRHead 中對于量化不友好的 inverse_sigmoid 操作;此外 MapTROE 對 Head 的優(yōu)化無需再引入 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 MapInstanceDetectorHead(nn.Module): ... def get_outputs(...): ... for lvl in range(len(outputs_classes)): tmp = reference_out[lvl].float() outputs_coord, outputs_pts_coord = self.transform_box(tmp) outputs_class = outputs_classes[lvl].float() outputs_classes_one2one.append( outputs_class[:, 0 : self.num_vec_one2one] ) outputs_coords_one2one.append( outputs_coord[:, 0 : self.num_vec_one2one] ) outputs_pts_coords_one2one.append( outputs_pts_coord[:, 0 : self.num_vec_one2one] ) outputs_classes_one2many.append( outputs_class[:, self.num_vec_one2one :] ) outputs_coords_one2many.append( outputs_coord[:, self.num_vec_one2one :] ) outputs_pts_coords_one2many.append( outputs_pts_coord[:, self.num_vec_one2one :] ) ... def forward(...): outputs = self.bev_decoder(...) if self.is_deploy: return outputs ... outputs = self.get_outputs(...) ... return self._post_process(data, outputs)
5.Attention 結(jié)構(gòu)優(yōu)化,通過數(shù)值融合方法,將部分數(shù)值運算提前進行融合,減少整體的量化操作,提高模型的量化友好度
4.4 其他優(yōu)化
4.4.1 設(shè)計優(yōu)化
五、總結(jié)與建議
5.1 部署建議
5.2 總結(jié)
本文通過對 MapTR 進行地平線量化部署的優(yōu)化,使得模型在征程 6 計算平臺上用較低的量化精度損失,最優(yōu)獲得征程 6M 單核 93.77 FPS 的部署性能。同時,MapTR 系列的部署經(jīng)驗可以推廣到其他相似結(jié)構(gòu)或相似使用場景模型的部署中。
對于地平線 MapTR 參考算法模型,結(jié)合 Sparse Bev 等的優(yōu)化方向仍在探索和實踐中,Stay Tuned!
六、附錄
*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。