1. BEV多任务感知系统概述
在自动驾驶领域,鸟瞰图(BEV)多任务感知系统正成为主流技术方案。这类系统通过统一的BEV特征表示,可以同时完成3D目标检测、语义分割、地图生成等多种感知任务。Apollo-Vision-Net作为典型的BEV多任务感知框架,其核心是基于BEVFormer的时空特征融合架构。
本次技术迭代的核心目标是在现有det+occ(检测+占据)任务基础上,新增det+map(检测+地图)分支,实现与MapTR(一种先进的在线矢量地图生成方法)的GT对齐。这种多任务并行架构具有以下技术优势:
- 共享BEV特征提取,计算资源利用率高
- 统一时空上下文建模,各任务相互促进
- 输出结果天然对齐,便于下游规划控制
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. MapTR接入方案设计
2.1 MapTR核心思想解析
MapTR提出了一种端到端的矢量地图生成范式,其核心创新点包括:
- 固定采样点表示:每条车道线/polyline采用固定数量(默认20个)的采样点表示
- 分层分类体系:将地图元素分为3大类(车道线、道路边界、人行横道等)
- 在线GT生成:动态生成矢量地图标注,避免依赖高精度离线地图
这种表示方法具有参数化程度高、易于优化、适合实时推理等特点,非常适合作为BEV多任务系统的地图分支目标。
2.2 系统架构调整
为实现det+map多任务并行,我们对Apollo-Vision-Net进行了以下架构调整:
-
新增MapTR风格Head:
- 包含map query embedding层
- 分类头(map_cls_head)预测元素类别
- 坐标头(map_pts_head)预测采样点位置
-
数据链路改造:
- 在数据加载pipeline中注入MapTR格式GT
- 新增CustomNuScenesDetOccMapDataset适配器
- 实现LoadMapTRGTDetMap数据加载组件
-
特征共享机制:
- 复用BEVFormer的时空注意力机制
- 共享BEV特征提取主干网络
- 独立的任务特定解码头
3. 关键技术实现细节
3.1 MapTR Head实现
MapTR head的核心代码实现如下:
python复制class BEVFormerDetMapHeadApollo(nn.Module):
def __init__(self, embed_dims=256, num_map_vec=50, map_num_pts=20, map_num_classes=3):
super().__init__()
self.map_query_embed = nn.Embedding(num_map_vec, embed_dims)
self.map_cls_head = nn.Sequential(
nn.Linear(embed_dims, embed_dims),
nn.ReLU(inplace=True),
nn.Linear(embed_dims, map_num_classes),
)
self.map_pts_head = nn.Sequential(
nn.Linear(embed_dims, embed_dims),
nn.ReLU(inplace=True),
nn.Linear(embed_dims, map_num_pts * 2),
)
def forward(self, bev_embed):
bs = bev_embed.size(0)
q = self.map_query_embed.weight.unsqueeze(0).expand(bs, -1, -1)
q = q + bev_embed.mean(dim=1).unsqueeze(1) # 添加全局上下文
map_cls = self.map_cls_head(q)
map_pts = self.map_pts_head(q).view(bs, -1, self.map_num_pts, 2)
return {'map_cls': map_cls, 'map_pts': map_pts}
该实现包含以下关键技术点:
- 可学习query设计:通过Embedding层生成固定数量的map query,每个query对应一个潜在的map元素
- 全局上下文融合:将BEV全局特征均值作为偏置加到query上,提供场景级语义信息
- 双分支预测:分类分支预测元素类别,坐标分支预测固定数量采样点的2D坐标
3.2 数据链路改造
为支持MapTR格式数据加载,我们实现了以下关键组件:
- 在线GT生成器:
python复制class VectorizedLocalMap:
def gen_vectorized_samples(self, location, lidar2global):
# 根据location选择地图区域
map_patch = self.load_map_patch(location)
# 矢量元素提取和采样
instances = self.extract_map_elements(map_patch)
# 固定点数采样和归一化
sampled_pts = [self.fixed_sample(inst, n_points=20) for inst in instances]
return {
'gt_vecs_pts_loc': sampled_pts, # [N,20,2]
'gt_vecs_label': labels # [N]
}
- 数据加载适配器:
python复制class CustomNuScenesDetOccMapDataset(CustomNuScenesDataset):
def prepare_train_data(self, idx):
data = super().prepare_train_data(idx)
# 注入MapTR GT
map_gt = self.vector_map.gen_vectorized_samples(
self.get_location(idx),
self.get_lidar_pose(idx)
)
data.update({
'gt_map_vecs_label': map_gt['gt_vecs_label'],
'gt_map_vecs_pts_loc': map_gt['gt_vecs_pts_loc']
})
return data
3.3 多任务损失设计
MapTR分支的损失函数包含两个部分:
- 分类损失:标准交叉熵损失,监督元素类别预测
python复制loss_cls = F.cross_entropy(pred_logits, gt_labels.long())
- 坐标损失:L1损失监督采样点位置
python复制loss_pts = F.l1_loss(pred_pts, gt_pts)
实际实现中还包含以下优化:
- 动态正负样本分配
- 基于IoU的样本权重调整
- 梯度裁剪和归一化
4. 数值稳定性优化
4.1 FP16训练问题分析
在混合精度(FP16)训练中,我们观察到三处注意力模块容易出现数值不稳定:
- 空间交叉注意力:在BEV空间特征采样时,大范围场景导致注意力logits值域过宽
- 时序自注意力:长序列建模时累积的数值误差
- 解码器变形注意力:多尺度特征融合中的数值问题
这些问题主要表现为softmax前的logits值过大,导致exp运算溢出产生inf/nan。
4.2 Logits Clamp方案
我们在三个关键注意力模块实施logits clamp策略:
python复制# 以TemporalSelfAttention为例
class TemporalSelfAttention(nn.Module):
def __init__(self, ..., attn_logits_clamp=20.0):
self.attn_logits_clamp = attn_logits_clamp
def forward(self, query):
attn_logits = self.attention_weights(query)
if self.attn_logits_clamp is not None:
attn_logits = attn_logits.clamp(
-self.attn_logits_clamp,
self.attn_logits_clamp
)
attn_weights = attn_logits.softmax(-1)
...
该方案具有以下优势:
- 可控性:clamp阈值可通过配置调整(典型值20-100)
- 可诊断性:配合debug_attn_nan标志可输出非有限值比例
- 低开销:clamp操作计算量几乎可忽略
4.3 其他稳定性措施
- 输入特征检查:
python复制def _finite_stats(self, name, x):
if x is None: return
finite = torch.isfinite(x)
ratio = finite.float().mean().item()
if ratio < 1.0:
print(f"[bevformer][nan] {name}: ratio={ratio}...")
- 异常值兜底:
python复制# lidar2img形状修复
if lidar2img.ndim == 3 and lidar2img.shape[-2:] == (4,4):
lidar2img = np.stack([np.asarray(x) for x in lidar2img], axis=0)
# img_shape缺失处理
if (H_img is None) or (W_img is None) or (H_img <=0) or (W_img <=0):
H_img, W_img = 1, 1
5. 训练与调试实践
5.1 Smoke测试方案
我们设计了最小训练闭环验证方案:
bash复制PYTHONPATH=. python tools/smoke_det_map_forward_train.py \
projects/configs/bevformer/bev_tiny_det_map_apollo.py \
--cfg-options model.debug_nan=True
该脚本执行以下验证流程:
- 构造单样本batch
- 运行forward_train
- 输出loss dict和数值诊断信息
典型输出示例:
code复制[smoke] img_metas[0][0].lidar2img shape=(6,4,4)
[bevformer][nan] feat_lvl0_after_embeds: ratio=0.8945...
[det_map][gt] sample0: num_lines=15, labels_uniq=[0,1,2]
loss_map_cls: 0.5236
loss_map_pts: 10.6262
5.2 调试技巧
-
渐进式验证:
- 先确保单样本forward能跑通
- 再验证backward梯度传播
- 最后进行完整训练
-
数值问题定位:
- 使用_finite_stats逐层检查特征
- 对比clamp开关前后的数值分布
- 监控loss曲线和梯度幅值
-
可视化调试:
python复制# MapTR预测可视化 def visualize_map_pred(pred_pts, pred_labels): plt.figure() for pts, label in zip(pred_pts, pred_labels): color = ['r','g','b'][label] plt.plot(pts[:,0], pts[:,1], color=color) plt.show()
6. 性能优化建议
基于实际部署经验,我们总结以下优化建议:
-
Query数量调优:
- 城市道路场景:50-100个query足够覆盖大部分情况
- 复杂路口可适当增加query数量
- 可通过统计GT元素数量分布确定合理值
-
Clamp阈值选择:
- FP16训练:建议20-50
- FP32训练:可适当放宽或关闭
- 可通过监控attention_weights的max/min动态调整
-
多任务平衡:
- 初始阶段给det任务更高权重
- 随着训练进行逐步提高map任务权重
- 可采用动态权重调整策略
7. 扩展与应用
当前实现还可向以下方向扩展:
- 动态query机制:根据场景复杂度动态调整query数量
- 时序融合:利用历史帧信息提升map元素连续性
- 交互式标注:将预测结果反馈给标注系统加速迭代
在实际自动驾驶系统中,该技术已应用于:
- 实时高清地图构建
- 路口精细化理解
- 临时障碍物关联
