logo Mr.Robota 具身智能学习手册 EN
第十五章:Challenge竞赛

AgiBot World Model 比赛复现与改进教程

本教程面向 AgiBot World Model 赛道的复现与方法改进说明。教程主要包含安装配置、基础方法、改进方法、推理过程四个部分。基础方法部分讲解 EVAC 动作条件视频扩散模型的原理,改进方法部分对应本项目中已经实现的 LoRA 微调、时序差分损失、Sobel 边缘损失和首帧锚定。期待能够给大家一个良好的学习体验,世界模型正是火热的方向,我们也在逐步探索,与大家一同学习~

1. 安装与配置

1.1 环境创建

创建 Python 3.10 环境:

bashconda create -n enerverse python=3.10.4
conda activate enerverse

进入项目目录并安装依赖:

bashcd /path/to/project
pip install -r requirements.txt

项目中实际会用到 jsonlines 和 fairscale,若运行时报缺包,直接安装:

bashpip install jsonlines
pip install fairscale

改进版使用 LoRA,代码中会调用 peft.LoraConfig 和 get_peft_model,因此还需要安装:

bashpip install peft

安装 PyTorch3D:

bashpip install --no-index --no-cache-dir pytorch3d \
  -f https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt240/download.html

如果显卡、CUDA 或 PyTorch 版本与默认 requirements 不匹配,可以优先安装与本机 CUDA 对应的 PyTorch 版本,再安装其它依赖。实际复现中,高版本显卡常见的问题不是代码逻辑错误,而是 torch、torchvision、xformers、CUDA wheel 之间版本不一致。

1.2 源代码问题处理

1)xformers 兼容处理

requirements.txt 中固定了:

textxformers==0.0.27.post2

如果 xformers 与当前 PyTorch/CUDA 不兼容,推理或训练可能在 attention 处报错。本项目中已经采用了一个保守处理:在 evac/lvdm/models/vae_models.py 中将 xformers 关闭,使用普通 attention 路径继续运行。

关键修改代码如下。这段代码保留了原本的 try import xformers 思路,但实际运行时强制关闭 xformers 快速路径:

pythonlogpy = logging.getLogger(__name__)

# try:
#     import xformers
#     import xformers.ops
#
#     XFORMERS_IS_AVAILABLE = True
# except:
#     XFORMERS_IS_AVAILABLE = False
#     logpy.warning("no module 'xformers'. Processing without...")

XFORMERS_IS_AVAILABLE = False
logpy.warning("no module 'xformers'. Processing without...")

这样做的代价是推理速度和显存效率会下降,但优点是更容易在不同机器上跑通。对于比赛复现教程,先保证结果可生成,比追求 attention 加速更重要。

2)注意力机制报错

此外,原来的代码可能会出现“注意力机制初始为 none”的报错,修复在 evac/lvdm/modules/attention.py 的 attention 前向传播逻辑中。原始问题是:当 context=None 时,代码如果仍然直接执行 self.to_k(context) 或 self.to_v(context),就会把空上下文传入线性层,导致 attention 计算报错。

本项目的修复方式不是简单修改配置,而是在 forward() 中显式区分自注意力和交叉注意力:当 context is None 时,q/k/v 都来自输入 x;当 context 存在时,q 来自 x,k/v 来自 context。对应完整修改块如下:

pythondef forward(self, x, context=None, mask=None, seq_length=None):
    spatial_self_attn = (context is None)
    k_ip, v_ip, out_ip = None, None, None

    # 核心修复:根据 context 是否为空区分自注意力和交叉注意力
    if context is None:
        # 自注意力:q/k/v 都来自输入 x
        q = self.to_q(x)
        k = self.to_k(x)
        v = self.to_v(x)
    else:
        # 交叉注意力:q 来自输入 x,k/v 来自上下文 context
        q = self.to_q(x)
        k = self.to_k(context)
        v = self.to_v(context)

    k_vpre, v_vpre, out_vpre = None, None, None

    h = self.heads
    q = self.to_q(x)
    context = default(context, x)

这段修改的核心意义是让 attention 在没有外部上下文时自动退化为 self-attention,而不是继续把 None 当作 cross-attention 的上下文输入。这样可以避免 context=None 触发的空对象报错,同时保留有上下文时的图像条件、轨迹条件等 cross-attention 分支。

3)FairScale 与训练策略报错处理

旧版 FairScale 可能在训练器策略处报错。这个问题通常发生在 PyTorch Lightning 默认读取 sharded strategy 或 deepspeed strategy 时:本地 fairscale、torch、pytorch-lightning 版本不完全匹配,训练还没进入模型前向就会失败。

本项目采用的处理方式是:不再实例化默认的 sharded strategy,而是在 trainer/trainer.py 中直接将训练策略改为 "ddp"。对应完整修改块如下:

python# strategy_cfg = get_trainer_strategy(lightning_config)
# trainer_kwargs["strategy"] = strategy_cfg if type(strategy_cfg) == str else instantiate_from_config(strategy_cfg)
trainer_kwargs["strategy"] = "ddp"

trainer_kwargs["precision"] = lightning_config.get("precision", 32)
trainer_kwargs["sync_batchnorm"] = False

这里有两个关键点。第一,LoRA adapter 必须在基础权重加载之后再应用,否则 adapter 可能绑定到尚未恢复权重的模型结构上。第二,trainer_kwargs["strategy"] = "ddp" 是为了绕开旧版 FairScale 或 sharded strategy 的兼容问题,优先保证训练流程能够稳定启动。

4)guidance rescale 参数名不一致

源码中写成下面这样:

pythonguidanc_erescale=args.gr

需要改成 guidance_rescale=args.gr。完整调用片段建议写成:

pythonwith torch.cuda.amp.autocast(dtype=torch.bfloat16):
    model.inference(
        config, img, action, delta_action,
        c2w, w2c, intrinsic,
        save_path, n_chunk_to_pred,
        chunk=chunk,
        n_previous=n_previous,
        n_valid=n - n_previous,
        unconditional_guidance_scale=args.cfg,
        guidance_rescale=args.gr,
        ddim_steps=args.ddim_steps,
        saving_tag="",
        saving_video=True,
        video_dir=os.path.join(args.save_root + "_video", task, clip, str(args.gid), "video")
    )
    torch.cuda.empty_cache()

否则模型推理函数可能接收不到正确的 guidance rescale 参数,轻则该参数不生效,重则直接出现 unexpected keyword 或参数缺失类报错。

1.3 权重与数据路径配置

训练配置文件为:

textconfigs/agibotworld/train_config_challenge_wm.yaml

需要重点检查三类路径。

第一,EVAC 预训练权重:

yamlmodel:
  pretrained_checkpoint: /path/to/EnerV_AC_deepspeed_v0.1.pt

第二,CLIP 图像编码器权重:

yamlmodel:
  params:
    img_cond_stage_config:
      params:
        abspath: /path/to/open_clip_pytorch_model.bin

第三,训练数据根目录:

yamldata:
  params:
    train:
      params:
        data_roots: ["/path/to/agibot/world_model/train"]

数据目录中的每个 episode 至少需要包含:

textepisode_x/
  frame.png
  head_intrinsic_params.json
  head_extrinsic_params_aligned.json
  proprio_stats.h5

这些文件分别提供初始视觉观测、相机内参、相机外参和机器人本体状态。World Model 的目标不是无条件生成视频,而是在给定初始图像和机器人动作的条件下预测未来视频。

1.4 改进版关键配置

本项目的核心改进都在同一个配置文件中打开:

yamlmodel:
  params:
    use_lora: True
    lora_config:
      r: 16
      alpha: 16
      dropout: 0.05
      target_modules: ['to_q', 'to_k', 'to_v', 'to_out.0']
    lora_checkpoint: null

    temporal_loss_weight: 0.05
    edge_loss_weight: 0.02
    anchor_first_frame: True

数据采样部分也要打开首帧锚定:

yamldata:
  params:
    train:
      params:
        anchor_first_frame: True

训练稳定性相关配置如下:

yamldata:
  params:
    batch_size: 1

lightning:
  precision: 32
  trainer:
    gradient_clip_val: 0.5

这里使用 batch_size: 1 是为了降低显存压力;使用 precision: 32 是为了让新增的时序差分损失和 Sobel 边缘损失更稳定。若显存充足,可以再尝试增大 batch size;若显存不足,优先保持 batch size 为 1。

1.5 训练命令

启动训练:

bashbash scripts/train.sh configs/agibotworld/train_config_challenge_wm.yaml

如果只使用单张 GPU:

bashCUDA_VISIBLE_DEVICES=0 bash scripts/train.sh configs/agibotworld/train_config_challenge_wm.yaml

脚本内部会调用 torchrun,并根据可见 GPU 数量设置进程数:

bashNGPU=`nvidia-smi --list-gpus | wc -l`
torchrun --nnodes=1 \
  --nproc_per_node=$NGPU \
  trainer/trainer.py \
  --base $config_file \
  --train

训练过程中建议先观察前 100 到 300 step:

  • 是否正常加载 EVAC 预训练权重。
  • 是否打印 LoRA 可训练参数量。
  • 日志中是否出现 train/loss_temporal。
  • 日志中是否出现 train/loss_edge。
  • 总 loss 是否出现 NaN。

1.6 推理命令

改进版推理脚本为:

textscripts/infer_lora.sh

脚本需要配置输入数据、输出目录、基础权重、LoRA checkpoint 和模型配置:

bashinput_root=/path/to/test/info_dataset
save_root=/path/to/save/results
ckp_path=/path/to/EnerV_AC_deepspeed_v0.1.pt
lora_ckp_path=/path/to/epoch=1-step=30000.ckpt
config_path=/path/to/configs/agibotworld/train_config_challenge_wm.yaml
n_pred=3

运行:

bashbash scripts/infer_lora.sh

脚本核心命令如下:

bashpython evac/main/infer_all.py \
  -i $input_root \
  -s $save_root \
  --ckp_path $ckp_path \
  --config_path $config_path \
  --lora_ckp_path $lora_ckp_path \
  --n_pred $n_pred

n_pred=3 表示每个输入 episode 生成 3 个候选结果。代码会循环设置不同随机种子:

pythonfor gid in range(args.n_pred):
    args.seed = gid
    args.gid = gid
    model = main(args, model=model)

2. 官方基线方法讲解:动作条件视频扩散 World Model

2.1 任务建模

World Model 赛道可以抽象为条件视频预测问题。给定初始观测帧、机器人动作序列、相机内外参和本体状态,模型需要生成未来一段操作视频。

形式化地,可以写成:

text输入:I_0, A_{1:T}, K, E, S
输出:I_{1:T}

其中:

  • I_0 是初始图像。
  • A_{1:T} 是未来动作序列。
  • K 是相机内参。
  • E 是相机外参。
  • S 是机器人本体状态。
  • I_{1:T} 是模型要生成的未来视频帧。

这个任务比普通视频生成更难,因为生成结果必须同时满足三类约束:

  1. 视觉约束:场景、物体、桌面、容器等外观要保持稳定。
  2. 动作约束:物体运动要符合机器人夹爪和末端执行器的动作。
  3. 时序约束:相邻帧之间不能跳变,长序列不能逐渐漂移。

2.2 数据输入如何进入模型

项目中每个 episode 的输入文件承担不同角色。

文件 模型使用方式 作用
frame.png 作为视觉条件输入 提供当前场景外观
head_intrinsic_params.json 构造相机几何条件 描述相机成像参数
head_extrinsic_params_aligned.json 构造相机位姿条件 对齐世界坐标、相机坐标和机器人动作
proprio_stats.h5 提取动作和状态 提供夹爪、末端位置、姿态等信息

evac/main/infer_all.py 会读取 proprio_stats.h5,再通过 get_actions() 得到动作条件。动作条件通常包括两类:

  • 绝对动作或状态:描述当前时刻机器人末端和夹爪状态。
  • 相对动作变化量:描述相邻时刻之间机器人运动的变化。

为什么要同时使用动作和相机几何?因为机器人操作视频中,同一个动作在不同相机视角下的像素运动不同。相机参数帮助模型把“机器人在三维空间中怎么动”转换成“画面中应该怎么变化”。

2.3 Latent Diffusion 的基本思想

模型不直接在像素空间生成视频,而是先把视频帧编码到 latent 空间,再在 latent 空间做扩散去噪。

流程如下:

text真实视频帧
  -> VAE Encoder
  -> latent 表示
  -> 加噪声
  -> 3D UNet 预测去噪方向
  -> 逐步采样得到未来 latent
  -> VAE Decoder
  -> 生成视频帧

这样做有两个好处:

  1. 计算量更小。latent 空间分辨率低于像素空间。
  2. 更适合视频生成。3D UNet 可以同时建模空间纹理和时间变化。

2.4 条件注入机制

该模型不是无条件扩散模型。它的去噪网络会接收多个条件源:

条件 进入方式 作用
初始图像 CLIP 图像编码 + 投影模块 提供语义和外观先验
动作序列 action / delta action 编码 控制未来运动
轨迹图 trajectory / ray map 条件 提供空间几何引导
相机参数 内外参构造几何输入 对齐视角与运动
历史帧 memory frames 维持上下文连续性

在配置中可以看到这些设计:

yamlfirst_stage_key: ["video", "traj"]
cond_stage_key: delta_action
conditioning_key: hybrid
use_raymap_dir: True
use_raymap_origin: True
use_cat_mask: True
image_cross_attention: true
traj_cross_attention: true

hybrid 表示模型会混合使用多种条件,而不只依赖单一图像或单一动作。image_cross_attention 和 traj_cross_attention 表示图像条件和轨迹条件通过 cross-attention 进入 UNet。

2.5 训练目标

扩散训练的核心是:随机选择时间步 t,给真实 latent 加噪声,然后让模型预测去噪目标。

在本项目配置中:

yamlparameterization: "v"

也就是使用 v-prediction。训练目标可以概括为:

pythonif parameterization == "v":
    target = get_v(x_start, noise, t)

loss_simple = mse(model_output, target)
loss = weighted(loss_simple) + original_elbo_weight * loss_vlb

这里的 x_start 是真实视频 latent,noise 是随机噪声,model_output 是 UNet 输出。模型学习的不是直接生成图像,而是在任意噪声强度下预测正确的去噪方向。

只对未来预测 chunk 计算主要损失,是这个任务的重要细节。历史帧作为条件输入,未来帧才是模型要学习生成的目标。

2.6 自回归分块生成

比赛中的视频可能较长,单次生成全部未来帧会带来显存和时序建模压力。因此模型采用分块预测:

text第 1 个 chunk:使用初始 memory 和动作条件生成未来若干帧
第 2 个 chunk:把已生成视频的一部分作为新 memory,再生成下一段
第 3 个 chunk:继续滚动生成

这种方法的优点是可以生成更长的视频;缺点是误差会累积。前一段生成结果如果已经模糊,后一段会把模糊结果继续当作条件,导致小物体消失、边缘变软、动作和视觉逐渐不同步。

后面的改进方法正是针对这个问题设计的。

3. 比赛中使用的改进方法讲解

改进版方法的目标是解决长序列自回归生成中的三类退化:

  1. 物体和夹爪边缘逐渐模糊。
  2. 相邻帧运动不连续。
  3. 多 chunk 推理后模型遗忘初始外观。

对应的实现包括:

  • LoRA 微调。
  • Temporal Difference Loss。
  • Sobel Edge Loss。
  • 首帧锚定。

3.1 LoRA 微调

3.1.1 为什么使用 LoRA

直接全量微调视频扩散模型成本很高。UNet 参数量大,显存占用高,且容易破坏预训练模型已有的通用生成能力。LoRA 的思路是在 attention 投影层旁边插入低秩矩阵,只训练少量新增参数。

对于本任务,LoRA 的作用是让模型适配比赛数据中的机器人操作场景,同时保留原模型的视频生成先验。

3.1.2 配置方式

配置文件中开启:

yamluse_lora: True
lora_config:
  r: 16
  alpha: 16
  dropout: 0.05
  target_modules: ['to_q', 'to_k', 'to_v', 'to_out.0']
lora_checkpoint: null

含义如下:

参数 含义
r: 16 LoRA 低秩矩阵的秩
alpha: 16 LoRA 缩放系数
dropout: 0.05 LoRA 分支 dropout
target_modules 注入 LoRA 的 attention 投影层
lora_checkpoint 推理时加载的 LoRA 权重

目标模块选择 to_q、to_k、to_v、to_out.0,是因为这些层直接决定 attention 如何从条件和上下文中取信息。对这些层做轻量适配,能改变模型对图像条件、动作条件和时序上下文的使用方式。

3.1.3 代码实现

在 evac/lvdm/models/ddpm3d.py 中,模型初始化保存 LoRA 配置:

pythonself.use_lora = use_lora
self._lora_config = lora_config or {}
self._lora_checkpoint = lora_checkpoint

加载基础权重后调用 apply_lora()。该函数完成三步:

pythonfor param in self.model.parameters():
    param.requires_grad = False

peft_config = LoraConfig(
    r=lora_r,
    lora_alpha=lora_alpha,
    lora_dropout=lora_dropout,
    target_modules=target_modules,
    bias="none",
)

self.model.diffusion_model = get_peft_model(
    self.model.diffusion_model, peft_config
)

第一步冻结原始 UNet,第二步构造 LoRA 配置,第三步把 LoRA 注入到 diffusion model。

优化器只收集可训练参数:

pythonif self.use_lora:
    params = [p for p in self.model.parameters() if p.requires_grad]

因此训练时主要更新 LoRA adapter,而不是全量更新视频扩散模型。

3.1.4 推理时加载 LoRA

evac/main/infer_all.py 会把命令行中的 LoRA checkpoint 注入配置:

pythonif hasattr(args, 'lora_ckp_path') and args.lora_ckp_path:
    config.model.params.lora_checkpoint = args.lora_ckp_path

然后在模型加载后应用 LoRA:

pythonmodel = load_checkpoints(model, config.model, ignore_mismatched_sizes=False)
if hasattr(model, 'apply_lora'):
    model.apply_lora()

这表示推理阶段由两部分权重组成:

text基础 EVAC 权重 + LoRA 微调权重

基础权重提供通用生成能力,LoRA 权重提供比赛数据适配。

3.2 Temporal Difference Loss

3.2.1 问题动机

原始扩散损失主要约束每一帧的预测 latent 是否接近真实 latent,但机器人操作视频还要求“变化过程”正确。例如夹爪抓住物体后,物体应该跟随夹爪连续移动,而不是在相邻帧之间突然跳变。

Temporal Difference Loss 直接约束相邻帧之间的差分,使模型学习真实视频中的运动趋势。

3.2.2 数学形式

设预测未来 latent 为 x_pred,真实未来 latent 为 x_gt。相邻帧差分为:

textΔx_pred(t) = x_pred(t+1) - x_pred(t)
Δx_gt(t)   = x_gt(t+1) - x_gt(t)

时序差分损失为:

textL_temporal = MSE(Δx_pred, Δx_gt)

总损失中加入:

textL = L_diffusion + λ_t L_temporal

其中 λ_t 对应配置:

yamltemporal_loss_weight: 0.05

3.2.3 代码实现

在 p_losses() 中先从 v-prediction 恢复 x0_pred:

pythonx0_pred = self.predict_start_from_z_and_v(x_noisy, t, model_output)
x0_pred_chunk = x0_pred[:, :, -self.chunk:]
x0_gt_chunk = x_start[:, :, -self.chunk:]

然后计算相邻帧差分:

pythonpred_diff = x0_pred_chunk[:, :, 1:] - x0_pred_chunk[:, :, :-1]
gt_diff = x0_gt_chunk[:, :, 1:] - x0_gt_chunk[:, :, :-1]
loss_temporal = F.mse_loss(pred_diff, gt_diff)
loss += self.temporal_loss_weight * loss_temporal

该损失只作用在预测 chunk 上,不改变历史 memory 的条件角色。

3.3 Sobel Edge Loss

3.3.1 问题动机

机器人操作任务中的关键对象往往很小,例如夹爪、方块、瓶口、容器边缘等。普通 MSE 容易让这些高频细节在长序列生成中被平均掉,表现为边界发虚、物体变糊、后半段目标消失。

Sobel Edge Loss 的目标是强化边缘结构,使预测 latent 在空间梯度上接近真实 latent。

3.3.2 方法形式

Sobel 算子分别计算横向和纵向梯度:

textGx = Sobel_x(x)
Gy = Sobel_y(x)
Edge(x) = sqrt(Gx^2 + Gy^2)

边缘损失为:

textL_edge = MSE(Edge(x_pred), Edge(x_gt))

配置权重为:

yamledge_loss_weight: 0.02

3.3.3 代码实现

初始化 Sobel kernel:

pythonsobel_x = torch.tensor([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
sobel_y = torch.tensor([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
self.register_buffer('sobel_x', sobel_x)
self.register_buffer('sobel_y', sobel_y)

对 latent 做 depthwise convolution:

pythonedge_x = F.conv2d(x_2d, self.sobel_x, padding=1, groups=c)
edge_y = F.conv2d(x_2d, self.sobel_y, padding=1, groups=c)
edges = torch.sqrt(edge_x ** 2 + edge_y ** 2 + 1e-6)

加入总损失:

pythonsobel_pred = self._apply_sobel(x0_pred_chunk)
sobel_gt = self._apply_sobel(x0_gt_chunk)
loss_edge = F.mse_loss(sobel_pred, sobel_gt)
loss += self.edge_loss_weight * loss_edge

该方法在 latent 空间中计算边缘。虽然 latent 不是像素,但 VAE 编码器保留了空间结构,latent 的局部梯度仍能近似反映物体边界和纹理变化。

3.4 首帧锚定

3.4.1 问题动机

自回归视频生成存在误差累积。模型生成第一段视频后,会把生成结果作为下一段预测的条件。如果第一段已经出现模糊或轻微漂移,第二段会继续放大这些误差。

首帧锚定的核心思想是:无论生成到第几个 chunk,都保留原始第一帧作为固定记忆,让模型持续看到最初的物体外观和场景布局。

3.4.2 训练阶段实现

在 dataset/agibotworld_challenge_dataset.py 中增加参数:

pythonanchor_first_frame=False

保存到对象:

pythonself.anchor_first_frame = anchor_first_frame

采样 memory frame 时,如果开启锚定,就强制第一个 memory index 为 0:

pythonif self.anchor_first_frame and len(mem_indexes) > 0:
    mem_indexes[0] = 0

这样训练时模型就会习惯:memory 的第一个位置保存原始第一帧。

3.4.3 推理阶段实现

在 evac/lvdm/models/ddpm3d.py 的 inference() 中,当 i_chunk > 0 时,历史帧索引不再完全均匀采样,而是保留第 0 帧:

pythonif self.anchor_first_frame:
    remaining = n_previous - 2
    if remaining > 0:
        idx_history = [0] + [
            1 + (n_history - 1) * i // remaining
            for i in range(remaining)
        ]
    else:
        idx_history = [0]

直观解释:

textmemory slot 0:永远放原始第一帧
其它 memory slot:从已生成历史中均匀选择
最后一帧:重复最新生成帧,保证 chunk 衔接

这一设计主要缓解长序列后半段的外观遗忘问题。

3.5 三个主要改进总结

问题 改进 作用
相邻帧跳变 Temporal Difference Loss 约束运动连续性
物体边缘模糊 Sobel Edge Loss 强化高频结构
长序列遗忘初始外观 首帧锚定 保留原始视觉参照

训练时的总目标可以概括为:

textL_total = L_diffusion
        + λ_t L_temporal
        + λ_e L_edge

其中 Temporal/Sobel 决定“优化什么目标”,首帧锚定决定“给模型什么上下文”。

4. 推理过程

本部分只说明如何运行 infer 并得到可提交或可查看的生成结果。完成训练并得到 LoRA checkpoint 后,推理流程可以分为四步:准备输入数据、配置推理脚本、执行推理命令、检查输出文件。

4.1 准备推理输入

推理输入目录应按任务和 episode 组织。每个 episode 至少需要包含初始图像、相机参数和机器人状态文件:

textinfo_dataset/
  task_x/
    episode_y/
      frame.png
      head_intrinsic_params.json
      head_extrinsic_params_aligned.json
      proprio_stats.h5

其中 frame.png 提供初始视觉观测,两个 JSON 文件提供相机内外参,proprio_stats.h5 提供机器人动作和本体状态。推理入口会读取这些文件,并把动作序列转换为模型需要的 action 与 delta action。

4.2 配置推理脚本

推理脚本需要填写五类路径:输入目录、输出目录、基础模型权重、LoRA 权重和配置文件。推荐在 scripts/infer_lora.sh 中写成下面的形式:

bashinput_root=/path/to/test/info_dataset
save_root=/path/to/save/results
ckp_path=/path/to/EnerV_AC_deepspeed_v0.1.pt
lora_ckp_path=/path/to/epoch=1-step=30000.ckpt
config_path=/path/to/configs/agibotworld/train_config_challenge_wm.yaml
n_pred=3

这里 ckp_path 是基础 World Model 权重,lora_ckp_path 是改进训练后得到的 LoRA checkpoint。n_pred=3 表示每个 episode 生成 3 个候选结果,对应随机种子 0、1、2。

脚本核心命令如下:

bashpython evac/main/infer_all.py \
  -i $input_root \
  -s $save_root \
  --ckp_path $ckp_path \
  --config_path $config_path \
  --lora_ckp_path $lora_ckp_path \
  --n_pred $n_pred

如果只想先快速验证流程,可以先把 n_pred 设为 1,确认能正常生成图片和视频后,再改回 3。

4.3 执行推理

进入项目根目录后运行:

bashbash scripts/infer_lora.sh

推理程序会先加载配置和基础权重,再把命令行中的 LoRA checkpoint 写入模型配置:

pythonconfig.model.pretrained_checkpoint = args.ckp_path
if hasattr(args, 'lora_ckp_path') and args.lora_ckp_path:
    config.model.params.lora_checkpoint = args.lora_ckp_path

随后模型加载 LoRA adapter:

pythonmodel = instantiate_from_config(config.model)
model = load_checkpoints(model, config.model, ignore_mismatched_sizes=False)
if hasattr(model, 'apply_lora'):
    model.apply_lora()

最后,程序会按照 n_pred 循环生成多个候选结果:

pythonmodel = None
for gid in range(args.n_pred):
    args.seed = gid
    args.gid = gid
    model = main(args, model=model)

4.4 检查推理输出

推理结束后,主要检查两个输出目录:

textresults/
results_video/

results/ 中保存逐帧图片,常见结构如下:

textresults/
  task_x/
    episode_y/
      0/video/frame_00000.jpg
      0/video/frame_00001.jpg
      ...
      1/video/frame_00000.jpg
      ...
      2/video/frame_00000.jpg
      ...

results_video/ 中保存合成后的视频,常见结构如下:

textresults_video/
  task_x/
    episode_y/
      0/video/outputs.mp4
      1/video/outputs.mp4
      2/video/outputs.mp4

检查时先确认每个 episode 是否都生成了对应的候选目录,再打开 outputs.mp4 查看视频是否能正常播放。如果只生成了图片但没有视频,优先检查视频保存路径和视频编码依赖;如果图片和视频都没有生成,优先检查输入目录结构、权重路径和 proprio_stats.h5 是否能被读取。

4.5 与官方基线对比

运行推理结果后,左侧为官方基线生成结果,右侧为本文改进方法生成结果,所有结果均为同一输入且都是最后一帧效果,以保证对比实验的准确性和有效性。

对比组 官方基线结果 改进方法结果
1组 官方基线结果 改进方法结果
2组 官方基线结果 改进方法结果
3组 官方基线结果 改进方法结果
4组 官方基线结果 改进方法结果

可以看出,改进后的方法对于生成的物体更加的清晰,结构更加的完整。

但是目前仍然存在一些问题,从改进的模型生成结果可以看出,虽然改进的方法相比于官方的基线模型效果有所改进,但是整体视觉效果仍然不够好。同时除了视觉效果以外,还有一些其他的问题。

改进方法结果

这一张图中可以看出,对于被遮挡后的物体继续生成问题,被遮挡后,物体很容易发生消失和畸变的问题,这个可能需要从模型的记忆能力或者模型结构本身进行改进。

改进方法结果

这一张图中显示出,生成模型对于盖上水瓶盖这个操作,对于铰链的物理结构并没有很好的理解,所以机械臂出现了穿模的情况,这个后续我们会对世界模型的物理信息理解进行进一步的改进。

5. 总结

本教程围绕改进版代码介绍了一个动作条件视频扩散 World Model 的复现与改进流程。基础方法使用 latent diffusion 预测未来机器人操作视频;改进方法在不全量重训大模型的前提下,加入 LoRA 微调、时序差分损失、Sobel 边缘损失和首帧锚定。

从方法分工看:

  • LoRA 负责低成本适配比赛数据。
  • Temporal Difference Loss 负责约束运动连续性。
  • Sobel Edge Loss 负责强化物体边缘和细节。
  • 首帧锚定负责缓解长序列外观遗忘。

最终复现路线可以概括为:

text安装依赖
  -> 配置 EVAC 权重、CLIP 权重和数据路径
  -> 开启 LoRA、Temporal Loss、Sobel Loss、首帧锚定
  -> 训练 LoRA checkpoint
  -> 使用 infer_lora.sh 推理
  -> 检查 results 和 results_video 输出
  -> 确认逐帧图片和 outputs.mp4 正常生成

希望这个教程能让大家对世界模型有更好的了解,同时,我们也会在机器人世界模型这个领域进行深耕,希望大家能够多多中论文,我们也会继续努力,多多发顶会,期待能够在顶会现场与大家见面。