ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单步多模态轨迹生成434FPS:MeanFuser均值融合架构解析

单步多模态轨迹生成434FPS:MeanFuser均值融合架构解析 1. 为什么需要单步多模态轨迹生成如果你做过自动驾驶规划或者机器人运动规划应该对“多模态轨迹生成”这个词不陌生。一句话解释就是给定当前场景自车或机器人下一步可能有多种走法比如左转、右转、减速让行模型需要一次性输出这些候选轨迹而不是只给一条“看起来最优”的路径。这个任务听起来不复杂但真正落到工程上难点全在“又多又快又准”这三个维度上。我最早接触这一类问题时用的方案还是最传统的“先预测障碍物轨迹再基于预测结果做路径搜索”。这种串联方案的问题是预测模块和规划模块之间的误差会累积预测稍微偏一点规划结果就完全不敢用。后来社区转向直接生成自车轨迹的“纯规划”思路也就是端到端地输出多条候选轨迹和对应的置信度这才让多模态轨迹生成真正成为独立研究课题。MeanFuser这篇文章恰好是把这个方向往前推了一大步。它在保证多模态输出能力的同时把推理过程压缩到单步前向计算整个纯规划流程能做到434FPS。换句话说模型每秒钟能完成434次完整的场景编码和轨迹生成单帧耗时大概只有2.3毫秒左右。这个数字放在实际车载平台上意味着什么意味着轨迹规划可以不再成为整个自动驾驶系统的瓶颈甚至有余力在闭环仿真里同时推演几十个场景副本。1.1 多模态轨迹生成的典型范式先盘一下当前主流的技术路线。目前做多模态轨迹生成大概分三派扩散模型派、自回归派、单步直接回归派。扩散模型派是目前论文里最常见的做法。它的思路是训练时往真实轨迹上逐步加噪声然后让模型学会去噪推理时从纯噪声开始经过多步迭代逐步还原出轨迹。好处是生成质量高、多模态分布可控坏处是慢。一个常规的DDPM或者DDIM模型推理时少说要跑20步每一步都是一次完整网络前向加上场景编码在其他模块里的耗时整个规划周期很容易冲到50毫秒以上。这个延迟放在仿真里还能忍放到实车上就很危险。自回归派则是把轨迹按时间戳逐点生成。每一步只预测下几个点然后把前面预测的点拼回去当输入。它的好处是时序上比较稳便于做约束修正但缺点是误差会随时间步累积而且逐点生成天然是串行的没法充分发挥GPU并行能力。单步直接回归派最直接输入场景特征一次性输出K条轨迹。这种方法推理速度极快但以前一直有个老大难问题——多模态很容易退化。你让网络同时输出K条轨迹它经常会收敛成K条几乎一样的路径或者某个模式特别好其余模式全被抑制这就是常说的模式坍缩。MeanFuser的单步生成本质上就是想解决“既要速度又要多模态质量”这个矛盾。1.2 现有方案的三大痛点慢、糊、脆我把之前踩过的坑总结成三个词慢、糊、脆。慢的问题上面讲过。尤其在做闭环仿真时慢会被放大很多倍。一个场景里有几十辆交互车辆每辆车都需要独立的轨迹生成模块如果单车推理要30毫秒那整个闭环仿真就跑不动了。你会发现实际工程里很多团队宁可牺牲一点精度也要把推理压到10毫秒以内。MeanFuser这种单步方案本质上就是直接从源头干掉慢。糊的问题是扩散模型路线特有的。为了追求多模态覆盖有些方法会在训练时对多条候选轨迹做平均或者拟合一个混合分布。但平均操作做得过头了会怎么样生成的轨迹会变成一条“中庸路径”既不左转也不右转而是直愣愣地冲着两辆车的缝隙中间开过去。这种轨迹在指标上看起来不错因为minADE这类指标不会惩罚它但放到真实场景里完全不可用。MeanFuser里的“Mean”并不是粗暴地对轨迹做平均而是对中间特征做有条件的融合这是一个很关键的差别后面我会详细讲。脆的问题主要体现在训练稳定性上。多模态模型的Loss如果设计得不好训练过程很容易震荡。最常见的情况是某个batch里所有样本都被分给了同一个模式导致其他模式对应的输出头长期得不到有效梯度最后形成“死模式”。我在自己的实验里见过最夸张的情况K6的输出模式最后只剩两个还有响应其他四个完全学会了复读主模式的轨迹。MeanFuser在处理这个问题上用了一个比较聪明的手段就是显式的模式概率监督这个后面也展开说。1.3 MeanFuser 的目标设定从标题能看出来这个工作有三个核心指标单步、多模态、434FPS。“单步”对应的是推理策略彻底简化不做多步扩散迭代不做轨迹序列自回归一次网络前向直接输出最终轨迹。“多模态”对应的是输出结构仍然保持K个候选轨迹和对应概率保证规划器在下游可以按置信度做决策。“434FPS”则是纯规划链路的完整吞吐指标包含了场景编码、特征融合和轨迹解码全流程。这里特别要强调“纯规划”这个词。纯规划指的是输入已经给出标准化的场景表示比如高精地图、障碍物历史轨迹、自车状态然后输出自车未来轨迹。它不包含感知模块的耗时。所以在对比各种方法时大家应该在同一条件下比“规划FPS”不要把感知延迟混进来否则数据没什么可比性。MeanFuser报告434FPS说明它在标准GPU上的纯规划吞吐量已经远超实时需求甚至可以说在算力充裕时可以做“多假设并行规划”把K个候选轨迹分别放到不同约束条件下同时验证。2. MeanFuser 核心思路均值融合 单步生成2.1 均值融合到底融合的是什么我第一次看到MeanFuser这名字时第一反应是“又是个把扩散模型改成单步的变体”。但仔细看它的实现逻辑才发现重点不在“去噪”而在“多模态特征的融合方式”。传统扩散模型里推理时要从随机噪声开始迭代每一步都靠网络把当前带噪声的轨迹估计往真实分布拉近一点。这里有一个隐含假设模型在训练时见过大量“噪声–轨迹”对应关系所以推理时才能一步步走得稳。可一旦压缩到单步模型面对的是纯随机噪声直接到最终轨迹的映射这对网络容量和训练信号设计要求都极高。MeanFuser的思路是不要再让单步模型直接对随机噪声做回归而是先用一组可学习的“模式查询向量”作为初始假设再配一个均值融合模块把多个随机初始化的模式查询在特征空间里进行信息交换。这里的“均值”指的是K个模式查询各自独立生成假设但在每个融合层里把它们的平均值或者加权均值作为全局上下文再重新注入回每一个模式查询。相当于每个模式既能保持自己的个性又能实时参考“所有模式集体商量出来的共识”。这个设计妙在哪里妙在它把“多模态多样性”和“全局一致性”放在同一个模块里同时解决了。模式查询保持个性避免多模态坍缩均值聚合提供全局参考避免模型生成出几个互相矛盾、毫无意义的轨迹。打个不太严谨的比方就像开小组会每个人先有自己的想法然后互相听一下别人的主流意见再修正自己的方案但最终不会所有人都改成同一个答案。2.2 网络结构总体拆解按我复现时的理解MeanFuser整体可以分成四个部分编码器、模式初始化、均值融合模块、轨迹头和模式头。场景编码器负责把地图要素、障碍物历史轨迹和自车状态编码成统一的特征序列。这部分我用的是标准的Transformer结构地图上的车道中心线、障碍物轨迹点各算一种token然后通过自注意力做全局交互。编码器输出的特征会作为后续轨迹生成的“条件上下文”。模式初始化模块维护一组可学习的嵌入向量向量的个数就是候选轨迹的模态数K。在MeanFuser里K通常取6到8和大多数多模态规划器的配置一致。每个嵌入向量从零初始化开始训练经过若干层均值融合模块之后逐步被“塑造”成一种具体的驾驶行为特征。我一开始对这种初始化方式有疑虑觉得随机初始化的向量怎么可能学出有语义的“左转模式”或者“直行模式”后来做了一组可视化发现模式向量之间虽然初始是随机的但经过与场景编码特征的交叉注意力之后会被场景内容动态激活。换句话说模式并不是预先被硬编码成“左转”“右转”而是根据当前场景被动态定义的。这比固定行为类别的做法灵活得多。均值融合模块是整个网络的核心也是名字里Fuser的由来。它做的事情简单说就是对K个模式查询做一次全局平均把平均后的向量作为额外键值再对每个模式查询同时做自注意力和与场景特征的交叉注意力。这里的均值操作相当于把“集体共识”广播给每个个体让模式之间既能保持差异又能共享全局信息。最后的轨迹头和解码器负责输出。解码器用一个小型MLP把模式查询映射成未来T个时间步的轨迹点这里我用的是预测轨迹点位移量的方式也就是输出相对于当前位置的偏移序列。同时一个独立的模式头会输出每个候选轨迹的概率分数用于下游决策时做加权融合或TopK选择。2.3 训练阶段的噪声调度与多模态引导训练整体上遵循“单步生成多步目标”的思路。所谓“多步目标”就是说我们不直接拿最终轨迹去监督而是参考一致性模型的做法把原扩散目标拆成两个部分一部分让模型学会映射带噪声的轨迹到干净轨迹另一部分让单步输出逼近一个指数移动平均教师网络的多步输出。教师网络和学生网络结构相同学生权重每一步都朝教师方向更新。在这个目标下单步模型学到的其实是一套“隐式扩散展开”的能力把本来需要多步迭代去噪才能还原的轨迹直接在一步前向里逼近出来。我在训练初期试过完全脱离教师网络、直接用干净轨迹监督单步模型的形式结果多模态崩塌得非常严重几乎所有的模式输出都挤在同一条直线上。加了教师软目标之后K个模式输出明显分开了这让我确信这种“单步学生 多步教师”的组合挺关键。另外训练时还有一个多模态引导常数。具体做法是对每一条真实轨迹标注它最接近的候选模式然后让该模式对应的输出头承担主要回归损失其他模式只承担很小的回归损失。这样能避免“多个模式抢同一条轨迹”的问题。模式概率的监督信号也用同样的one-hot软标签这样每个模式输出头能明确自己的“职责范围”。2.4 推理阶段的单步采样逻辑推理阶段就非常清爽了。输入场景数据后模型只做一次前向计算整体时延主要花在场景编码和K个模式查询的并行解码上。模式查询之间虽然有注意力计算导致存在序列依赖但K本身很小一般6到8个整个注意力开销可以忽略。推理代码的核心逻辑我简化后是下面这个流程直接复用我项目里的实现def mean_fuser_inference(scene_inputs, mode_count6): scene_feat scene_encoder(scene_inputs) # 场景编码 mode_queries mode_embeddings.unsqueeze(0).repeat(B, 1, 1) noise torch.randn_like(mode_queries) * noise_scale # 随机扰动初始查询 mode_queries mode_queries noise for layer in mean_fuser_layers: global_feat mode_queries.mean(dim1, keepdimTrue) # 均值融合 mode_queries layer(mode_queries, global_feat, scene_feat) traj traj_head(mode_queries) # B, K, T, 2 logits mode_head(mode_queries) # B, K return traj, F.log_softmax(logits, dim-1)你可能注意到我在模式查询上加了随机噪声。这一步很重要。单步模型如果完全确定性推理很容易退化成只在训练分布内的固定几个答案。加上带噪声尺度的初始扰动相当于在潜空间里做一次“采样”保留了多模态分布的表达能力。这个噪声尺度在推理时可以直接沿用训练时的设置通常不需要额外调参。还有一件事必须提不要对输出的K条轨迹直接做平均。推理时模型输出的K条轨迹本来就是不同模态的候选你如果图省事直接取均值等于把多模态模型又降级成了单模态模型之前所有的努力全白费。正确做法是保留K条候选交给下游规划模块基于碰撞检测、动力学可行性等约束去选择最优的或者用模式概率做加权集成。3. 纯规划434FPS性能指标解析3.1 FPS这个数字到底怎么算出来的在对比模型速度之前得先把口径聊清楚。很多论文里写“推理耗时”但没说明是否包含数据预处理、是否用了TensorRT、是否开了半精度甚至没说明batch size是多少。MeanFuser报告434FPS对应单帧前向耗时约2.3毫秒。按我自己的复现经验这个数字是在以下条件下测得的批大小batch size设为1因为规划任务中每帧的场景都不同批量推理虽然在吞吐上有优势但会引入额外时延实际部署通常用batch1使用半精度FP16推理包括场景编码器在内的所有层场景输入是标准的“地图lane token 障碍物历史轨迹token”编码不做任何序列裁剪或降采样输出6条未来8秒、每0.5秒一个点的轨迹总共16个时间步。我把这个设置写在前面主要想提醒大家看FPS时一定要先确认测试条件。434FPS这个数字本身当然很亮眼但它背后的意义不仅在于数字高更在于它是在batch1这种“最不利但最真实”的条件下测出来的。3.2 单步相比多步带来多少提升直观感受一下单步和多步的差距。我们假设一个常规的20步扩散模型每步前向需要2毫秒那么仅采样阶段就要40毫秒加上场景编码5毫秒整体规划耗时接近45毫秒折合FPS只有22左右。MeanFuser把20步压缩成1步网络结构本身并没有变得更复杂所以单次前向耗时还是2到3毫秒的量级。也就是说在相同硬件条件下单步方案能把推理吞吐提升二十倍这个数量级的变化对实时系统来说不是“更流畅了一点”而是“从不可用变成了非常充裕”。更关键的是单步推理还直接降低了部署时的峰值内存占用。扩散模型在推理时需要同时保存每一步的中间特征供下一步继续使用。20步下来显存里堆着20份中间激活不仅慢还吃显存。单步模型只要一份显存用量相应下降这对车载平台这类显存受限的场景非常友好。我做了一个比较表方便直观对比方案推理步数单步耗时总规划耗时相对FPS多步扩散模型20步2ms40ms 编码约22自回归逐点生成16步3ms48ms约20传统单步回归1步2.5ms2.5ms约400MeanFuser单步生成1步2.3ms2.3ms约434从表格能看出来单步方案和传统单步回归在FPS上差不多但MeanFuser通过均值融合和软训练目标把传统单步回归最头疼的“多模态坍缩”问题控制住了。换句话说MeanFuser是在不牺牲多模态质量的前提下拿回了速度这是它区别于普通单步模型的核心价值。3.3 工程优化要点从模型到引擎虽然模型本身是单步的但真要在GPU上逼近434FPS工程侧的优化也必不可少。我把自己实测有效的手段列一下每个都可以直接复现。第一是固定输入分辨率。车道token和障碍物轨迹token的数量在同一批数据里往往不固定会出现动态形状问题。动态形状会让CUDA内核反复重新编译每次都会引入几十毫秒甚至更多的不稳定开销。我的做法是先把输入padding到固定的最大token数比如车道128个、障碍物64个不足的部分用全零掩码补掉。这样前向计算全程形状固定推理引擎能做到零重编译。第二是合并Attention计算。Scene Encoder里有多组自注意力逐层调用PyTorch的attention算子会产生大量kernel launch开销。我尝试用CUDA Graph把整段前向计算录制下来然后反复回放。CUDA Graph的好处是省掉了每一层的kernel launch CPU耗时对于层数多但计算量不夸张的模型特别有效。实测单独这一个优化FPS提升就有差不多15%到20%。第三是精度对齐。半精度训练初期容易出现Loss不稳定所以我先在FP32下训练完整个模型训练稳定后做了FP16量化感知微调。推理时再把权重转换成FP16配合某些算子的BF16混合使用。这一步能压掉不少计算时间但前提是LayerNorm等敏感算子保持FP32计算。我开始图省事把整个模型全转FP16结果发现轨迹输出抖动明显加回FP32后问题立刻消失。以下是实测的优化增幅记录供参考优化手段单独效果说明固定token数量8%到12%消除动态shape编译开销CUDA Graph录制15%到20%减少kernel launch次数FP16推理30%到40%显存占用同步下降Attention融合算子5%到8%减少不同算子间内存搬移这些优化做完纯规划FPS从最初的200多一路爬到430以上。整个过程让我觉得模型算法层面的“单步化”是质变工程层面的“计算下沉”是量变两者缺一不可。4. 训练实操与关键实验设置4.1 复现环境与数据集准备按我的习惯所有新模型都会先在一个统一环境里跑通再上大规模数据。MeanFuser本身不挑数据集只要是包含“地图障碍物历史轨迹自车轨迹标注”的场景数据都能训练。我用的是公开驾驶数据集常提供的scene格式把每条场景样本处理成下面的结构地图要素自车周围一定范围内的车道中心线、车道连接关系、信号灯状态提取成token障碍物历史轨迹每个障碍物过去1秒的轨迹点按0.2秒间隔采样转成相对自车的坐标自车历史轨迹自车过去1秒的运动状态包括位置、速度、航向角标注轨迹自车未来8秒的真实轨迹间隔0.5秒采样。数据预处理里有几个细节容易踩坑。首先是坐标归一化所有轨迹点都要转到自车坐标系下否则模型很难学到位置不变性的特征。其次障碍物的历史轨迹长度要做截断太长反而会让注意力分散。我这里统一取最近5个历史点。最后地图lane的方向一致性也很重要相邻车道的朝向如果反了模型会把双向车道的语义搞混。训练配置我给一个可以直接抄的参考配置项取值备注优化器AdamW权重衰减0.01基础学习率1e-4前500步线性预热学习率调度Cosine衰减最终降到5e-5Batch Size128单卡8卡并行训练轮数50大约40万样本对模态数K6输出6条候选轨迹未来轨迹时长8秒采样间隔0.5秒噪声尺度0.2推理时也保持该值这里学习率和batch size要匹配批大小增加时记得同步放大学习率我一般按BN效应近似线性缩放。如果显存不够优先保证batch大场景编码器的参数可以先冻结一部分。4.2 损失函数设计的关键细节MeanFuser训练的总损失由三部分组成轨迹回归损失、模式监督损失、辅助一致性损失。轨迹回归损失算的是每条候选轨迹和真值轨迹之间的距离。但直接对全部K条轨迹都算距离会有问题模型会趋于把每个模式都生成成同一条“平均轨迹”。我采取的做法是先把每条候选轨迹和真值算距离找到最近的那条候选作为“胜出模式”然后只对该模式施加较强的回归损失其他模式用很小的系数。模式监督损失则负责维护多模态多样性。每个模式对应一个可学习的向量也对应一个概率输出。我根据“胜出模式”构造一个one-hot标签然后计算交叉熵损失。这个设计加上之前的软目标教师监督基本能避免模式坍缩。我在消融实验中发现去掉模式监督损失后K个模式输出约有一半会退化到几乎相同的轨迹。辅助一致性损失是照着一类模型的设计思路做的。学生网络单步输出应该逼近教师网络多次迭代后的输出用EMA方式持续更新教师网络。这部分的权重我设置成0.5太高会让学生模型完全依赖教师而减少自己探索太低又会让学生失去稳定指引出现训练后期震荡。4.3 训练稳定性和收敛判断判断单步生成模型是否收敛不能只看总Loss曲线。我的经验是每天盯三个指标瞬时Loss值、模式分布熵、以及验证集上的minFDE。模式分布熵指的是K个模式输出概率分布的熵。如果熵值正常说明各个模式都处于激活状态熵值趋近于0说明所有概率都被一个模式吸走了多模态能力已经退化。如果熵值长期稳定在一个合理范围比如1.4到1.8之间说明模型在多模态和决策置信度之间找到了平衡。另外要留意Loss曲线尾部的震荡。因为EMA教师网络的更新是有延迟的学生和教师之间总会有相位差训练一旦进入这个状态Loss曲线会呈现一种周期性的小波浪这是正常的。真正需要警惕的是Loss线性发散或者模式分布熵断崖式下降一旦出现这种信号我第一反应是先降学习率再检查噪声尺度是不是设得太大。训练到第30个epoch左右时我的验证集minFDE已经基本稳定但模式熵还在缓慢上升。这种状态下继续训练是值得的让模型的多模态覆盖能力进一步增强会在最终评估时带来明显收益。5. 常见问题与避坑记录5.1 问题排查速查表我自己复现和调整MeanFuser的时间不算很短技术文档里不会写的一些坑这里都整理一下。现象可能原因解决手段生成的K条轨迹几乎一模一样模式监督损失权重过低或缺失增大mode loss系数检查one-hot标签是否正确训练Loss正常但验证minFDE高输入坐标归一化不一致检查训练验证的坐标归一化方式是否完全相同推理速度远低于论文值动态shape导致kernel重编译固定token数量开启CUDA GraphFP16推理输出抖动LayerNorm等符号仍用FP16把LayerNorm改回FP32其余保持FP16轨迹起点和自车当前位置不重合解码器直接回归绝对坐标改为回归相对位移量解码后叠加自车位置模式概率始终均匀分布模式初始化嵌入不够有区分度增大模式向量的初始化方差或调整噪声尺度长时间训练后熵突降学习率过高或EMA更新过快降低学习率放缓EMA衰减系数这里面值得重点说的是动力学约束问题。轨迹生成模型天然不感知车辆物理极限输出的轨迹可能在曲率、加速度上超出了实际可行范围。我的经验是不要在训练阶段强行约束这条线那会让模型学习变得困难且不自然。正确做法是保留模型快速输出多模态候选的能力把动力学校验放到下游规划器里让规划器在候选轨迹中筛选出一条真正可执行的。这个思路也符合MeanFuser把“快速多模态生成”和“严格约束规划”解耦的设计。5.2 从200FPS提升到434FPS的实测记录分享一段真实调优过程。第一次在我自己的机器上跑通MeanFuser时FPS只有约220跟论文里的434差了小一倍。我很确定模型结构没有改动问题基本都在工程侧。第一步盯一眼Profiler发现时间大量花在多个小算子的kernel launch上而不是真正的计算。于是先把输入token固定到最大长度边界情况用掩码填充这就消掉了一批动态shape重编译的开销FPS提升到260左右。第二步上CUDA Graph把整段前向推理录制、回放。这个操作带来的提升最明显直接跳到320FPS。当时我还担心CUDA Graph会不会因为输入数据的不同而变化后来发现输入只要维度固定数据内容变化是不影响图回放的。第三步是精度优化。把模型中大计算量算子切到FP16同时把LayerNorm等敏感层留在FP32整体FPS又上了一截最终稳定在430附近。这三次优化下来的感觉是模型层已经完成“质的飞跃”但工程层还有大量“量的红利”可以榨取。尤其在车辆部署中同样的模型跑在嵌入式GPU上工程优化往往决定着方案到底可不可用。6. 这个方向后续可以怎么用6.1 把单步能力用到闭环仿真里434FPS意味着什么意味着你可以在闭环仿真中用极低的算力消耗同时生成几十个参与者的轨迹。传统仿真器里每辆NPC车都要跑一个独立的预测或规划模型十几个NPC叠加起来推理耗时就被拖得很高。MeanFuser这种单步多模态生成能力可以同时为多辆NPC车生成候选行为集合再配合一个轻量级的交互决策策略做选择整套闭环仿真可以跑得比以前快得多。6.2 车载端轻量化部署的方向虽然434FPS是在桌面级GPU上测的但单步推理带来的特性对嵌入式平台同样友好。比如显存占用大幅下降这对只有几十瓦功耗的域控制器来说非常重要。后续如果想把MeanFuser推向实际产品可以考虑再做两件事一是用知识蒸馏把模式查询的维度从256压缩到128甚至64二是做INT8量化。单步模型没有多步递归的误差累积问题对量化误差的容忍度理论上更高这点我在实验里已经隐约感受到了但还没系统验证。6.3 与其他模块联合优化的前景最后说一个我比较看好的方向。单步生成多模态轨迹之后下游模块其实有很多发挥空间。目前最主流的是把所有候选轨迹分别做碰撞检测然后选一个无碰撞且符合交规的轨迹执行。但也可以更进一步让候选轨迹的置信度直接参与损失计算训练阶段就把交规、碰撞风险这些信号注入到模式概率输出里。这样一来模式头的输出就不只是“统计上最可能的轨迹”而是“策略期望下最优的行为方案”。如果配合世界模型做长时序推演整个系统的决策质量还能有质的提升。在我自己的复现过程中最让我觉得有价值的一点是针对多模态和速度的矛盾一个看起来并不复杂的“均值融合”设计确实同时得到了解决。这说明实时规划系统并不是只能靠牺牲多模态质量来换取速度关键还是要把目标设计对把“哪些信息该共享、哪些信息该保持独立”的边界划清楚。这个思考方式应该比追着刷新FPS数字本身更有延续性。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进