跳转至

🧠 VLA大模型深度解析

⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。

VLA大模型深度解析结构图

学习时间:5小时 | 难度:⭐⭐⭐⭐ 中高级 | 前置知识:Transformer、多模态大模型基础


本章目标

  • 深入理解VLA(Vision-Language-Action)模型的设计哲学
  • 掌握RT-2、Octo、OpenVLA、π0的架构细节
  • 理解动作标记化(Action Tokenization)与扩散策略(Diffusion Policy)
  • 学会VLA模型的数据收集与微调方法
  • 了解最新进展:GR-2、RDT-1B、Embodied-GPT

1. VLA模型演进

1.1 从专用到通用

Text Only
第一代: 任务专用模型 (2015-2020)
  - 每个任务单独训练CNN+MLP
  - 不能泛化到新任务/新物体

第二代: 语言条件模型 (2020-2022)
  - 语言指令作为条件输入
  - CLIPort, SayCan, BC-Z
  - 仍需大量机器人数据

第三代: 基础VLA模型 (2023-2024)
  - 利用预训练VLM的视觉-语言理解能力
  - RT-2, Octo, OpenVLA
  - 可在新物体/新场景零样本泛化

第四代: 扩散VLA + 世界模型 (2024-2026)
  - π0, GR-2, RDT-1B
  - 用扩散模型生成连续动作
  - 开始具备物理世界预测能力

1.2 VLA vs VLM

Text Only
VLM (视觉-语言模型):
  输入: 图像 + 文本 → 输出: 文本
  代表: GPT-4V, LLaVA, Qwen-VL

VLA (视觉-语言-动作模型):
  输入: 图像 + 语言指令 → 输出: 机器人动作
  代表: RT-2, OpenVLA, π0

  关键挑战: 如何将VLM的语义理解能力"接地"(ground)到物理动作?

2. 核心模型详解

2.1 RT-2 (Robotic Transformer 2)

Text Only
架构: PaLI-X (55B) 或 PaLM-E (12B) 作为backbone

  输入: [图像tokens] + [语言指令tokens]
  输出: [动作tokens] (将连续动作离散化为token)

动作表示:
  7DoF动作 = [Δx, Δy, Δz, Δroll, Δpitch, Δyaw, gripper]
  每个维度量化为256个bin → 7个token
  例: 向前移动5cm = token_137 (x方向第137个bin)

关键创新:
  1. 动作作为"另一种语言"被VLM学习
  2. 网络预训练知识帮助理解新概念
     → "把泰勒·斯威夫特附近的物体移到垃圾桶"
     → 模型知道Taylor Swift是谁(来自网络知识),能找到她的照片
  3. Chain-of-Thought推理: 先输出推理过程,再输出动作
Python
# RT-2 伪代码结构
class RT2(nn.Module):  # 继承nn.Module定义网络层
    def __init__(self, vlm_backbone, action_bins=256, action_dim=7):
        super().__init__()  # super()调用父类方法
        self.vlm = vlm_backbone  # PaLI-X / PaLM-E
        self.action_bins = action_bins
        self.action_dim = action_dim

        # 将连续动作空间均匀量化
        # 每个维度 [-1, 1] → 256个bin
        self.bin_edges = torch.linspace(-1, 1, action_bins + 1)

    def tokenize_action(self, continuous_action):
        """连续动作 → 离散token"""
        tokens = []
        for d in range(self.action_dim):
            # 找到最近的bin
            val = continuous_action[..., d]
            bin_idx = torch.bucketize(val, self.bin_edges[1:])
            bin_idx = bin_idx.clamp(0, self.action_bins - 1)
            tokens.append(bin_idx)
        return torch.stack(tokens, dim=-1)  # (B, 7)  # torch.stack沿新维度拼接张量

    def detokenize_action(self, tokens):
        """离散token → 连续动作"""
        bin_centers = (self.bin_edges[:-1] + self.bin_edges[1:]) / 2
        actions = []
        for d in range(self.action_dim):
            actions.append(bin_centers[tokens[..., d]])
        return torch.stack(actions, dim=-1)

    def forward(self, images, text_instruction):
        """
        images: (B, T, C, H, W) 历史图像序列
        text_instruction: 语言指令
        """
        # 拼接图像token + 文本token → VLM
        output_tokens = self.vlm(images, text_instruction)

        # 最后7个token即为动作
        action_tokens = output_tokens[:, -self.action_dim:]
        action = self.detokenize_action(action_tokens)

        return action  # (B, 7)

2.2 OpenVLA(开源VLA主力)

Text Only
架构: Llama-2 7B + DINOv2/SigLIP 双视觉编码器
  - Llama-2: 语言backbone (7B参数)
  - DINOv2: 自监督视觉特征 (擅长空间理解)
  - SigLIP: 视觉-语言对齐特征 (擅长语义理解)

训练数据: Open X-Embodiment (970K机器人轨迹, 22种机器人)

特点:
  1. 完全开源 (权重 + 代码 + 数据)
  2. 双视觉编码器互补:空间精度 + 语义理解
  3. 7B参数在单GPU上可实时推理
Python
# OpenVLA 推理示例
from transformers import AutoModelForVision2Seq, AutoProcessor
from PIL import Image
import torch

class OpenVLAInference:
    """OpenVLA模型推理封装"""

    def __init__(self, model_path="openvla/openvla-7b"):
        self.processor = AutoProcessor.from_pretrained(
            model_path, trust_remote_code=True
        )
        self.model = AutoModelForVision2Seq.from_pretrained(
            model_path,
            torch_dtype=torch.bfloat16,
            trust_remote_code=True
        ).cuda()

    def predict_action(self, image: Image.Image, instruction: str):
        """
        输入一帧图像和语言指令,输出7DoF动作
        """
        # 构造prompt
        prompt = f"In: What action should the robot take to {instruction}?\nOut:"

        # 编码
        inputs = self.processor(prompt, image).to("cuda", dtype=torch.bfloat16)  # 移至GPU/CPU

        # 生成动作token
        action = self.model.predict_action(**inputs, unnorm_key="bridge_orig")

        # action: [Δx, Δy, Δz, Δroll, Δpitch, Δyaw, gripper_open]
        return action

    def run_episode(self, env, instruction, max_steps=200):
        """运行一个完整episode"""
        obs = env.reset()

        for step in range(max_steps):
            image = Image.fromarray(obs['image'])
            action = self.predict_action(image, instruction)

            obs, reward, done, info = env.step(action)

            if done:
                return info.get('success', False)

        return False

2.3 π0(扩散VLA,Physical Intelligence)

Text Only
核心创新: 用扩散模型替代自回归token预测

  为什么扩散更好?
  - 自回归(RT-2/OpenVLA): 动作量化为离散bin(256级) → 精度损失
  - 扩散(π0): 直接生成连续动作 → 无量化误差
  - 扩散天然适合多模态分布(同一指令可能有多种合理动作)

  架构:
  - 视觉: SigLIP
  - 语言: Gemma (谷歌开源LLM)
  - 动作: 流匹配(Flow Matching) → 比DDPM更高效

  训练数据: 10000+小时真实机器人数据(发布时最大规模的机器人演示数据集之一)

  结果: 零样本执行折叠衣服、收拾桌子等灵巧操作
Python
import torch
import torch.nn as nn

class FlowMatchingActionHead(nn.Module):
    """
    π0的核心:流匹配动作生成头

    基本思想:
    - 训练时: 学习从噪声分布到动作分布的向量场
    - 推理时: 从噪声出发,沿向量场积分得到动作

    对比DDPM: 流匹配只需要~10步去噪(DDPM需要50-100步)
    """

    def __init__(self, context_dim, action_dim, action_horizon=16):
        super().__init__()
        self.action_dim = action_dim        # 每步动作维度 (7)
        self.action_horizon = action_horizon # 预测未来16步动作
        flat_dim = action_dim * action_horizon

        # 去噪网络: 预测向量场 v(x_t, t, context)
        self.denoiser = nn.Sequential(
            nn.Linear(flat_dim + context_dim + 1, 512),
            nn.SiLU(),
            nn.Linear(512, 512),
            nn.SiLU(),
            nn.Linear(512, 512),
            nn.SiLU(),
            nn.Linear(512, flat_dim)
        )

    def forward(self, noisy_actions, timestep, context):
        """预测向量场 v"""
        t_embed = timestep.unsqueeze(-1)  # unsqueeze增加一个维度
        x = torch.cat([noisy_actions.flatten(-2), context, t_embed], dim=-1)  # torch.cat沿已有维度拼接张量
        return self.denoiser(x).view_as(noisy_actions)

    def training_loss(self, actions, context):
        """
        流匹配训练损失
        x_0: 噪声 (从标准正态分布采样)
        x_1: 真实动作
        x_t = (1-t)*x_0 + t*x_1  (线性插值)
        v_target = x_1 - x_0     (最优向量场)
        """
        B = actions.shape[0]
        x_1 = actions  # 真实动作
        x_0 = torch.randn_like(x_1)  # 噪声

        # 随机时间步
        t = torch.rand(B, device=actions.device)

        # 线性插值
        t_expand = t.view(B, 1, 1)  # 重塑张量形状
        x_t = (1 - t_expand) * x_0 + t_expand * x_1

        # 目标向量场
        v_target = x_1 - x_0

        # 预测向量场
        v_pred = self.forward(x_t, t, context)

        # MSE损失
        loss = ((v_pred - v_target) ** 2).mean()
        return loss

    @torch.no_grad()  # 禁用梯度计算,节省内存
    def sample(self, context, num_steps=10):
        """
        推理: ODE积分生成动作
        x_0 → x_1 (10步欧拉法)
        """
        B = context.shape[0]
        x = torch.randn(B, self.action_horizon, self.action_dim,
                         device=context.device)

        dt = 1.0 / num_steps
        for i in range(num_steps):
            t = torch.full((B,), i * dt, device=context.device)
            v = self.forward(x, t, context)
            x = x + v * dt

        return x  # (B, horizon, action_dim)

3. Diffusion Policy(扩散策略)

3.1 为什么扩散适合机器人?

Text Only
问题: 人类示教数据是多模态的
  - 同一指令"把杯子放架子上",不同人放的位置不同
  - 传统MSE回归 → 取平均 → 动作在所有模态之间(不合理)
  - 扩散模型 → 可以采样不同模态 → 每次执行一种合理方案

对比:
  MSE回归:   ▓░░░░░░░░░░░▓  ← 两个模态的平均(无效动作)
  扩散采样:   ▓            或            ▓  ← 采样到其中一个模态

3.2 Diffusion Policy实现

Python
class DiffusionPolicy(nn.Module):
    """
    Diffusion Policy (Chi et al., RSS 2023)
    条件扩散模型用于行为克隆
    """

    def __init__(self, obs_encoder, noise_pred_net,
                 obs_horizon=2, pred_horizon=16, action_dim=7,
                 num_diffusion_steps=100):
        super().__init__()
        self.obs_encoder = obs_encoder
        self.noise_net = noise_pred_net
        self.obs_horizon = obs_horizon
        self.pred_horizon = pred_horizon
        self.action_dim = action_dim
        self.num_steps = num_diffusion_steps

        # DDPM噪声调度
        betas = torch.linspace(1e-4, 0.02, num_diffusion_steps)
        alphas = 1.0 - betas
        alphas_cumprod = torch.cumprod(alphas, dim=0)
        self.register_buffer('alphas_cumprod', alphas_cumprod)
        self.register_buffer('sqrt_alphas_cumprod', alphas_cumprod.sqrt())
        self.register_buffer('sqrt_one_minus_alphas_cumprod',
                            (1 - alphas_cumprod).sqrt())
        self.register_buffer('betas', betas)

    def forward(self, obs_seq, actions=None):
        """
        obs_seq: (B, obs_horizon, obs_dim) 观察历史
        actions: (B, pred_horizon, action_dim) 训练用
        """
        # 编码观察
        obs_features = self.obs_encoder(obs_seq)  # (B, feat_dim)

        if actions is not None:
            return self._compute_loss(obs_features, actions)
        else:
            return self._sample(obs_features)

    def _compute_loss(self, obs_features, actions):
        """训练: 预测噪声"""
        B = actions.shape[0]
        noise = torch.randn_like(actions)

        # 随机时间步
        t = torch.randint(0, self.num_steps, (B,), device=actions.device)

        # 加噪
        # view(B,1,1)将每个样本的标量α值扩展为(B,1,1),以广播方式与(B,预测步长,动作维度)的张量逐元素相乘
        sqrt_alpha = self.sqrt_alphas_cumprod[t].view(B, 1, 1)
        sqrt_one_minus_alpha = self.sqrt_one_minus_alphas_cumprod[t].view(B, 1, 1)
        noisy_actions = sqrt_alpha * actions + sqrt_one_minus_alpha * noise

        # 预测噪声
        noise_pred = self.noise_net(noisy_actions, t, obs_features)

        return ((noise_pred - noise) ** 2).mean()

    @torch.no_grad()
    def _sample(self, obs_features, use_ddim=True, ddim_steps=10):
        """推理: 从噪声去噪采样"""
        B = obs_features.shape[0]
        x = torch.randn(B, self.pred_horizon, self.action_dim,
                         device=obs_features.device)

        if use_ddim:
            # DDIM加速采样 (100步 → 10步)
            step_indices = torch.linspace(0, self.num_steps - 1, ddim_steps).long()
            for i in reversed(range(len(step_indices))):
                t = step_indices[i].expand(B).to(obs_features.device)
                noise_pred = self.noise_net(x, t, obs_features)

                alpha = self.alphas_cumprod[t].view(B, 1, 1)
                alpha_prev = self.alphas_cumprod[step_indices[i-1]].view(B, 1, 1) \
                             if i > 0 else torch.ones(B, 1, 1, device=x.device)

                x0_pred = (x - (1 - alpha).sqrt() * noise_pred) / alpha.sqrt()
                x = alpha_prev.sqrt() * x0_pred + \
                    (1 - alpha_prev).sqrt() * noise_pred

        return x  # (B, pred_horizon, action_dim)

4. 数据收集与训练

4.1 机器人数据收集方法

方法 效率 质量 代表
遥操作(VR手柄) 低(1:1时间) ALOHA, Mobile ALOHA
遥操作(主从臂) 很高 Gello, UMI
人类视频学习 极高(YouTube) 低(无动作标注) RT-2, GR-1
仿真数据 极高 中(sim2real gap) RoboGen, GenSim
自主探索 低→逐渐提高 RoboAgent

4.2 VLA微调

Python
from transformers import AutoModelForVision2Seq, AutoProcessor
from torch.utils.data import DataLoader
import torch

class VLAFineTuner:
    """VLA模型微调(以OpenVLA为例)"""

    def __init__(self, model_path, learning_rate=2e-5, use_lora=True):
        self.processor = AutoProcessor.from_pretrained(
            model_path, trust_remote_code=True
        )
        self.model = AutoModelForVision2Seq.from_pretrained(
            model_path, torch_dtype=torch.bfloat16, trust_remote_code=True
        ).cuda()

        if use_lora:
            self._apply_lora()

        self.optimizer = torch.optim.AdamW(
            # filter+lambda过滤出requires_grad=True的参数(排除冻结的视觉编码器),只优化LoRA等可训练部分
            filter(lambda p: p.requires_grad, self.model.parameters()),  # lambda匿名函数
            lr=learning_rate, weight_decay=0.01
        )

    def _apply_lora(self, rank=32, alpha=64):
        """对Llama部分应用LoRA(冻结视觉编码器)"""
        from peft import LoraConfig, get_peft_model

        lora_config = LoraConfig(
            r=rank,
            lora_alpha=alpha,
            target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
            lora_dropout=0.05,
            bias="none"
        )
        self.model = get_peft_model(self.model, lora_config)

        # 冻结视觉编码器
        for name, param in self.model.named_parameters():
            if "vision" in name.lower():
                param.requires_grad = False

        trainable = sum(p.numel() for p in self.model.parameters() if p.requires_grad)
        total = sum(p.numel() for p in self.model.parameters())
        print(f"可训练参数: {trainable/1e6:.1f}M / {total/1e6:.1f}M "
              f"({100*trainable/total:.1f}%)")

    def train_epoch(self, dataloader):
        """训练一个epoch"""
        self.model.train()  # train()训练模式
        total_loss = 0

        for batch in dataloader:
            images = batch['images'].cuda()
            instructions = batch['instructions']
            target_actions = batch['actions'].cuda()

            # 前向传播
            outputs = self.model(
                images=images,
                text=instructions,
                labels=target_actions  # 动作token作为标签
            )

            loss = outputs.loss

            # 反向传播
            self.optimizer.zero_grad()  # 清零梯度
            loss.backward()  # 反向传播计算梯度
            torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
            self.optimizer.step()  # 更新参数

            total_loss += loss.item()  # 将单元素张量转为Python数值

        return total_loss / len(dataloader)

# 微调超参数建议
# - 学习率: 2e-5 (全参) / 5e-5 (LoRA)
# - 批大小: 16-64 (取决于显存)
# - 训练轮数: 50-200 (取决于数据量)
# - 数据量: 至少50条有效轨迹(小规模), 500+条(鲁棒)

5. 前沿VLA模型

5.1 GR-2 (字节跳动, 2024)

Text Only
特点:
  - 世界模型 + 策略模型的统一训练
  - 先学预测未来视频帧 → 再学生成动作
  - 38B参数,Sora级别的视频生成能力

架构:
  视频数据(大规模) ──→ 视频预测预训练 ──→ 世界模型
  机器人数据(少量) ──→ 动作微调 ──→ 策略模型

意义: 世界模型让机器人"想象"行动后果,减少对真实数据的依赖

5.2 RDT-1B (清华, 2024)

Text Only
特点:
  - 基于DiT (Diffusion Transformer) 的VLA
  - 1.2B参数,高效率
  - 统一控制单/双臂,灵巧手
  - 支持多种动作空间

关键设计:
  - 物理可解释的动作表示
  - 分层扩散: 先生成粗动作 → 再细化

5.3 模型对比表

模型 参数量 动作类型 视觉编码器 开源 特色
RT-2 55B 离散token ViT 网络知识迁移
Octo 93M 离散+扩散 ResNet 轻量多机器人
OpenVLA 7B 离散token DINOv2+SigLIP 开源主力
π0 ~3B 流匹配 SigLIP 灵巧操作SOTA
GR-2 38B 连续 Video-MAE 世界模型
RDT-1B 1.2B 扩散 SigLIP 高效DiT

6. 练习题

概念理解

  1. RT-2将动作tokenize为离散bin,有什么优缺点?
  2. 为什么π0选择流匹配而不是DDPM?
  3. 双视觉编码器(DINOv2+SigLIP)比单编码器好在哪?
  4. 世界模型(如GR-2)如何减少对机器人数据的依赖?

代码实践

  1. 入门:实现一个简化版的Action Tokenizer(256 bins量化+反量化)
  2. 进阶:用DDPM实现一个条件扩散策略(2D推箱子环境)
  3. 高级:在SIMPLER环境中微调OpenVLA,评估零样本泛化

面试高频题

  1. VLA和传统视觉伺服(Visual Servoing)的本质区别是什么?
  2. 自回归VLA(RT-2)和扩散VLA(π0)各自适合什么场景?
  3. 如何评估VLA模型的泛化能力?设计一个evaluation protocol
  4. 从OpenVLA微调到你的特定机器人需要多少数据?如何提高数据效率?
  5. VLA的延迟问题如何解决?(模型量化、动作缓冲、异步推理)

最后更新:2026年2月