☰
OmniDrive:端到端自动驾驶的多模态大模型实践
2026/10/26 5:07:33 网站建设 项目流程

做自动驾驶算法的人大概都有过这种体验:感知模块跑得挺稳,检测框画得也漂亮,可一旦要回答“前面那辆车为什么在减速”“左边这条窄道能不能借一下”这类问题,整条链路就哑了。传统模块化架构把感知、预测、规划切成三段,每段各自优化各自的指标,中间靠一层低维符号(检测框、车道线、轨迹点)传递信息,信息在这一层被压得很扁。OmniDrive 这个方向想干的事情,就是把这层“压缩”拆掉,让一个模型同时吃下多视角图像、点云、历史轨迹和自然语言指令,直接吐出可执行的行驶决策与轨迹。它解决的不是某个单点精度问题,而是感知—推理—规划三者在表示层面割裂这个结构性毛病。这篇内容适合两类人看:一类是想把大模型往车端塞、但被时延和显存卡住的工程同学;另一类是想搞清楚“全模态驾驶智能体”到底怎么做数据对齐、怎么做训练调度的算法同学。下面我按自己趟过的顺序,把这套东西拆开讲。

1. 为什么非要把感知、推理、规划塞进同一个模型

1.1 模块化链路的三处硬伤

先说清楚痛点在哪,不然“端到端”就只是个时髦词。模块化链路的第一处硬伤是信息不可逆丢失:感知模块输出的是三维框和类别,一帧画面里几万个像素被压缩成几十个向量,纹理、光照、遮挡关系、路面材质的湿滑程度全没了。规划模块拿到这些符号,只能靠规则和经验去猜,遇到“前车尾灯没亮但在缓慢下沉”这种隐含制动意图的场景,符号层根本没有对应字段可填。

第二处是目标函数互相打架。检测头优化的是 mAP,追求召回率,宁可多报几个假框;规划模块追求的是舒适性和安全性,希望输入干净、少抖动。两个目标在联合训练时梯度方向并不一致,中间又隔着一层不可导的 NMS,梯度传不回去,只能各训各的。第三处是长尾场景的规则爆炸:每发现一个 corner case 就打一个补丁,补丁之间还会互相冲突,维护成本随时间指数上升。这三处加在一起,就是为什么很多团队感知指标刷得很高,实际路测体验依然僵硬。

OmniDrive 的思路是把这三段合成一条可导的链路:几何特征不经过符号化,直接进入语言模型的表示空间,规划输出也从“规则生成”变成“模型解码”。这样做的好处是梯度能从最终轨迹回传到图像编码器,坏处是训练难度和推理成本都上了一个台阶——后面我会专门算这笔账。

1.2 一个模型吃三件事的整体架构取舍

架构上最核心的取舍是:用什么样的中间表示,把视觉几何和语言语义缝在一起。可选方案大致三种。第一种是纯文本桥接,把感知结果转成一段描述性文字喂给大模型,实现最简单,但几何精度全丢,相当于绕了一圈又回到符号化。第二种是纯 cross-attention 桥接,让语言模型的每一层都去 attend 图像特征,表达能力强,但 token 数量爆炸,7B 模型配上六路高分辨率图像,显存直接翻车。

OmniDrive 走的是第二条路的收敛版本:先用一组可学习的 3D query 把多视角图像压成固定数量的稀疏 token,再把这一小撮 token 投影进语言空间。固定数量是关键——它把视觉侧的 token 预算从“随分辨率线性增长”变成“常数”,后面无论加多少路相机、提多高分辨率,进语言模型的 token 数都是可控的。这是我实测下来最实用的一招,也是整个框架能跑在车端算力上的前提。缺点也明显:query 数量有限,密集小目标(远处锥桶、路面坑洞)容易被漏掉,需要靠额外的占用栅格头兜底。

1.3 什么样的团队值得跟进这套思路

不是所有人都该上这套。我的判断标准有三条:一是你手上已经有带语言标注或可自动生成语言标注的多模态数据,没有数据这块,模型再漂亮也是空转;二是你的算力至少能支撑一次 7B 级别模型的多卡联合训练,哪怕只是 LoRA,单卡 24G 是绝对不够的;三是你的产品场景对“可解释”有需求,比如需要向乘客解释为什么变道、为什么绕行,纯数值规划器说不出人话。三条里满足两条以上,这套路线就值得投入;只满足一条,建议先在仿真里做小规模验证,别急着上车。

2. 数据与模态:输入到底长什么样

2.1 六路环视图像、稀疏点云与文本指令的统一表示

OmniDrive 的输入是异构的,得先统一到同一套“帧 + 坐标系 + 时间戳”的规范下。图像侧一般是六路环视相机,前视分辨率最高,侧后视可以适当降采样,我通常配成前视 1600×900、其余 800×450,理由是前视承担了绝大部分远距离目标和交通标志识别任务,侧后视主要用于近距离加塞和变道判断,不需要那么高的解析度。点云侧是激光雷达的稀疏点,不一定每一帧都同步,可以按最近邻时间戳对齐,或者干脆把点云只用作训练期的几何监督,推理时不输入——后者在成本敏感的量产方案里更常见。

文本侧是很多人容易做糙的地方。指令不能只有“向前行驶”这种废话,得覆盖三类:导航级指令(“前方路口左转进辅路”)、交互级指令(“礼让右侧汇入车辆”)、解释级问答(“刚才为什么减速”)。前两类用于训练规划,第三类用于训练推理链。坐标系统一尤其要盯死:所有真值轨迹必须落在自车当前帧的局部坐标系,角度用弧度且范围统一到 [-π, π],一旦混进全局坐标系,模型学到的就是“我在世界地图上的绝对位置”,换个城市立刻失效。这个坑我踩过,loss 曲线看着正常,验证集上轨迹偏差却大得离谱,查了两天才发现是某批数据的坐标系没转换。

2.2 三维占用与检测标注如何跟语言对齐

语言和几何的对齐不能靠模型自己悟,得在数据层面给出锚点。具体做法是:每条涉及空间关系的问答,都绑定一组三维标注 ID。比如问“左前方那辆白色货车离我多远”,答案里的“白色货车”对应一个具体的 3D 框 ID,“12 米”对应这个框中心到自车中心的欧氏距离。这样训练时,语言侧的 token 和几何侧的 query 就有了显式的对应关系,模型不用从零学“哪个词指哪个物体”。

占用栅格的标注更麻烦。公开数据集里的占用标注通常是体素级的 0/1 标签,分辨率 0.4 米左右,但语言问答题里经常会问“这条路能不能过”,答案需要对通行性做出判断,而不是逐个体素分类。我的做法是加一层轻量的几何后处理:把自车前方一条走廊(宽度约 3 米、长度 30 米)内的体素统计出来,占用率低于阈值就判为可通行,再把这个判定和对应的问答对绑起来。这样语言侧的“能不能过”就有明确的数值来源,不会变成模型瞎猜。

2.3 数据配比与采样策略的实操心得

配比这块没有标准答案,但有几点是我试出来的经验。第一,语言数据不要追求量大,要追求分布均衡:感知类问答、预测类问答、规划类问答、解释类问答大致按 3:2:3:2 分配,某一类超过 40% 模型就会偏科,问它“为什么”它给你背检测结果。第二,长尾场景要过采样但不能只过采样:雨天、夜间、隧道出口这些场景建议按 2 到 3 倍采样,再高会让模型对正常天气的泛化变差。第三,空帧要留:连续几帧没有任何目标的空旷路段也得保留一定比例,否则模型一看到空旷就慌,输出莫名其妙的减速。

采样策略上我用的是“场景簇均衡 + 帧级随机”的混合方式:先把数据按场景类型(城市拥堵、高速巡航、路口、泊车)聚类,每个 epoch 从每个簇里等量抽,簇内再随机抽帧。这样能避免高速场景帧数多就把城市复杂场景淹掉的问题。代价是每个 epoch 的数据顺序不可复现,调试时得固定随机种子。

3. 核心结构与关键实现细节

3.1 视觉编码与三维特征构建

视觉主干我建议用 ViT 系列而不是 ResNet,原因是 ViT 的 patch 表示更容易和语言 token 做维度对齐,而且多尺度特征融合更自然。六路图像分别过共享权重的 backbone,得到每路的多尺度特征图,然后做视角变换。视角变换有两条主流路线:一条是 LSS 式的显式深度分布预测加外积投影,优点是几何可解释,缺点是对深度估计误差敏感;另一条是 BEV query 加 cross-attention 的隐式查询,优点是精度高,缺点是训练慢、显存吃紧。

我的选择是混合:近距离(0 到 20 米)用 LSS 显式投影保证精度,远距离(20 米以外)用稀疏 query 查询保证效率。这个分界线不是拍脑袋定的,是根据相机标定误差算出来的——20 米外像素级深度误差已经超过 1 米,显式投影的收益被误差吃掉了,不如交给 query 去学。BEV 网格分辨率我用 0.5 米,范围前后 100 米、左右 50 米,算下来是 200×200 的栅格,特征通道 256,这一层特征就是后面所有感知头的公共底座。

3.2 查询式感知头:目标、地图、占用三类 query

query 的设计是整个框架的骨架,我把它分成三类,各自有独立的初始化方式和更新逻辑。目标 query初始化自 BEV 特征上的高响应点,数量固定为 300 个,负责检测动态目标,输出中心点、尺寸、朝向、速度。地图 query数量 100 个,负责矢量化的车道线、路沿、停止线,初始化用均匀网格撒点再加位置编码,因为地图元素分布比较均匀,不需要靠特征响应去引导。占用 query数量 64 个,不做显式监督,只作为稠密几何的压缩桶,把 BEV 特征里没法被前两类 query 覆盖的残余信息兜住。

三类 query 之间要有交互,我用的是交替的 self-attention 加 cross-attention:self-attention 在同一类内部做,cross-attention 让目标 query 去查询地图 query 的上下文(判断这个目标是不是停在车道里),再让所有 query 去查询 BEV 特征。这个顺序很重要,反过来先查 BEV 再做类间交互,模型会倾向于把地图元素也当成动态目标,导致车道线被预测出速度。这个细节文档里不会写,但影响很大。

3.3 语言主干与跨模态投影层

语言主干选 7B 级别的开源模型做底座,具体型号看你的许可证约束。关键在于投影层怎么设计。最省事的方案是一个两层 MLP,把 query 输出的 256 维特征映射到语言模型的隐藏维度,参数量小、训练快,但表达能力有限,复杂空间关系(“第二辆车后面那个骑车的”)学不好。更重的方案是 Q-Former 式的可学习查询池,用一组固定的 latent query 去“读”感知特征,再交给语言模型,表达能力强但多一层训练难度。

我用的是折中方案:MLP 加一层轻量 cross-attention,cross-attention 的 query 是语言 token 的嵌入,key/value 是感知 query 的输出。这样语言侧可以按需去“取”几何信息,而不是被动接收一堆固定 token。实测下来,涉及多目标空间关系的问答准确率比纯 MLP 高约 8 个百分点,参数量只多了不到 20M。训练时注意,投影层的学习率要比主干高一个数量级,否则它收敛太慢,主干会先过拟合到语言任务上。

3.4 从推理到轨迹:输出头的落地形式

轨迹输出有两种形式,回归和离散 token 分类。回归直接吐连续坐标,简单但容易抖;离散化把前方可行区域切成格子(横向 0.5 米一档、纵向 1 米一档、时间维 0.5 秒一档),每一步预测落在哪个格子里,稳定但精度受格距限制。OmniDrive 这类框架普遍用离散 token,因为要和语言模型的词表统一——轨迹 token 就是词表里额外扩出来的一批特殊 token,模型输出它们就像输出文字一样自然。

我的配置是:预测未来 4 秒,每 0.5 秒一个点,共 8 个轨迹点,每个点用横纵向两个离散 token 表示,所以一条完整轨迹是 16 个 token。词表扩展 2048 个(横向 32 档 × 纵向 64 档),比直接扩几万个连续 bin 要省显存。推理时用受限解码,只在轨迹 token 集合里采样,避免模型输出“前方 3 米左转 90 度”这种物理上不可能的结果。这里要加一层运动学约束:相邻两个轨迹点之间的横向偏移不能超过对应时间内的最大侧向加速度所允许的距离,超了就回退到上一帧轨迹做平滑。

4. 训练流程与实操复现要点

4.1 显存预算估算与硬件配置

先把账算清楚,不然方案设计得再漂亮也跑不起来。假设视觉主干用 ViT-L(约 300M 参数),语言主干 7B,投影层 20M,感知头约 50M,总参数量约 7.4B。bf16 精度下,纯权重占 14.8GB。优化器用 AdamW,一阶二阶动量各占一份,又是 29.6GB。梯度占 14.8GB。这三项加起来已经 59GB,还没算激活值。

激活值的大头在视觉侧:六路 1600×900 图像,patch size 14,每路约 7200 个 patch token,六路合计 43000 多个 token,经过 24 层 ViT,每层都要存 attention 矩阵。用梯度检查点(gradient checkpointing)可以把激活降到原来的三分之一左右,代价是训练速度慢 25% 到 30%。语言侧用 LoRA(rank 16)冻结主干,可训练参数降到 40M 左右,优化器状态基本可以忽略。这么算下来,单卡 80G 跑 batch size 1 是可行的,想上 batch 8 至少需要 8 卡配 ZeRO-2,或者 16 卡配 ZeRO-3。我实际跑的配置是 8 卡 80G、ZeRO-2、梯度累积 4 步,等效 batch 32,单步耗时约 2.3 秒。

4.2 三阶段训练:对齐、联合、指令微调

一次性端到端训这套东西几乎必然失败,我的经验是拆成三个阶段。第一阶段只训感知:冻结语言主干,只训视觉主干、感知头和投影层,损失用检测的 L1 加 GIoU 加 focal、占用的交叉熵。这一阶段的目标是让 query 学到有物理意义的几何表示,大概跑 20 个 epoch,检测 mAP 收敛到接近纯感知模型的水平。

第二阶段才是联合训练:解冻投影层和语言侧的 LoRA,让语言损失(next-token 交叉熵)加进来,权重给 1.0,感知损失权重降到 0.5。这一步最危险,语言损失的梯度很容易把刚训好的几何表示冲垮,表现为检测指标突然掉一截。我的应对是视觉主干保持冻结,只让感知头和投影层参与联合优化,同时监控检测损失,一旦连续三个 epoch 上升就回滚学习率。

第三阶段做指令微调:加入解释类问答和复杂交互指令,学习率降到第二阶段的十分之一,只训 LoRA 和投影层。这一阶段数据量不用大,几万条高质量样本足够,重点是把模型的“说话方式”调顺,别让它输出一堆标点混乱的检测字段。

4.3 损失函数与关键超参配置

损失函数是几路加权求和,权重怎么定直接影响最终表现。我的配置如下:

loss_cfg = { "cls": {"weight": 2.0, "type": "focal", "gamma": 2.0, "alpha": 0.25}, "box_l1": {"weight": 0.5, "type": "l1"}, "box_iou": {"weight": 1.0, "type": "giou"}, "map": {"weight": 1.0, "type": "l1_line"}, "occupancy":{"weight": 1.5, "type": "bce", "pos_weight": 3.0}, "traj_cls": {"weight": 2.0, "type": "ce", "label_smooth": 0.1}, "traj_l1": {"weight": 0.5, "type": "l1"}, "lang": {"weight": 1.0, "type": "ce", "ignore_index": -100}, }

几个值得说的点:分类损失给 2.0 是因为正样本极少,权重低了模型会倾向于全预测背景;占用损失的 pos_weight 给 3.0 是为了对抗体素级正负样本极度不均衡,这个值不要超过 5,否则模型会把整片空间都预测成占用,看起来召回很高实际没法用;轨迹分类损失加 label smooth 0.1 是为了抑制抖动,让相邻轨迹 token 的分布别太尖锐。语言损失的 ignore_index 一定要设对,padding 位置不屏蔽的话,模型会把大量算力浪费在学“怎么输出 padding”上。

学习率方面,视觉骨干用 1e-5(如果是微调预训练权重),感知头 1e-4,投影层 5e-4,LoRA 用 2e-4。整体用 cosine 衰减,warmup 比例 0.05。梯度裁剪阈值设 1.0,这套多任务联合训练里梯度爆炸是家常便饭,裁剪比调学习率更管用。

4.4 评测指标与验证方式

只看一个 mAP 是自欺欺人。我关注四组指标:检测侧看 mAP 和 NDS 类综合分;占用侧看 IoU 和特定类别(行人、车辆、可行驶区域)的分项 IoU;规划侧看位移误差、碰撞率、舒适度分项(加速度抖动、横摆角速度超限比例);语言侧看问答准确率和幻觉率。幻觉率这个指标特别重要——模型有没有编造不存在的物体,直接决定了这套系统能不能被信任。我的测法是固定一组包含已知目标数量的场景,统计模型描述中出现的不存在目标数,超过 5% 就要回去查投影层和数据对齐。

验证集一定要按场景切分,不能按帧随机切。同一段路的相邻帧如果一半在训练集一半在验证集,指标会虚高十几个点,这一点在时序模型里尤其明显。我一般按地理位置加时间段做双重切分,比如用三个城市的早高峰、两个城市的夜间数据做验证,这样测出来的指标才敢拿去汇报。

5. 常见问题与排查手记

5.1 损失震荡与梯度异常

联合训练阶段损失震荡是最常见的问题,表现是语言损失正常下降,检测损失上下横跳。八成是梯度冲突,不是学习率问题。排查方法很简单:分别打印感知头和语言侧的梯度范数,如果感知侧范数比语言侧高一个数量级以上,就是量级失衡。我的解法是给两路损失各加一个可学习的权重(uncertainty weighting),让模型自己调平衡;如果不想引入额外参数,就手动把感知损失权重乘上梯度范数的反比做归一化。

还有一种情况是 loss 直接变 NaN,通常出在占用头的 BCE 上。浮点下溢会导致 log(0),加个 1e-7 的 eps 就好。另外注意混合精度训练时,投影层的 cross-attention 里 softmax 容易溢出,把那里的 attention 计算强制转成 fp32,代价很小但能省掉几天的排查时间。

5.2 语言能力退化与“只会报框”现象

训到第二阶段,模型经常变成“只会报检测结果”的复读机,问它为什么减速,它回一句“前方有车”。这是灾难性遗忘的典型表现,几何梯度过强把语言能力压没了。三个手段组合使用:一是在联合训练时混入 10% 到 20% 的纯语言通用数据,保持语言模型的基础能力;二是 LoRA 的 rank 别调太小,16 是下限,复杂推理任务建议 32 到 64;三是分阶段解冻,先只训投影层,等语言损失稳定了再开 LoRA。

另一个隐蔽的问题是指令跟随失效,模型忽略“缓慢”“尽快”这类程度副词。查下来往往是数据里这类词太少。解决办法是在生成问答对时,对同一条轨迹配多个不同语气、不同紧急程度的指令描述,让模型学会把措辞差异映射到轨迹差异上。

5.3 轨迹抖动与物理不合理输出

轨迹抖动有两种来源,得分开治。一种是 token 级抖动,相邻两帧输出的轨迹 token 跳变,属于解码不稳定,用温度采样加 top-k 限制能缓解,或者干脆对输出做时序平滑。另一种是物理抖动,轨迹本身合理但二阶导不连续,乘客坐着难受。后者要在训练时加约束,最简单的做法是加一项加速度惩罚损失,把相邻三个轨迹点的加速度算出来,超过阈值就罚。

还有一种更麻烦的情况:模型输出的轨迹在几何上不撞任何东西,但明显违反交通规则,比如在实线处变道。这类问题靠纯数据驱动很难根治,我的做法是加一层轻量的规则校验,把车道线和轨迹做几何相交判断,越线就重采样。这层校验放在后处理里,不参与训练,成本低且可控。

5.4 常见问题速查表

现象可能原因排查手段处理方式
检测 loss 正常但 mAP 停滞query 初始化退化,全部挤在同一区域可视化 query 的中心点分布加 query 间斥力损失或改用匈牙利匹配
占用预测全为占用pos_weight 过大或标签泄漏统计预测正例比例把 pos_weight 降到 2 以内,检查标注
语言问答全是模板句指令微调数据多样性不足抽样看训练集问句长度分布补数据,增加句式变换
轨迹整体偏移一个固定量坐标系转换矩阵有误用真值轨迹反投影验证检查外参和时间戳对齐
推理时延翻倍KV cache 未复用或图像 token 未裁剪分段计时开启 cache,裁掉边缘视角的低价值 token
夜间场景性能断崖训练集夜间样本占比过低按光照条件分组统计过采样夜间并加亮度扰动增强

6. 车端部署的算力账与时延控制

6.1 推理时延的分段拆解与优化手段

训练能跑通只是第一步,上车才是硬仗。目标定在 10Hz 输出,也就是 100 毫秒一帧的预算。拆开来看:六路图像预处理加骨干推理大约 35 毫秒(用 TensorRT 量化到 INT8,ViT-L 在中等算力平台上大概就是这个量级);query 更新和感知头 15 毫秒;投影层加语言模型解码 40 毫秒;轨迹后处理与规则校验 5 毫秒。加起来 95 毫秒,卡得死死的,没有任何余量。

优化重点在语言模型这一侧。7B 模型逐 token 解码,光生成 16 个轨迹 token 就要走近十次前向,成本很高。我的做法是把轨迹输出从自回归改成一次性并行输出:轨迹 token 之间没有强因果关系,不需要一个个生成,直接用一个轻量 MLP 头并行吐出 16 个位置的概率分布。这样语言模型只需要跑一次 prefilling,时延能压到 20 毫秒以内。代价是丢掉了一点 token 间的依赖建模,实测对轨迹质量影响在 2% 以内,完全可接受。

6.2 安全兜底与降级策略

再漂亮的模型也得有兜底。我的设计是双通道:主通道是 OmniDrive 端到端输出,备份通道是一个轻量的规则规划器,只吃感知框和车道线。正常情况下主通道结果经过几何合法性校验(是否撞障碍、是否越实线、加速度是否超限)后下发;校验不通过或者模型推理超时,立刻切到备份通道,切换延迟控制在 10 毫秒以内。

降级还要分等级:算力紧张时先砍掉语言解释输出(乘客侧功能),保留轨迹;再紧张就降低图像分辨率;最极端情况直接退到纯感知加规则。这套分级策略我建议在仿真里就把每一级的触发条件跑一遍,别等到实车才发现切换逻辑有死循环。

6.3 后续可以继续深挖的方向

这套框架目前还有几个明显的短板。一是长时序记忆基本没有,模型对三秒前发生过什么只有一个模糊的印象,加塞博弈类场景吃亏;可行的方向是引入轻量的时序记忆模块,把历史 query 做压缩存起来。二是多智能体交互建模粗糙,现在只把周围车辆当成静态几何,没有建模它们的意图;可以尝试在 query 之间加一层意图预测头。三是数据闭环还没打通,模型在实车上遇到的新场景怎么自动筛选、自动标注、自动回流,这部分工程量比模型本身还大。

我在实际项目里的体会是:这套东西真正的门槛不在模型结构,而在数据管线的规范程度。坐标系、时间戳、标注一致性、语言问答的生成质量,这四样里任何一样出问题,模型表现都会莫名其妙地差,而且极难定位。所以如果你的团队正准备上手,我建议先花两周把数据规范文档写死、把自动化校验脚本跑通,再动模型代码。磨刀不误砍柴工,这句话在这个方向上格外成立。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询