一句话总结

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 轨迹积分获取

常见坑

  1. BEV 高度信息丢失 → 对于高架桥、地下通道等场景,补充 Occupancy 3D 头
  2. LLM 幻觉污染几何预测 → 语义门控加 dropout,不要让 LLM 特征权重过大
  3. 点云未来预测的时间偏移 → 确保标注的未来帧是相对当前车辆坐标系而非全局坐标系

什么时候用 / 不用?

适用场景 不适用场景
需要同时理解语义和预测几何 只需要目标检测或追踪
有大量多模态标注数据 小数据集(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