转载自公众号:敢敢AUTOHUB
0. 简介
WorldVLN 研究的是空中视觉语言导航,也就是让无人机听懂自然语言指令,并在三维环境中连续飞行。它最重要的观点是:无人机导航不能只做“看图出动作”,而要先预测动作会让世界怎样变化,再根据预测结果生成动作。如果把传统 VLA 看成“看到当前画面就立刻反应”,那么 WorldVLN 更像“先在脑中预演下一小段飞行,再执行,并用真实新画面校正下一次预演”。下面会按从主到次的顺序解释 WorldVLN。先说明空中 VLN 为什么难,再讲 WorldVLN 的整体架构,然后进入本地代码中的服务接口、缓存机制、动作解码器和 GRPO 训练流程。读完后,你应该能理解三件事:第一,WorldVLN 为什么不是普通 VLA;第二,它如何完成“预测-动作-观测-更新”的闭环;第三,代码里哪些模块对应论文中的关键概念。
Github项目地址:https://github.com/EmbodiedCity/WorldVLN.code
论文地址:https://arxiv.org/html/2605.15964v1
1. 为什么空中VLN需要世界动作模型
1.1 任务难点:无人机看到的是不断变化的世界
视觉语言导航听起来像是“看懂图像,再照着文字走”,但无人机场景比静态图文理解难得多。无人机只能看到第一视角的局部画面,向前飞、侧移、转弯、下降都会让下一帧画面发生明显变化。比如指令是“靠近白色建筑物左侧”,模型不仅要识别白色建筑物,还要判断向哪个方向移动后,建筑物会出现在更合适的位置。真正困难的不是识别目标,而是预测自己的动作会怎样改变后续观测。
1.2 直接 VLA 的短板
传统 VLA 模型通常把策略写成条件映射:输入观测和指令,输出动作。这类模型继承了视觉语言模型的语义理解能力,擅长识别物体、场景和文本含义,但不一定理解动作导致的时空变化。对无人机来说,小的偏航误差会改变后续所有画面,小的高度误差会影响目标尺度和遮挡关系。如果模型没有世界动态先验,连续动作里的小误差就会被放大成轨迹漂移。
1.3 论文中的数学形式
论文把空中 VLN 写成部分可观测序列决策问题。给定语言指令 、历史观测 和历史动作 ,策略生成下一步动作 。论文主文中的动作是 ,前三项表示相对三维位移,最后一项表示相对偏航角。动作执行后,位姿和观测更新为 。这组公式表达的核心很简单:动作不是终点,动作会改变下一次看见的世界。
2. 本地工程解读
本地仓库 WorldVLN.code/ 可以按功能拆成四层。第一层是 Worldmodel/infinity/ 和 Worldmodel/runtime/,负责潜在自回归视频骨干和流式推理;第二层是 Worldmodel/action_decoder/,负责把视频潜表示转换为动作 token 并输出运动增量;第三层是 infer/,提供在线服务端和客户端协议;第四层是 train/ 与 action_aware_grpo/,分别对应监督训练、rollout 采集和 GRPO 后训练。先按模块边界读代码,比从模型细节硬啃更容易建立全局理解。
| 论文概念 | 本地代码位置 | 工程作用 |
|---|---|---|
| 潜在自回归世界骨干 | Worldmodel/runtime/, Worldmodel/infinity/ |
编码真实观测,预测未来 latent world transition |
| 动作解码器 | Worldmodel/action_decoder/ |
将 VAE decoder feature 映射到 TimesFormer token 并回归动作 |
| 在线闭环推理 | infer/server.py, infer/client.py |
维护 session_id、接收 RGB 帧、输出动作段 |
| 监督训练 | train/, train/TRAINING.md |
微调世界骨干并训练 latent-to-action 映射 |
| Action-aware GRPO | action_aware_grpo/, Worldmodel/runtime/tools/GRPO/ |
采样 rollout、计算片段奖励、构造 replay 权重 |
3. 总体架构:预测世界转移,再解码动作
3.1 输入:语言指令和真实观测历史
模型输入包括语言指令和已经收到的第一视角观测历史。文本先经过编码器得到 ,图像或视频片段经过视频 VAE 编码器得到潜在表示 。随后,潜在自回归 Transformer 预测下一段世界状态转移,形式为 。这里预测的是 latent segment,不是完整 RGB 视频,因此目标更接近“为动作服务的世界变化”。
3.2 中间层:预测到的 latent 不是拿来欣赏的图像
动作解码器 接收预测出的潜在世界转移,并输出航点动作序列 。这一设计的关键不是让未来画面看起来多逼真,而是让潜在转移保留足够的空间、运动和语义信息,能够被稳定解码为动作。WorldVLN 的世界模型不是为了生成视频而生成视频,而是为了让动作决策知道“下一步可能发生什么”。
3.3 闭环:执行后必须用真实观测纠偏
动作执行后,WorldVLN 会接收真实观测 ,再通过视频 VAE 编码成 ,并把真实 latent 放回自回归上下文。也就是说,模型不会把自己预测的 当成永久记忆继续滚动,而是用真实世界反馈覆盖预测状态。
预测只服务当前动作,下一轮决策必须重新锚定真实观测,这是它区别于全序列视频生成式世界模型的关键。
4. 从 VLA 到 WAM:范式差异在哪里
4.1 VLA:从当前观测直接到动作
VLA 模型通常学习 ,也就是把视觉语言理解和动作生成放在一个端到端映射里。这个设定简单,训练和部署都比较直观,但缺点是模型未必知道动作如何改变环境。举个更通俗的例子,VLA 像是看一眼路口就立刻决定往哪走;如果路口简单,它可能表现不错,但如果后面要连续转弯、绕行、升降,它就容易因为没有预判而越走越偏。
4.2 WAM:先预测世界怎样变,再决定动作
World Action Model 更接近 与 的组合。前半部分估计世界如何演化,后半部分从演化中恢复动作。这个结构把“看见什么”和“做什么”之间插入了一个可训练的预测空间,使模型有机会学习运动、视角和几何关系。它不是简单多加一个模块,而是把导航从反应式控制改成预测式控制。
4.3 通俗类比:边走边看,而不是闭眼冲完整条路
可以把 WorldVLN 理解成一个会短程预演的飞行员:它先根据当前画面和指令预判下一小段飞行后世界会怎样,然后执行动作,接着马上用真实新画面修正判断。它不会一次性想象完整路线,也不会完全相信自己的想象。这个机制特别适合无人机,因为无人机视角变化太快,一次错误预测如果不及时纠正,后面每一步都会错得更远。
5. 在线闭环推理:服务端如何维护状态
5.1 服务协议:同一个 session 连续推进
WorldVLN 的在线推理使用 session_id 维护轨迹状态。第一次请求通常发送 1 帧 warmup 图像和指令,服务端生成下一段动作;客户端执行这些动作后,收集下一段真实 RGB 帧,再使用同一个 session_id 发回服务端。默认 step 为 16,服务端每次输出一段 16 帧对应的动作。这不是一次性预测完整轨迹,而是严格的分段闭环协议。
5.2 接口代码:输入图像,输出 6D delta
# WorldVLN.code/infer/server.py
class PredictDeltaActionsRequest(BaseModel):
session_id: str = Field(..., description="Trajectory/session identifier")
instruction: Optional[str] = Field(None, description="Prompt/instruction; used on first call or when updating prompt")
prompt: Optional[str] = Field(None, description="Alias of instruction (compat)")
negative_prompt: Optional[str] = Field("", description="Optional negative prompt")
images_base64: List[str] = Field(..., description="RGB images as base64 strings; first call typically 1 frame, later typically 16 frames")
action_head_mode: str = Field(
"tsformer_latent",
description=(
"'tsformer_latent' (default): Stage2 latent2action "
"(decoder-features -> adapter tokens -> TimesFormer sliding windows) -> 16 actions per 16-frame segment."
),
)
class PredictDeltaActionsResponse(BaseModel):
actions: List[List[float]] = Field(
...,
description="Delta actions list; each is [dx_cm,dy_cm,dz_cm,droll_deg,dyaw_deg,dpitch_deg].",
)
segment_index: int
num_received_frames: int
prefix_latents: int
done: bool
这段代码体现了开源实现与论文公式之间的一个重要差异。论文主公式使用 4-DoF 动作,即三维位移加偏航角;而服务端工程接口输出 6D delta,顺序为 [dx_cm,dy_cm,dz_cm,droll_deg,dyaw_deg,dpitch_deg]。复现时如果直接把论文中的 套到工程接口上,会漏掉 roll 和 pitch 两个通道,也可能在单位换算上出错。实际控制链路必须明确坐标系、角度单位和动作执行频率。
5.3 缓存机制:把事实和想象分开
InfinityStreamingSession 是闭环推理的关键封装。它把文本前缀缓存写成 t0,把真实观测缓存写成 gt_obs,并把推理过程中产生的 KV-cache 标记为 Pred。执行校正时只清除 Pred cache,保留文本和真实观测缓存。这个实现细节直接对应论文的“预测 latent 只用于当前动作,后续上下文由真实观测重建”。
# WorldVLN.code/Worldmodel/runtime/tools/infinity_streaming_session.py
"""
Key conventions:
- Use 't0' as the text-prefix cache key and write it as GT (is_pred=False) so it won't be removed by clear_pred_cache().
- Use 'gt_obs' as the observation-frame cache key and write it as GT (is_pred=False).
- KV-cache entries written during inference are treated as Pred (is_pred=True) and are cleared in one shot during correction.
"""
def correction_clear_pred(self):
"""Step 4: clear Pred KV cache in one shot (GT caches are kept)."""
self.infinity.clear_pred_cache()
从控制系统角度看,这相当于把“可靠事实”和“临时想象”分开管理。语言指令和真实观测是可靠事实,需要保留在上下文中;模型预测出的未来 latent 只是为了当前动作决策服务,不能长期污染记忆。对无人机这种视角变化剧烈的主体来说,错误想象如果被持续滚入上下文,就会导致场景语义漂移和几何漂移。WorldVLN 的缓存设计,就是为了让每轮决策重新锚定真实传感器输入。
6. 动作解码器:从潜在世界转移到 6D 动作
6.1 为什么还需要动作解码器
动作解码器是 WorldVLN 中容易被忽略但非常关键的部分。世界模型输出的是 summed_codes 这样的潜在表示,不能直接交给飞控系统。开源实现先把 summed_codes 转成 Stage-2 VAE decode 需要的 z_ext,再通过 VAE decoder 抽取中间特征,并使用 adapter 把特征转换为 TimesFormer patch tokens。最后,TimesFormer 以滑动窗口方式输出每一帧对应的动作增量。
6.2 Adapter:把 VAE 特征翻译成 TimesFormer token
# WorldVLN.code/Worldmodel/action_decoder/src/models/vae96_to_tsformer_adapter.py(节选)
class Vae96ToTSformerEmbedAdapter(nn.Module):
"""
Map InfinityStar decoder `up_block_3` feature (B,96,T,256,256) to TSformer patch tokens.
Output tokens match TSformer `PatchEmbed` output:
- patch_tokens: (B*T, N=12*40=480, D=384)
- returns (patch_tokens, T, W_grid=40)
"""
def forward(self, f96_up3: torch.Tensor) -> Tuple[torch.Tensor, int, int]:
if f96_up3.ndim != 5:
raise ValueError(f"expected f96_up3 shape (B,96,T,H,W), got {tuple(f96_up3.shape)}")
B, C, T, H, W = f96_up3.shape
if int(C) != 96:
raise ValueError(f"expected channel=96, got C={C}")
x = f96_up3.permute(0, 2, 1, 3, 4).reshape(B * T, C, H, W)
x = _resize_to_192x640(x)
h = self.conv_a(x)
h = self.patch(h)
if self.skip is not None:
s = self.skip(x)
s = F.avg_pool2d(s, kernel_size=self.patch_size, stride=self.patch_size)
xg = h + 0.1 * s
else:
xg = h
tokens = xg.flatten(2).transpose(1, 2).contiguous()
tokens = self.out_norm(tokens)
grid_w = int(xg.shape[-1])
return tokens, int(T), grid_w
这段 adapter 代码说明了表示对齐的具体形式:输入是 VAE decoder 的五维特征 (B,96,T,H,W),输出是 TimesFormer 使用的 patch token (B*T,480,384)。从直觉上看,它就像一个“翻译层”:世界模型说的是 latent 语言,动作头听得懂的是时空 token 语言,adapter 负责把两者接起来。没有这层对齐,动作头很难稳定地从视频生成特征中读出运动信息。
6.3 因果滑窗:不能偷看未来
# WorldVLN.code/infer/server.py
def _stage2_predict_16_actions_for_segment_cm_deg(
*,
st: "TrajectoryState",
infer_res: "SegmentInferResult",
stride: int = 1,
) -> List[List[float]]:
"""
Stage-2 latent2action path:
- Build full tokens_tnd for the whole predicted horizon from summed_codes (z_ext).
- For the current segment, take only left-context frames [ctx_start .. clip_end] (NO right-context),
run window=4 sliding inference with overlap averaging, and slice the 16 actions for this segment.
"""
这里的 “NO right-context” 很重要。在线控制不能使用还没有发生的真实未来帧,否则离线评估和真实部署会出现接口不一致。WorldVLN 的默认 tsformer_latent 路径只使用左侧上下文,并以窗口大小 4 的滑窗做动作预测,重叠位置再做平均。动作解码器遵守因果约束,才能真正用于闭环部署。
7. 两阶段训练:先对齐世界先验,再优化动作后果
7.1 第一阶段:先学会“看懂导航视频”
第一阶段是监督训练,目标是把视频生成骨干中的时空先验迁移到导航数据上,并让潜在世界表示变得 action-decodable。对世界模型骨干,训练目标可以写成 ,即给定指令和历史真实 latent,预测下一段真实 latent。对动作解码器,训练目标可以写成 ,即从真实视频片段的 latent 中恢复专家动作。
7.2 本地训练入口
本地 train/TRAINING.md 提供了第一阶段训练的工程约束。默认设置包括 Python 3.10、视频帧数 49、fps 为 16、分辨率预设 0.40M、模型 infinity_qwen8b、学习率 1e-5,并需要准备 T5 文本编码器、InfinityStar video VAE 和 8B 基座权重。GitHub README 还说明动作解码器训练分为 Stage A 和 Stage B,前者进行 adapter distillation,后者进行 latent-to-action training。这说明训练流程不是一个脚本跑到底,而是先对齐表征,再学习动作输出。
# WorldVLN.code README 中的训练入口
bash train/scripts/train_from_base.sh
# Action decoder: Stage A + Stage B
bash train/action_decoder/scripts/train_stageA_ddp.sh
bash train/action_decoder/scripts/train_stageB_ddp.sh
7.3 第二阶段:Action-aware GRPO 让模型看动作后果
第二阶段是 Action-aware GRPO。监督学习能够让模型模仿专家轨迹,但真实闭环导航关注的是动作执行后的后果,因此 WorldVLN 在模拟器中采样一组在线 rollout,并对每个片段计算奖励。论文给出的片段奖励形式为 。其中 trajectory reward 关注动作与专家轨迹的几何一致性,task reward 关注终点或阶段目标进度,reference reward 约束策略不要偏离已学到的世界模型先验。
# WorldVLN.code/Worldmodel/runtime/tools/GRPO/reward_uavflow.py
def _reward_act_with_clip_decay(
pred_poses: List[List[float]],
gt_poses: List[List[float]],
clip_len: int,
num_clips: int,
clip_alpha: float,
alpha_xyz: float,
alpha_yaw: float,
alpha_all6: float,
) -> Dict[str, Any]:
"""
Compute action-level reward by summing per-clip rewards with temporal decay:
r_act = r0 * 1 + r1 * alpha + r2 * alpha^2
"""
代码中的 clip_alpha 对应时间衰减思想,默认值为 0.9。早期动作被赋予更高权重,是因为无人机前几步的偏差会改变后续观测,从而影响后面所有动作。reward_uavflow.py 还同时计算 xyz、yaw 和全 6D 表示上的误差,并在 task reward 中评估 endpoint 距离和 yaw 误差。这比只看最终成功失败更细,因为每个片段都能获得训练信号。
# WorldVLN.code/Worldmodel/runtime/tools/GRPO/build_replay_dataset.py
r = float(args.lambda_act) * r_act + float(args.lambda_task) * r_task + float(args.lambda_ce) * r_ce
mode = str(args.mode or "raw_reward").strip().lower()
has_precomputed_adv = all("grpo_adv_final" in rows[i] for i in idx.tolist())
adv_pre = np.asarray([float(rows[i].get("grpo_adv_final", 0.0)) for i in idx], dtype=np.float64)
if mode == "precomputed_adv" and has_precomputed_adv:
s = r.copy()
w = adv_pre.copy()
m = (w > 0).astype(np.float64)
这段 replay 构造代码体现了 GRPO 阶段的工程取舍:动作奖励、任务奖励和 CE/reference 奖励可以按权重组合,若已有预计算优势值,则直接使用 grpo_adv_final 作为训练权重。它不是简单地把所有成功轨迹都当正样本,也不是只惩罚失败轨迹,而是把片段质量、任务进展和策略稳定性一起考虑。对一个 8B 级自回归世界骨干来说,保留世界模型先验和提升动作结果同样重要。
8. 小结
WorldVLN 的主要贡献不是把一个视频生成模型简单接到无人机控制上,而是把空中视觉语言导航重新组织成“预测世界转移、解码动作、真实观测回填、再预测”的闭环过程。自回归世界骨干提供短时空间演化先验,动作解码器把 latent transition 转成可执行航点,Action-aware GRPO 则通过在线 rollout 后果继续优化动作质量。这三部分组合起来,使模型不只理解当前画面,还能利用对后续状态的预测来做决策。
参考资料
- • arXiv: WorldVLN: Autoregressive World Action Model for Aerial Vision-Language Navigation• Project page: https://embodiedcity.github.io/WorldVLN/• GitHub code: https://github.com/EmbodiedCity/WorldVLN.code• Hugging Face weights: https://huggingface.co/EmbodiedCity/WorldVLN
176