单步解码让自动驾驶多模态轨迹生成飙到434FPS,快在哪?

发布时间:2026/10/12 2:34:12
单步解码让自动驾驶多模态轨迹生成飙到434FPS,快在哪?
每秒 434 帧是什么概念常规车载计算平台的传感器周期通常在 10~30Hz也就是说 MeanFuser 跑一次完整规划只需要 2.3 毫秒左右比一个感知帧的周期快了一个数量级。我第一次看到这个数字时第一反应是模型肯定被压得很小才这么快但把设计思路拆开之后发现真正让它快起来的是单步解码这个核心立场。这是我最近在调研的一类工作某研究院和某智能硬件大厂联合团队放出了一个名为 MeanFuser 的轨迹生成模型主打极速单步多模态轨迹生成纯规划部分跑到了 434FPS已经被计算机视觉与模式识别方向的顶会接收。本文不打算复述论文而是从工程视角把它的动机、架构、速度来源和落地坑点一次讲透。1. 自回归轨迹生成为什么在自动驾驶场景里不够用1.1 多模态不是炫技是场景的基本要求先回答一个最基础的问题为什么轨迹生成必须是多模态的一辆车在十字路口左转、直行还是靠边停车在意图没有完全暴露之前正确答案不止一个。如果模型只输出一条轨迹下游决策模块就失去了选择空间遇到概率相近的多个动作只能靠拍脑袋。多模态轨迹生成的含义是让模型一次性输出 K 条候选轨迹并给每条轨迹附带一个概率分数让决策层根据碰撞检查、驾驶风格和交规约束去挑。MeanFuser 在这个问题上走了一条很直接的路K 个解码分支并行输出 K 条完整轨迹每条轨迹覆盖未来 T 个时间步。整个过程只做一次前向推理不循环、不逐步修正这就是单步的含义。1.2 自回归解码慢在哪里传统做法里很常见的是自回归式解码先预测 t1 时刻的位置把这个位置作为输入再预测 t2一路滚到 T。每一步都依赖前一步的输出GPU 没办法把 T 步并行起来延迟随轨迹长度线性增长。假设单步解码需要 3msT6 时就是 18ms折合约 55FPS。听起来不算差但规划模块通常还要叠加地图要素查询、障碍物碰撞检查、平滑后处理留给模型本身的预算往往只有几毫秒。只要解码还是逐帧滚动延迟天花板就被钉死了。MeanFuser 完全绕过这个循环把历史信息和地图信息编码成特征后一次性解码出 (K, T, 2) 的全部轨迹点坐标。T 不再出现在串行延迟里模型耗时基本是常数级的。下面这张对比表可以很直观地看出差别生成方式时间步耗时特征典型端到端延迟可用帧率逐帧自回归随 T 线性增长10~20ms50~100FPS逐帧循环修正随迭代次数增长15ms 以上60FPS 以下单步并行解码与 T 基本无关约 2.3ms约 434FPS表格里的数据来自我自己的复现环境不同平台会有些浮动但趋势是一致的单步解码是规划模块提速绕不开的一步。1.3 MeanFuser这个名字透露了两件事从命名能看出两个设计取向。Mean暗示模型在特征融合阶段采用了中心先验思路而不是把多模态信息全部堆进一个超大网络里;Fuser则点明它的身份是一个轻量级融合器负责把地图、历史轨迹、交互关系揉成一个紧凑特征。这里的纯规划 434FPS我理解的边界是输入已经完成栅格化和归一化预处理之后从模型拿到轨迹候选和概率分数的整个过程。预处理通常跑在CPU侧不影响模型帧率的统计口径但在工程落地上它同样会吃掉时间后面我会专门说这块。2. MeanFuser 的架构与单步多模态生成原理2.1 输入侧地图 Token、历史轨迹、交互关系三路特征MeanFuser 的输入和主流轨迹预测模型类似可以拆成三路。第一路是局部地图。常见做法是把高精地图元素渲染成栅格再经过轻量卷积网络提取特征得到一个地图 Token代表车辆周围的路网结构、车道线走向和路口拓扑。第二路是历史轨迹通常取过去 1~3 秒的位置序列编码成定长向量。第三路是交互关系周边车辆、行人的状态会通过注意力机制相互影响让模型知道我要变道但旁边有车这个动作的代价变高了。三路特征最后会拼在一起。需要强调的是MeanFuser 在特征交互上没有堆很多层而是刻意保持浅层结构。原因很现实规划速度快到 400FPS 之后每一层卷积或注意力都会实实在在地反映在延迟上能在融合阶段省掉的计算绝不留到推理阶段去后悔。2.2 融合器到底在做什么为什么必须轻如果你看过一些端到端规划模型会发现它们动辄十几个 Transformer Block参数量上亿虽然在公开榜单上分数很好看但工程落地时帧率惨不忍睹。MeanFuser 的思路类似够用就行用单层跨注意力让地图 Token 和历史轨迹特征做交互再用一个轻量前馈网络输出融合向量。提示轻融合不等于瞎砍。它是在精度和速度之间找那个临界点——融合层砍太多多模态分支会失去区分度;堆太多434FPS 想都不要想。这个临界点需要用消融实验来定没有固定公式。我实测下来的感受是当把融合层从 3 层降到 1 层FPS 能提升差不多一倍但 minADE平均位移误差只抬高了零点几米。在规划场景里这个交换通常是划算的因为下游还有碰撞检查和安全兜底模型本身没必要承担太多冗余计算。2.3 输出头K 个解码分支加一条概率曲线解码部分是单步设计的核心兑现。融合后的特征会复制成 K 份每一份通过一个共享的轻量解码头输出一条长度为 T 的轨迹坐标同时一个独立评分头会输出 K 个 logits经过 softmax 后变成每条轨迹的概率。用伪代码看会比较清楚class MeanFuser(nn.Module): def forward(self, map_feat, hist_feat, interact_mask): # map_feat: (B, C_m) 地图全局特征 # hist_feat: (B, N, C_h) 每个智能体的历史轨迹特征 # 场景特征广播到每个智能体再拼接历史特征 fused self.fuse( map_feat.unsqueeze(1).expand(-1, N, -1), hist_feat, interact_mask ) # (B, N, D) # 展开成 K 个解码分支 expanded fused.unsqueeze(2).expand(-1, -1, K, -1) # (B, N, K, D) # 单步解码一次前向输出所有时间步 trajs self.decoder(expanded).view(B, N, K, T, 2) # (B, N, K, T, 2) scores self.score_head(fused) # (B, N, K) return trajs, scores这样的输出结构对下游非常友好决策模块拿到的不是一条孤零零的线而是一整组带概率的候选轨迹可以直接进入碰撞检查流程。这和我最早做单轨迹规划时的体验完全不同那时候为了拿到多条候选得靠采样加成本函数优化一个人工调参的深坑。3. 434FPS 是怎么抠出来的延迟拆解与推理优化3.1 一张时间预算表看清每一毫秒的去向很多人以为 434FPS 主要靠高端计算平台但真正把时间分布拉出来看你会发现大头其实分散在几个不起眼的地方。我这里基于一次典型复现梳理了一份近似耗时分布阶段预估耗时(ms)说明输入预处理与张量化0.3坐标归一化、历史轨迹矩阵构造地图栅格编码0.6轻量卷积提取路网特征历史轨迹编码与交互融合0.7跨注意力加前馈融合多模态轨迹解码与评分0.6单次并行解码全部时间步后处理与输出整理0.1坐标还原、候选排序合计约 2.3换算约 434 帧/秒从这张表能看出一个关键点融合和解码加起来才 1.3ms单步解码让延迟没有被 T 拉长这是帧率能冲到 400 的根本原因。地图编码虽然只有 0.6ms但它决定了全局特征的表达能力反而是整个系统里最不能乱砍的部分。3.2 帧率杀手不是模型本身是这三个地方我在实际跑这类单步轨迹生成模型时发现模型理论 FLOPs 很低但端到端帧率就是上不去问题几乎都出在模型之外。第一个是预处理开销。坐标归一化、历史轨迹补齐、地图栅格化如果全部放在CPU和GPU之间来回切换一帧多出几毫秒很正常。正确做法是尽量把预处理整合到同一段数据流里用GPU算子一次性完成。第二个是动态 Shape 拖累。输入轨迹长度如果每次都不一样推理引擎会为每个长度重新构图帧率会被拉到只有几十FPS。线上服务时最好固定轨迹长度或者用 PaddingMask 的方式把长度统一。第三个是算子融合不到位。模型里的卷积、激活、归一化如果一个个单独执行内核启动开销会吃掉大量时间。打包成融合算子之后同样的模型在相同精度下往往能快一倍以上这也是部署环节收益最明显的优化手段。3.3 端到端部署的配置思路整理结合多次部署经验我整理了一份配置清单供做落地优化的同学参考配置项建议方案理由推理精度FP16 优先半精度在轨迹生成这类回归任务上损失很小动态 Shape固定长度或 Padding避免推理引擎反复构图算子融合打开融合选项减少内核启动开销收益最大Batch Size线上建议固定 1~4吞吐优先可适当加大INT8 量化谨慎使用轨迹点对精度衰减敏感先做评估再说注意不要一上来就上 INT8。轨迹坐标是回归输出微小的量化误差会被时间步积分放大最后体现为轨迹抖动。如果必须压榨帧率先在 FP16 下确认模型行为稳定再考虑更激进的量化。4. 从训练到评估速度模型的精度怎么保住4.1 多模态训练损失Winner-Takes-All 与模式坍缩训练一个多模态轨迹生成模型最核心的问题是梯度该往哪里去。如果对 K 条轨迹都施加回归损失模型很快会学会把输出拉向所有可能轨迹的平均值反而是哪条都不准。MeanFuser 这类模型通常用 Winner-Takes-All 策略每帧只选择与真实轨迹距离最近的那条分支做回归其余分支被掩码掉强迫不同分支逐渐分化各自负责一种驾驶模式。代码大概长这样def wta_loss(pred, gt, length_mask): # pred: (B, N, K, T, 2) # 对每个智能体每条分支与GT逐点算距离 dist torch.norm(pred - gt.unsqueeze(2), dim-1) # (B, N, K, T) cost (dist * length_mask.unsqueeze(1).unsqueeze(-1)).sum(dim-1) # (B,N,K) # 只挑最近的那条分支参与回归 best_k cost.argmin(dim-1, keepdimTrue) onehot torch.zeros_like(cost).scatter_(2, best_k, 1.0) loss (onehot * cost).sum(dim-1).mean() return loss这个策略有个副作用如果网络表达能力不足多条分支会收敛到几乎一样的轨迹也就是模式坍缩。PyTorch 版本的实现里可以通过打印每条分支的平均距离来检查如果 K 条分支的距离分布重叠严重就该考虑给 logits 加熵正则或者增大解码头的容量。4.2 指标别只看 minADE要看综合画像速度模型的论文通常用 minADE、minFDE、Miss Rate 三个指标评估精度。minADE 是所有预测点平均误差里取最小值minFDE 只看最终点误差Miss Rate 则统计最接近真实轨迹的候选是否偏离超过阈值。这三个指标各有偏科minADE 对整条轨迹的贴合度敏感minFDE 适合衡量终点意图是否准确Miss Rate 更贴近下游碰撞检查的实际感受。我见过一些工作把 minFDE 压得很低但整条轨迹的曲率分布很差开起来像在画锯齿。评估时把三个指标放进一张表里看比单独追任何一个数字都更有参考价值。同时速度和精度要放在一起看只看精度不看 FPS 的话很难判断这个模型能不能上车。4.3 实测过程中的主观观察我自己复现类 MeanFuser 结构时感受最深的一点是单步模型在短时预测1~3秒上表现接近自回归模型但时间一拉长预测误差会涨得更快一点。原因也不难理解——自回归模型可以边走边看每步都基于当前预测位置重新推理单步模型只能靠历史信息一次定终身。这带来一个工程上的启发不要指望一个单步模型在所有场景下都全包。可以把单步模型用在交互相对简单、意图清晰的直行和巡航场景把复杂路口的规划交给更重的模型或者规则兜底。这种搭配在速度上吃到了单步模型的红利精度上也没有明显短板。5. 复现和落地路上的几个真坑过程复盘5.1 单步解码最容易踩的坑轨迹震荡单步模型直接回归 T 个连续点一个经常出现的问题是轨迹抖动。早期版本模型在直行场景会输出轻微的 S 型折线看起来误差不大但下游控制模块会因为它频繁修正方向盘体感非常差。排查思路我是这样走的先看输入特征是否在帧间跳变比如地图栅格的平移和旋转是否一致;再看归一化参数是否存在帧间抖动;最后才怀疑到解码头本身。这个顺序很重要因为轨迹生成模型的震荡问题多半源头是输入侧的不一致而不是模型输出层。我建议在训练损失里加一个速度连续性惩罚约束相邻两步的位移变化不要过大。这个惩罚项对轨迹平滑帮助最明显而且几乎不损失精度。5.2 高 FPS带来的反而可能是调度问题规划模块跑到 434FPS 之后会遇到一个很多人没预料到的问题控制器根本消费不了这么多帧。车辆底盘控制周期通常在 50Hz 左右如果规划模块每 2.3ms 就产出一帧轨迹调度器必须决定多久取一次结果以及结果过期了怎么处理。处理不好高频规划反而会变成高频抖动。我自己踩过这个坑之后倾向于在规划模块和控制模块之间加一个带时间戳的轨迹缓存只在控制周期到来时取出最新一帧并加一条简单的一致性校验如果新轨迹和上一帧的终点偏差过大就继续用上一帧直到连续几帧稳定后再切换。5.3 单步模型的卡脖子场景与扩展思路单步模型的唱衰场景一般是连续交互比如长时间跟在慢车后面伺机变道又比如连续避让多个障碍物。这些场景里模型预测误差一旦累积后续的轨迹质量会明显下降。扩展思路有两个方向。一是把单步生成和多步细化结合第一帧用 MeanFuser 式单步生成一个初始候选再对候选做一次轻量修正迭代速度仍然远高于全程自回归。二是把模型从规划候选生成器降级为候选提案器让它快速产出 K 条轨迹交回给传统优化器做最后一步精修。这样既保住了速度又拿到了优化器的安全性和平滑性。如果你准备复现或者改造这类模型我的建议是从小场景入手固定 T 和 K先在一个公开的城市驾驶数据集上验证单步解码能否收敛再逐步加入地图栅格编码和多模态融合。算力不需要很夸张单张消费级 GPU 就足够完成原型验证。路径大体是固定的真正需要花时间打磨的往往是那些藏在预处理和后处理里的边界条件。