HERMES++:统一 3D 场景理解与未来几何预测的自动驾驶世界模型
一句话总结
HERMES++ 在同一个框架里同时做好”看懂场景”和”预测未来”——用 LLM 的语义推理能力增强几何预测,打通了理解与生成之间长期存在的鸿沟。
为什么这个问题重要?
自动驾驶系统需要两种能力:理解当前场景(这辆车是什么?行人在哪里?)和预测未来演化(3 秒后这辆车会在哪里?)。
现有方法有一个尴尬的分裂:
- World Model 流派(如 GAIA-1、DriveDreamer):擅长生成逼真的未来视频帧,但缺乏对场景的语义理解,也很难输出明确的 3D 几何信息
- LLM 驱动流派(如 DriveLM、NuScenes-QA):能回答”前方是否有障碍物”这类问题,但不能预测物理世界的几何演化
HERMES++ 的核心主张:让 LLM 的语义理解指导几何预测,而不是让两者独立运行。
背景知识
BEV 表示:把多个摄像头”压平”
现代自动驾驶车辆通常配备 6 路环视摄像头。BEV(Bird’s Eye View,鸟瞰图)是将这些透视图像统一投影到俯视平面的表示方式。
优点:
- 消除了透视畸变,不同摄像头的坐标可以直接比较
- 天然适合 2D 规划和检测
- 可以作为 LLM 的输入 token
代价:丢失了高度维度。BEV 擅长区分”前方 20 米有车”,但对”高架桥上有车”这类垂直分布信息处理较弱。
为什么要引入 LLM?
点云预测本质上是个序列建模问题——给定当前几何状态,预测未来状态。LLM 天然擅长建模复杂依赖关系,但其训练数据是文本/图像,不是 3D 点云。
HERMES++ 的关键洞察:LLM 学到的语义知识(”这是一个路口,通常有行人过街”)可以作为约束,减少几何预测的歧义性。
核心方法
直觉解释
整个系统可以想象成两条并行的”大脑”协作:
多视角图像
│
▼
BEV 编码器 ──────────────────────────────────┐
│ │
▼ ▼
LLM 理解分支 几何预测分支
(问答、检测) (World Queries)
│ │
└──── LLM增强的World Queries ────────────┘
│
Current-to-Future Link
│
未来点云预测
│
Joint Geometric Optimization
语义分支的输出不仅用于回答问题,还通过”世界查询增强”注入几何预测分支。
BEV 特征提取
BEV 编码器的核心是可变形交叉注意力:可学习的 BEV 查询向量从多视角图像特征中聚合空间信息。
\[\text{BEV\_feat} = \text{CrossAttn}(\mathbf{Q}_{bev}, \mathbf{K}_{img}, \mathbf{V}_{img})\]其中 $\mathbf{Q}{bev} \in \mathbb{R}^{H{bev} \times W_{bev} \times d}$ 是可学习的 BEV 查询,$\mathbf{K}{img}, \mathbf{V}{img}$ 来自投影后的多摄像头图像特征。
import torch
import torch.nn as nn
class BEVEncoder(nn.Module):
"""将多视角图像特征提升到BEV空间(简化版)"""
def __init__(self, img_dim=256, bev_h=200, bev_w=200, bev_dim=256):
super().__init__()
self.bev_h, self.bev_w = bev_h, bev_w
# 可学习的BEV查询
self.bev_queries = nn.Parameter(
torch.randn(bev_h * bev_w, bev_dim)
)
# 交叉注意力:BEV查询从图像中聚合空间信息
self.cross_attn = nn.MultiheadAttention(
embed_dim=bev_dim, num_heads=8, batch_first=True
)
self.norm = nn.LayerNorm(bev_dim)
def forward(self, img_feats):
"""
img_feats: [B, N_cam, C, H, W]
实际实现中需要结合相机内外参做透视投影对齐
# ... (相机投影对齐代码省略)
"""
B, N, C, H, W = img_feats.shape
img_kv = img_feats.view(B, N * H * W, C) # 展平为序列
bev_q = self.bev_queries.unsqueeze(0).expand(B, -1, -1)
bev_feat, _ = self.cross_attn(bev_q, img_kv, img_kv)
bev_feat = self.norm(bev_feat)
# 重塑为2D特征图
return bev_feat.view(B, self.bev_h, self.bev_w, -1).permute(0, 3, 1, 2)
LLM 增强的 World Queries
这是 HERMES++ 最有意思的设计。普通的几何预测只依赖视觉特征;HERMES++ 额外引入一个语义门控:
\[\tilde{\mathbf{q}}_i = \mathbf{q}_i \odot \sigma\left(W_g [\mathbf{q}_i; \mathbf{f}_{llm}]\right)\]其中 $\mathbf{q}i$ 是第 $i$ 个 world query,$\mathbf{f}{llm}$ 是 LLM 提取的语义上下文,$\sigma$ 是 Sigmoid 门控。含义:LLM 认为语义上重要的几何区域,对应的 world query 会被放大。
class LLMEnhancedWorldQuery(nn.Module):
"""用LLM语义特征增强几何world queries"""
def __init__(self, bev_dim=256, llm_dim=4096, wq_dim=256, num_queries=900):
super().__init__()
self.llm_proj = nn.Linear(llm_dim, wq_dim)
self.world_queries = nn.Parameter(torch.randn(num_queries, wq_dim))
# 语义门控:决定哪些几何查询被语义激活
self.semantic_gate = nn.Sequential(
nn.Linear(wq_dim * 2, wq_dim),
nn.Sigmoid()
)
self.spatial_cross_attn = nn.MultiheadAttention(
embed_dim=wq_dim, num_heads=8, batch_first=True
)
def forward(self, bev_feat, llm_features):
"""
bev_feat: [B, C, H, W]
llm_features: [B, L, llm_dim] LLM最后几层的隐状态
"""
B = bev_feat.shape[0]
# 聚合LLM上下文
llm_ctx = self.llm_proj(llm_features.mean(dim=1)) # [B, wq_dim]
wq = self.world_queries.unsqueeze(0).expand(B, -1, -1) # [B, N_q, D]
llm_ctx_exp = llm_ctx.unsqueeze(1).expand_as(wq)
# 语义门控:LLM知识注入几何查询
gate = self.semantic_gate(torch.cat([wq, llm_ctx_exp], dim=-1))
wq_enhanced = wq * gate
# 从BEV中读取空间特征
bev_kv = bev_feat.flatten(2).permute(0, 2, 1) # [B, H*W, C]
wq_out, _ = self.spatial_cross_attn(wq_enhanced, bev_kv, bev_kv)
return wq_out
Current-to-Future Link:时序桥接
用当前帧的 world queries 和语义上下文,通过 Transformer Decoder 解码未来几何状态。
class CurrentToFutureLink(nn.Module):
"""以当前语义上下文条件化未来几何演化"""
def __init__(self, query_dim=256, num_future=3, num_pts=20000):
super().__init__()
self.num_future = num_future
self.temporal_decoder = nn.TransformerDecoder(
nn.TransformerDecoderLayer(
d_model=query_dim, nhead=8, batch_first=True, dropout=0.1
),
num_layers=4
)
# 未来时刻的可学习位置编码(区分t+1, t+2, t+3)
self.future_pe = nn.Parameter(torch.randn(num_future, query_dim))
# 输出头:world query → 3D坐标偏移
self.pc_head = nn.Sequential(
nn.Linear(query_dim, 512), nn.ReLU(),
nn.Linear(512, 3)
)
def forward(self, current_wq, semantic_ctx):
"""
current_wq: [B, N_q, D] 当前帧world queries
semantic_ctx: [B, L, D] LLM语义上下文
"""
B, N_q, D = current_wq.shape
# 为T个未来时刻生成查询
fut_q = self.future_pe.unsqueeze(0).unsqueeze(2) # [1, T, 1, D]
fut_q = fut_q.expand(B, -1, N_q, -1).reshape(B, -1, D) # [B, T*N_q, D]
# memory = 当前几何 + 语义上下文
memory = torch.cat([current_wq, semantic_ctx], dim=1)
fut_feat = self.temporal_decoder(fut_q, memory)
fut_feat = fut_feat.view(B, self.num_future, N_q, D)
return self.pc_head(fut_feat) # [B, T, N_q, 3]
联合几何优化
损失函数组合显式几何约束和隐式正则化:
\[\mathcal{L}_{total} = \lambda_1 \mathcal{L}_{CD} + \lambda_2 \mathcal{L}_{occ} + \lambda_3 \mathcal{L}_{reg}\]- $\mathcal{L}_{CD}$:双向 Chamfer 距离,约束点云整体几何形状
- $\mathcal{L}_{occ}$:占用预测损失,保证场景结构完整性
- $\mathcal{L}_{reg}$:隐空间正则化,对齐几何感知先验
class JointGeometricOptimization(nn.Module):
"""显式几何约束 + 隐式正则化的联合损失"""
def chamfer_distance(self, pred, gt):
"""双向Chamfer距离;pred/gt: [B, N, 3]"""
diff = pred.unsqueeze(2) - gt.unsqueeze(1) # [B, N1, N2, 3]
dist = diff.norm(dim=-1)
d_fwd = dist.min(dim=2).values.mean() # 预测→真值
d_bwd = dist.min(dim=1).values.mean() # 真值→预测
return (d_fwd + d_bwd) / 2
def forward(self, pred_pc, gt_pc, pred_occ, gt_occ, z_pred, z_prior,
lambdas=(1.0, 0.5, 0.1)):
"""
联合优化三种几何损失
# ... (每个未来时刻分别计算,对T取均值,代码省略)
"""
import torch.nn.functional as F
L_cd = self.chamfer_distance(pred_pc, gt_pc)
L_occ = F.binary_cross_entropy_with_logits(pred_occ, gt_occ.float())
L_reg = F.mse_loss(z_pred, z_prior.detach()) # 隐空间对齐几何先验
l1, l2, l3 = lambdas
total = l1 * L_cd + l2 * L_occ + l3 * L_reg
return total, {'cd': L_cd.item(), 'occ': L_occ.item(), 'reg': L_reg.item()}
可视化:当前帧与未来预测对比
import numpy as np
import matplotlib.pyplot as plt
def visualize_future_prediction(current_pc, future_pcs, gt_future_pcs=None):
"""
current_pc: [N, 3] 当前帧激光雷达点云
future_pcs: [T, N, 3] 预测的未来点云
gt_future_pcs: [T, N, 3] 可选,真值对比
"""
T = future_pcs.shape[0]
fig = plt.figure(figsize=(4 * (T + 1), 4))
def plot_bev(ax, pc, title, color='steelblue', s=0.3):
"""只画BEV(xy平面),便于对比运动趋势"""
mask = (np.abs(pc[:, 0]) < 50) & (np.abs(pc[:, 1]) < 50)
ax.scatter(pc[mask, 0], pc[mask, 1], s=s, c=color, alpha=0.4)
ax.set_xlim(-50, 50); ax.set_ylim(-50, 50)
ax.set_aspect('equal'); ax.set_title(title, fontsize=9)
ax.set_xlabel('X (m)'); ax.set_ylabel('Y (m)')
ax0 = fig.add_subplot(1, T + 1, 1)
plot_bev(ax0, current_pc, 'Current (t=0)')
colors = plt.cm.Reds(np.linspace(0.4, 0.9, T))
for t in range(T):
ax = fig.add_subplot(1, T + 1, t + 2)
plot_bev(ax, future_pcs[t], f'Pred t+{t+1}', color=colors[t])
if gt_future_pcs is not None:
# 绿色叠加显示真值
plot_bev(ax, gt_future_pcs[t], '', color='green', s=0.2)
plt.tight_layout()
# 预期输出:连续帧的BEV点云,可以看到动态物体(车、行人)的运动轨迹
plt.savefig('hermes_prediction.png', dpi=150, bbox_inches='tight')
实验
评估基准
主要在 nuScenes 数据集上评估:
- 32 线激光雷达,6 路环视相机
- 1000 个场景,覆盖城市、高速、住宅区
- 提供 3D 检测、跟踪、地图分割等多任务标注
未来点云预测评估指标:
- Chamfer Distance(CD):越低越好,衡量点云形状相似度
- L1 Distance:体素化后的占用误差
定量结果
| 方法 | CD ↓ | L1 ↓ | 3D Det mAP ↑ | 参数量 |
|---|---|---|---|---|
| OccWorld | 2.85 | 0.43 | — | ~50M |
| UniAD | — | — | 0.38 | ~97M |
| HERMES | 2.61 | 0.39 | 0.41 | ~210M |
| HERMES++ | 2.43 | 0.36 | 0.44 | ~350M |
注:数值来自论文表格,”-“表示该方法不支持对应任务
HERMES++ 同时在未来点云预测和 3D 理解两个任务上超越了各自的专家模型。代价是参数量和计算量显著上升。
工程实践
实际部署考虑
推理速度是最大挑战。LLM 分支(即使是裁剪版)推理延迟在 GPU 上通常是 50-200ms,而自动驾驶系统要求 10Hz(100ms/帧)以内。
实际部署中常见的折中方案:
# 常见坑1:LLM推理频率与感知频率不匹配
# 错误做法:每帧都跑LLM
for frame in camera_stream:
llm_feat = llm_model(frame) # 200ms,卡死了
# 修复:LLM异步低频运行,几何预测高频同步运行
llm_cache = None
for i, frame in enumerate(camera_stream):
if i % 5 == 0: # 2Hz更新语义上下文
llm_cache = llm_model.async_forward(frame)
geom_pred = geometry_branch(frame, cached_llm_feat=llm_cache) # 10Hz
# 常见坑2:BEV分辨率 vs. 内存的tradeoff
# 200x200 BEV @ float32 = 200*200*256*4 ≈ 40MB,还在跑6路摄像头
# 实际部署中通常降到 100x100 或使用 FP16
bev_feat = bev_encoder(img_feats).half() # FP16节省一半内存
# 但要注意:BEV太小会损失小目标检测能力(行人、骑手)
数据采集建议
- 多样性 > 数量:城市/高速/夜间/雨天场景各需覆盖,单一场景过拟合严重
- 时间同步:激光雷达与摄像头必须硬件同步,软件补偿误差通常 >10ms 不可接受
- 标注难点:未来点云的”真值”本身就是预测问题——通常用高精度 IMU+SLAM 轨迹积分获取
常见坑
- BEV 高度信息丢失 → 对于高架桥、地下通道等场景,补充 Occupancy 3D 头
- LLM 幻觉污染几何预测 → 语义门控加 dropout,不要让 LLM 特征权重过大
- 点云未来预测的时间偏移 → 确保标注的未来帧是相对当前车辆坐标系而非全局坐标系
什么时候用 / 不用?
| 适用场景 | 不适用场景 |
|---|---|
| 需要同时理解语义和预测几何 | 只需要目标检测或追踪 |
| 有大量多模态标注数据 | 小数据集(LLM分支易过拟合) |
| 高端计算平台(>16GB 显存) | 边缘部署(车载 SoC) |
| 城市复杂场景 | 高速公路(场景语义简单,不值得引入LLM) |
与其他方法对比
| 方法 | 核心能力 | 缺点 | 适用场景 |
|---|---|---|---|
| UniAD | 多任务理解(检测/规划) | 不预测未来几何 | 端到端规划 |
| OccWorld | 未来占用预测 | 无语义理解 | 场景预见 |
| DriveLM | 语言问答推理 | 无几何输出 | 可解释性 |
| HERMES++ | 理解 + 未来几何 | 计算重,部署难 | 研究/高算力系统 |
我的观点
HERMES++ 的技术思路是正确方向:语义理解和物理预测不应该是孤立的两个系统。LLM 确实知道”路口通常有行人”这类先验,把这个知识用于几何预测是合理的。
但有几个现实问题值得关注:
LLM 真的有帮助吗? 论文中的消融实验应该严格对比”有无 LLM 增强”在不同场景类型下的表现。如果提升主要来自模型参数量增加而非 LLM 的语义知识,结论就要打折扣。
实时性是硬约束。 目前 HERMES++ 的推理速度论文未明确报告,这对工业应用是红线。350M 参数的模型在 Orin X(自动驾驶常用 SoC)上跑是否能达到 10Hz 是个严峻问题。
未来几何预测本质上是不确定的。 当前方法输出确定性预测,而真实世界中未来场景有多种可能性(行人可能左转也可能右转)。引入概率输出(如 CVAE)可能是下一步。
整体来看,HERMES++ 是学术研究的重要进展,但距离量产部署还需要在推理效率和不确定性建模上有实质性突破。官方代码:https://github.com/H-EmbodVis/HERMESV2
Comments