Vision-Language-Action —— VLA具身智能
引言:当AI从“会说话”到“会动手”
2023年7月,Google DeepMind的一篇论文让整个具身智能圈为之震动。论文的标题直截了当:《RT-2: Vision-Language-Action Models Transfer Web Knowledge to Robotic Control》。翻译成大白话就是:让在互联网上“读过万卷书”的视觉语言模型,直接去控制机器人。
这个想法听起来有些疯狂——让一个原本只会“看图说话”的AI,去指挥机械臂抓取物体、旋转关节、执行连续运动。但RT-2做到了,而且做得相当漂亮。它开创了 VLA(Vision-Language-Action) 这一全新模型类别,将视觉、语言和动作三个原本割裂的领域,统一到了一个模型中。
然而,将高维的连续动作空间塞进一个原本为离散文本设计的语言模型,绝非易事。这就引出了两个关键的技术问题:
- 如何让语言模型“输出”动作? —— 答案是 动作Token化(Action Tokenization)
- 如何让输出的动作既平滑又连贯? —— 答案是 动作分块(Action Chunking)
本文将带你深入VLA的技术内核,从RT-2的“动作即文本”思想出发,一路穿越动作分块Transformer(ACT)和扩散策略(Diffusion Policy)的数学原理,用代码和公式为你拆解具身智能最核心的技术栈。
一、RT-2与VLA:当语言模型学会“输出”动作
1.1 什么是VLA?
在RT-2之前,机器人控制通常采用“分层”架构:一个高级规划器将任务分解为子目标,一个低级控制器将子目标转换为关节力矩。这种架构的问题在于:每一层都是独立优化的,信息在传递中不断丢失。
RT-2提出了一个更激进的方案:端到端的VLA模型。它将视觉感知、语言理解和动作生成全部压缩到一个Transformer中。
VLA的核心思想可以概括为一句话:让模型像生成文本一样生成动作。对于模型来说,回答“图像中是什么?”(输出一个文本Token序列)和回答“如何捡起苹果?”(输出一个动作Token序列),在本质上是一样的。
1.2 动作Token化:将连续动作“翻译”成语言
机器人的动作空间是连续的——机械臂的每个关节可以旋转任意角度,夹爪可以张开任意程度。而语言模型的输出空间是离散的——词汇表里每个词是一个独立的Token。
RT-2的核心创新就是解决了这个“连续 vs 离散”的矛盾。具体来说:
第一步:离散化(Discretization)
RT-2采用8维动作空间:
- 6个自由度:末端执行器的位置变化(Δposx, Δposy, Δposz)和旋转变化(Δrotx, Δroty, Δrotz)
- 1个维度:夹爪张合程度(gripper_extension)
- 1个维度:终止标志(terminate)
每个连续维度被离散化为256个区间(bins) 。这个离散化方案继承自RT-1:每个维度的连续值被映射到0-255之间的一个整数。
1import numpy as np2
3class ActionTokenizer:4 """5 将连续动作离散化为Token6 继承自RT-1的256-bin离散化方案7 """8 def __init__(self, num_bins=256):9 self.num_bins = num_bins10 # 每个动作维度的取值范围(示例)11 self.action_ranges = {12 'delta_pos': (-0.1, 0.1), # 位置变化:-0.1m ~ 0.1m13 'delta_rot': (-0.2, 0.2), # 旋转变化:-0.2rad ~ 0.2rad14 'gripper': (0.0, 1.0), # 夹爪:0(闭合)~ 1(张开)15 'terminate': (0, 1) # 终止:0(继续)~ 1(完成)16 }17
18 def discretize(self, continuous_action):19 """20 将连续动作向量离散化为Token序列21
22 Args:23 continuous_action: dict,包含各维度的连续值24 Returns:25 list of int: 长度为8的Token序列,每个值在[0, 255]之间26 """27 tokens = []28 for dim, value in continuous_action.items():29 low, high = self.action_ranges[dim]30 # 将连续值映射到[0, num_bins-1]的整数31 normalized = (value - low) / (high - low)32 bin_idx = int(np.clip(normalized * (self.num_bins - 1), 0, self.num_bins - 1))33 tokens.append(bin_idx)34 return tokens35
36 def continuousize(self, tokens):37 """38 将Token序列解码回连续动作39 """40 action = {}41 dims = list(self.action_ranges.keys())42 for i, dim in enumerate(dims):43 low, high = self.action_ranges[dim]44 # 取区间的中心值作为连续值45 value = low + (tokens[i] / (self.num_bins - 1)) * (high - low)46 action[dim] = value47 return action48
49# 示例:将一个连续动作转化为Token序列50tokenizer = ActionTokenizer()51action = {'delta_pos': 0.03, 'delta_rot': 0.05, 'gripper': 0.7, 'terminate': 0}52tokens = tokenizer.discretize(action)53print(f"动作Token序列: {tokens}")54# 输出示例: [166, 166, 166, 126, 126, 126, 179, 0]第二步:嵌入语言模型
这些离散化的动作Token被嵌入到模型的语言字典中,与自然语言Token共用同一表示空间。模型在训练时,既能预测“苹果”这样的文本Token,也能预测“128 91 241 5 101”这样的动作Token。
这种设计的精妙之处在于:模型不需要学习任何新的“动作模块” 。它原本的文本生成能力被直接“复用”到了动作生成上。
1.3 Co-Fine-Tuning:联合微调
仅仅把动作变成Token还不够——模型还需要学会在什么情况下生成什么样的动作Token。RT-2采用了联合微调(Co-Fine-Tuning) 策略。
训练时,每个Batch中同时混合了两种数据:
- 机器人轨迹数据:(图像,指令)→ 动作Token
- 互联网视觉语言数据:(图像,问题)→ 文本回答
这种设计的深层逻辑是:让模型在“学动作”的同时不“遗忘”视觉语言知识。只做机器人数据微调会导致模型“灾难性遗忘”——失去从互联网数据中获得的丰富语义理解能力。而联合微调让模型既能保持视觉语言的泛化能力,又能学会输出有效的动作。
RT-2构建了两个版本的模型:
- RT-2-PaLI-X:基于PaLI-X,最大版本达550亿参数
- RT-2-PaLM-E:基于PaLM-E,120亿参数
最大的55B模型可以以1-3Hz的频率运行,5B的小版本可达5Hz。
1.4 RT-2的涌现能力
当你在互联网规模的视觉语言数据上预训练一个模型,再让它学习输出动作Token时,一些意想不到的能力会“涌现”出来:
- 对新物体的泛化:看到训练中从未见过的物体,也能正确抓取
- 对未见命令的理解:能够执行训练数据中从未出现过的指令(如“把物体放到数字3上面”)
- 基础推理能力:能够理解“拿起最小的物体”或“拿起离另一个物体最近的物体”
- 多步推理(Chain-of-Thought) :能够进行多阶段的语义推理,比如“找出可以用作临时锤子的物体”(答案是石头)或“给疲倦的人最适合喝什么”(答案是能量饮料)
二、动作分块(Action Chunking):从“一步一动”到“批量规划”
2.1 为什么需要动作分块?
早期的模仿学习模型采用单步预测策略:每一帧观测输入模型,模型输出一个动作,机器人执行这个动作,然后重复这个过程。
这种方式存在一个致命的问题:时序不一致性(Temporal Inconsistency) 。模型在每一帧独立决策,前后动作之间缺乏连贯性,导致机器人的运动抖动、不平稳。
动作分块(Action Chunking)的核心思想是:让模型一次性预测未来多步的动作序列,然后逐步执行。
数学上,设当前时刻为t,模型一次性预测未来K步的动作:
a^t:t+K=πθ(ot,g)
其中ot是当前观测,g是任务目标,a^t:t+K是未来K步的动作序列。
在执��时,通常采用部分执行策略:只执行前k步(k<K),然后重新观测、重新规划。这种“滑动窗口”策略既保证了动作的平滑性,又保持了对外界变化的响应能力。
2.2 ACT:动作分块Transformer
ACT(Action Chunking with Transformers) 是将动作分块与Transformer结合的代表性工作。
ACT的架构包含两个关键设计:
(1)条件变分自编码器(CVAE)
ACT引入CVAE来建模动作的多模态分布。对于同一个观测,可能存在多种合理的动作轨迹——比如抓取一个杯子,可以从左侧抓,也可以从右侧抓。CVAE通过一个潜在变量z 来捕捉这种多模态性:
z∼N(μϕ(ot,at:t+K),σϕ(ot,at:t+K))
a^t:t+K=Decoderθ(ot,z)
(2)Transformer解码器
ACT使用Transformer解码器来生成动作序列。与RT-2将动作离散化为Token不同,ACT直接回归连续动作值。解码器以观测特征和潜在变量z为条件,自回归地生成K步动作。
1import torch2import torch.nn as nn3
4class ActionChunkingTransformer(nn.Module):5 """6 简化的ACT(Action Chunking with Transformers)模型7 """8 def __init__(self, obs_dim, action_dim, chunk_size=50,9 latent_dim=32, num_heads=8, num_layers=4):10 super().__init__()11 self.chunk_size = chunk_size12 self.latent_dim = latent_dim13
14 # 观测编码器15 self.obs_encoder = nn.Linear(obs_dim, 256)16
17 # 潜在变量编码器(CVAE的encoder)18 self.latent_encoder = nn.Sequential(19 nn.Linear(obs_dim + action_dim * chunk_size, 128),20 nn.ReLU(),21 nn.Linear(128, latent_dim * 2) # mean + log_var22 )23
24 # Transformer解码器25 self.action_embed = nn.Linear(action_dim, 256)26 self.pos_embed = nn.Parameter(torch.randn(chunk_size, 256))27 self.decoder = nn.TransformerDecoder(28 nn.TransformerDecoderLayer(256, num_heads, batch_first=True),29 num_layers=num_layers30 )31 self.action_head = nn.Linear(256, action_dim)32
33 def forward(self, obs, actions=None, train=True):34 # obs: [B, obs_dim]35 obs_feat = self.obs_encoder(obs) # [B, 256]36
37 if train and actions is not None:38 # 训练模式:使用CVAE39 # actions: [B, chunk_size, action_dim]40 latent_input = torch.cat([obs, actions.flatten(1)], dim=-1)41 latent_params = self.latent_encoder(latent_input)42 mean, log_var = latent_params.chunk(2, dim=-1)43 # 重参数化采样44 z = mean + torch.exp(0.5 * log_var) * torch.randn_like(mean)45 else:46 # 推理模式:从先验采样47 z = torch.randn(obs.size(0), self.latent_dim, device=obs.device)48
49 # 以观测特征和潜在变量为条件50 # 简化为:将条件拼接到每个时间步51 cond = torch.cat([obs_feat, z], dim=-1) # [B, 256+latent_dim]52 cond_proj = nn.Linear(256 + self.latent_dim, 256).to(obs.device)(cond)53 cond_proj = cond_proj.unsqueeze(1).expand(-1, self.chunk_size, -1)54
55 # 生成动作序列56 # 用可学习的起始token + 位置编码57 action_tokens = torch.zeros(obs.size(0), self.chunk_size, 256, device=obs.device)58 action_tokens = action_tokens + self.pos_embed.unsqueeze(0)59
60 # Transformer解码器(以条件为memory)61 decoded = self.decoder(action_tokens, cond_proj) # [B, chunk_size, 256]62 actions_pred = self.action_head(decoded) # [B, chunk_size, action_dim]63
64 return actions_predACT的核心优势在于:通过一次性预测K步动作,模型被迫学习动作之间的时序依赖关系,从而生成更平滑、更连贯的运动。
2.3 动作分块的核心参数:块大小(Chunk Size)
块大小K的选择是一个关键的超参数:
- K太小:模型退化为单步预测,动作抖动
- K太大:模型需要预测太远的未来,精度下降;且“开环”执行时间过长,无法应对外界变化
实际应用中,常见的配置是:预测50步,执行15步。这种“预测多一点、执行少一点”的策略既保证了平滑性,又保持了 reactivity。
2.4 动作分块的数学本质
从控制理论的角度看,动作分块可以理解为一种模型预测控制(MPC)的变体。MPC在每个时间步求解一个有限时域的最优控制问题,然后执行第一个控制量。动作分块与此类似,但区别在于:
- MPC需要在线求解优化问题(计算密集)
- 动作分块通过离线学习的生成模型直接输出最优轨迹(计算高效)
三、扩散策略(Diffusion Policy):用扩散模型生成平滑动作
如果说RT-2代表了“用语言模型生成动作”的路线,ACT代表了“用Transformer生成动作序列”的路线,那么扩散策略(Diffusion Policy) 则代表了第三条路——用扩散模型生成动作。
3.1 为什么扩散模型适合机器人控制?
扩散模型在图像生成领域已经大放异彩(我们在第四篇中详细讨论过)。将扩散模型应用于机器人控制,有几个天然的优势:
- 多模态分布建模:机器人的动作分布天然是多模态的——同一个任务有多种完成方式。扩散模型擅长建模复杂的多模态分布
- 时序一致性:扩散模型的迭代去噪过程天然考虑了动作序列的整体结构,生成的轨迹平滑自然
- 鲁棒性:扩散模型对噪声和扰动具有较强的鲁棒性
3.2 Diffusion Policy的数学原理
Diffusion Policy将机器人的视觉运动策略建模为一个条件去噪扩散过程。
与DDPM类似,Diffusion Policy包含前向加噪和反向去噪两个过程。但区别在于:
- DDPM的去噪目标:图像像素
- Diffusion Policy的去噪目标:动作序列 at:t+K
前向过程:向动作序列逐步添加高斯噪声
q(ai∣ai−1)=N(ai;1−βiai−1,βiI)
其中 a0=at:t+K 是原始动作序列,aN 是纯噪声。
反向过程:学习从噪声中恢复动作序列
pθ(ai−1∣ai,ot,g)=N(ai−1;μθ(ai,ot,g,i),Σi)
与DDPM一样,训练目标简化为噪声预测:
L=Ei,a0,ϵ[∥ϵ−ϵθ(ai,ot,g,i)∥2]
其中 ai=αˉia0+1−αˉiϵ。
1import torch2import torch.nn as nn3import torch.nn.functional as F4
5class DiffusionPolicy(nn.Module):6 """7 简化的Diffusion Policy模型8 用扩散模型生成动作序列9 """10 def __init__(self, obs_dim, action_dim, chunk_size=16,11 n_diffusion_steps=100, hidden_dim=256):12 super().__init__()13 self.chunk_size = chunk_size14 self.n_diffusion_steps = n_diffusion_steps15 self.action_dim = action_dim16
17 # 观测编码器18 self.obs_encoder = nn.Sequential(19 nn.Linear(obs_dim, hidden_dim),20 nn.ReLU(),21 nn.Linear(hidden_dim, hidden_dim)22 )23
24 # 噪声预测网络(类似DDPM中的U-Net/DiT)25 # 输入:带噪动作序列 + 时间步 + 观测条件26 self.denoiser = nn.Sequential(27 nn.Linear(action_dim * chunk_size + hidden_dim + 1, hidden_dim * 2),28 nn.ReLU(),29 nn.Linear(hidden_dim * 2, hidden_dim * 2),30 nn.ReLU(),31 nn.Linear(hidden_dim * 2, action_dim * chunk_size)32 )33
34 # 噪声调度(预计算)35 self.betas = torch.linspace(1e-4, 0.02, n_diffusion_steps)36 self.alphas = 1 - self.betas37 self.alpha_bars = torch.cumprod(self.alphas, dim=0)38
39 def forward(self, obs, actions=None, train=True):40 """41 训练:预测噪声42 推理:从噪声生成动作序列43 """44 obs_feat = self.obs_encoder(obs) # [B, hidden_dim]45
46 if train and actions is not None:47 # 训练模式48 B = actions.shape[0]49 # actions: [B, chunk_size, action_dim]50 actions_flat = actions.flatten(1) # [B, chunk_size * action_dim]51
52 # 随机采样时间步53 t = torch.randint(0, self.n_diffusion_steps, (B,), device=obs.device)54
55 # 采样噪声56 noise = torch.randn_like(actions_flat)57
58 # 加噪59 alpha_bar = self.alpha_bars[t].view(-1, 1)60 noisy_actions = torch.sqrt(alpha_bar) * actions_flat + \61 torch.sqrt(1 - alpha_bar) * noise62
63 # 预测噪声64 t_norm = t.float().view(-1, 1) / self.n_diffusion_steps65 model_input = torch.cat([noisy_actions, obs_feat, t_norm], dim=-1)66 noise_pred = self.denoiser(model_input)67
68 # 损失:预测噪声的MSE69 loss = F.mse_loss(noise_pred, noise)70 return loss71
72 else:73 # 推理模式:从纯噪声开始去噪74 B = obs.shape[0]75 # 从纯噪声开始76 x = torch.randn(B, self.chunk_size * self.action_dim, device=obs.device)77
78 # 逐步去噪79 for i in reversed(range(self.n_diffusion_steps)):80 t = torch.full((B,), i, device=obs.device, dtype=torch.long)81 t_norm = t.float().view(-1, 1) / self.n_diffusion_steps82
83 # 预测噪声84 model_input = torch.cat([x, obs_feat, t_norm], dim=-1)85 noise_pred = self.denoiser(model_input)86
87 # 去噪一步(DDPM采样)88 alpha = self.alphas[i]89 alpha_bar = self.alpha_bars[i]90 beta = self.betas[i]91
92 x = (1 / torch.sqrt(alpha)) * (93 x - (beta / torch.sqrt(1 - alpha_bar)) * noise_pred94 )95
96 if i > 0:97 x = x + torch.sqrt(beta) * torch.randn_like(x)98
99 # 重塑为动作序列100 actions_pred = x.view(B, self.chunk_size, self.action_dim)101 return actions_pred3.3 扩散策略 + 动作分块:天作之合
扩散策略天然适配动作分块。理由如下:
-
扩散模型生成的是“序列”而非“单点” :DDPM的去噪过程输出的是一个完整的张量——在图像生成中是整张图,在动作生成中是完整的动作序列。这与动作分块“一次性预测多步”的理念完美契合
-
双向上下文建模:扩散模型的去噪过程是迭代优化的,每一步去噪都考虑了序列中所有位置之间的关系。这与自回归模型的“从左到右”生成不同,扩散模型能够利用未来信息来优化当前预测,从而生成更连贯的轨迹
-
多模态性:扩散模型通过随机采样不同的初始噪声,可以生成多样化的动作轨迹,适合处理机器人控制中的多模态问题
实验表明,基于扩散的VLA在机器人控制任务上表现优于自回归基准模型。
3.4 扩散策略的挑战与优化
扩散策略的主要挑战是推理速度。DDPM通常需要数十到上百步迭代才能生成一个动作序列,这对于实时机器人控制来说太慢了。
针对这一问题,研究者提出了多种优化方案:
- DDIM加速:将采样步数从100步减少到10-20步(我们在第四篇中详细讨论过)
- 实时分块(RTC) :一种推理时算法,支持扩散策略的异步实时执行
- 块自适应缓存(BAC) :通过缓存中间动作特征来加速推理
四、技术全景:三种路线的对比与融合
至此,我们已经介绍了VLA领域的三种核心技术路线。它们之间的关系可以用下表概括:
| 维度 | RT-2(VLA) | ACT(动作分块Transformer) | Diffusion Policy |
|---|---|---|---|
| 核心思想 | 动作即文本 | 一次性预测多步动作 | 用扩散模型生成动作序列 |
| 动作表示 | 离散Token(256 bins) | 连续值回归 | 连续值(扩散去噪) |
| 生成方式 | 自回归(从左到右) | 自回归(Transformer解码) | 迭代去噪(双向上下文) |
| 多模态性 | 较弱(离散化损失) | CVAE潜在变量 | 强(随机采样噪声) |
| 平滑性 | 中等 | 好(多步预测) | 极好(整体去噪) |
| 推理速度 | 快(1-3Hz for 55B) | 中等 | 较慢(需多步迭代) |
| 代表工作 | RT-2, OpenVLA | ACT | Diffusion Policy, RDT-1B |
4.1 三条路线的演进逻辑
RT-2的路线代表了“最大化复用互联网知识”的思路。它不追求动作表示的精度,而是追求让VLM“原生”地输出动作。这种思路的优势在于泛化性——模型可以从互联网规模的视觉语言数据中汲取丰富的语义知识。
ACT的路线代表了“用Transformer建模时序依赖”的思路。它专注于让模型学会动作之间的时序关系,从而生成平滑、连贯的运动。
Diffusion Policy的路线代表了“用生成模型建模动作分布”的思路。它利用扩散模型强大的分布建模能力,生成高质量、多模态的动作序列。
4.2 融合趋势:新一代VLA
最新的VLA模型正在融合这三条路线的优势:
- RDT-1B(Robotics Diffusion Transformer) :将扩散模型与Transformer架构结合,专为双臂精细操作设计
- Dream-VLA:以扩散语言模型为骨干,天生适合动作分块和并行生成
- Diffusion Transformer Policy:用大型多模态扩散Transformer直接对动作块进行去噪
这些工作的共同趋势是:用扩散模型生成动作序列 + 用Transformer提供可扩展性 + 用动作分块保证时序一致性。
五、代码实践:一个完整的动作分块VLA示例
让我们用一个简化的端到端示例,把本文的核心概念串起来。
1import torch2import torch.nn as nn3import torch.nn.functional as F4import numpy as np5from collections import deque6
7class ActionChunkingVLA(nn.Module):8 """9 简化的动作分块VLA模型10 结合了:视觉编码器 + 语言编码器 + 动作分块解码器11 """12 def __init__(self, vocab_size, embed_dim=256, action_dim=8,13 chunk_size=16, num_heads=8, num_layers=4):14 super().__init__()15 self.chunk_size = chunk_size16 self.action_dim = action_dim17
18 # 视觉编码器(简化:用线性层代替ViT)19 self.vision_encoder = nn.Sequential(20 nn.Linear(3*224*224, 512), # 假设输入224x224x321 nn.ReLU(),22 nn.Linear(512, embed_dim)23 )24
25 # 语言编码器(简化:词嵌入 + Transformer)26 self.text_embedding = nn.Embedding(vocab_size, embed_dim)27 self.text_encoder = nn.TransformerEncoder(28 nn.TransformerEncoderLayer(embed_dim, num_heads, batch_first=True),29 num_layers=230 )31
32 # 动作分块解码器33 self.action_decoder = nn.TransformerDecoder(34 nn.TransformerDecoderLayer(embed_dim, num_heads, batch_first=True),35 num_layers=num_layers36 )37 self.action_head = nn.Linear(embed_dim, action_dim)38
39 # 可学习的动作查询(类似ACT的起始token)40 self.action_query = nn.Parameter(torch.randn(chunk_size, embed_dim))41
42 # 动作分词器(256 bins)43 self.tokenizer = ActionTokenizer(num_bins=256)44
45 def encode_vision(self, image):46 """编码视觉输入"""47 # image: [B, 3, 224, 224]48 B = image.shape[0]49 image_flat = image.view(B, -1)50 return self.vision_encoder(image_flat) # [B, embed_dim]51
52 def encode_text(self, text_tokens):53 """编码文本指令"""54 # text_tokens: [B, seq_len]55 emb = self.text_embedding(text_tokens)56 return self.text_encoder(emb).mean(dim=1) # [B, embed_dim]57
58 def forward(self, image, text_tokens, actions=None, train=True):59 """60 前向传播61 Args:62 image: [B, 3, 224, 224]63 text_tokens: [B, seq_len]64 actions: [B, chunk_size, action_dim] (训练时提供)65 """66 # 1. 编码多模态输入67 vision_feat = self.encode_vision(image) # [B, embed_dim]68 text_feat = self.encode_text(text_tokens) # [B, embed_dim]69
70 # 2. 融合视觉和语言特征71 cond = (vision_feat + text_feat) / 2 # [B, embed_dim]72 cond = cond.unsqueeze(1) # [B, 1, embed_dim]73
74 # 3. 动作分块解码75 # 扩展条件到每个时间步76 cond_expanded = cond.expand(-1, self.chunk_size, -1)77
78 # 可学习的动作查询 + 条件79 query = self.action_query.unsqueeze(0).expand(image.shape[0], -1, -1)80 query = query + cond_expanded # 注入条件81
82 # Transformer解码83 decoded = self.action_decoder(query, cond_expanded)84 actions_pred = self.action_head(decoded) # [B, chunk_size, action_dim]85
86 if train and actions is not None:87 # 训练损失:MSE + 动作Token化辅助损失88 mse_loss = F.mse_loss(actions_pred, actions)89
90 # 可选:离散化辅助损失(让模型学会输出合理的bin)91 # 将预测的连续动作离散化,与真实动作的Token比较92 return mse_loss93
94 return actions_pred95
96# ============ 推理示例 ============97def run_robot_control():98 """模拟机器人闭环控制"""99 model = ActionChunkingVLA(vocab_size=10000)100 model.eval()101
102 # 模拟环境103 class SimEnv:104 def __init__(self):105 self.obs = torch.randn(1, 3, 224, 224)106 self.state = np.zeros(8)107 def step(self, action):108 # 执行动作,返回新观测109 self.state = action[0] if len(action.shape) > 1 else action110 self.obs = torch.randn(1, 3, 224, 224)111 return self.obs112
113 env = SimEnv()114
115 # 任务指令116 text = "pick up the red cube"117 text_tokens = torch.randint(0, 10000, (1, 20))118
119 # 控制循环120 action_buffer = deque()121 chunk_size = 16122 execute_steps = 5 # 每次执行5步,然后重新规划123
124 for step in range(100):125 # 如果缓冲区为空,重新规划126 if len(action_buffer) == 0:127 with torch.no_grad():128 # 生成动作块129 actions_chunk = model(env.obs, text_tokens, train=False)130 # actions_chunk: [1, chunk_size, action_dim]131 action_buffer.extend(actions_chunk.squeeze(0).tolist())132
133 # 从缓冲区取一个动作执行134 action = action_buffer.popleft()135 env.step(action)136
137 # 每执行execute_steps步,清空缓冲区强制重新规划138 if step % execute_steps == 0:139 action_buffer.clear()140
141 print("控制循环完成")142
143if __name__ == "__main__":144 run_robot_control()六、总结与展望
从RT-2的“动作即文本”到ACT的“动作分块Transformer”,再到Diffusion Policy的“扩散生成动作”,VLA领域在短短两年内经历了飞速的演进:
- RT-2开创了VLA范式:证明了将预训练VLM的知识直接迁移到机器人控制是可行的。动作Token化+联合微调成为后续工作的标准配置
- 动作分块解决了时序一致性问题:通过一次性预测多步动作,ACT和Diffusion Policy让机器人的运动从“抖动”变得“平滑”
- 扩散策略带来了高质量的动作生成:利用扩散模型的多模态建模能力,生成的动作序列既平滑又多样
当前,这三条路线正在走向融合。最新的VLA模型——如RDT-1B、Diffusion Transformer Policy、Dream-VLA——正在将扩散模型、Transformer架构和动作分块三者结合起来。
展望未来,VLA的发展可能沿着以下几个方向继续突破:
- 实时性:通过模型蒸馏、量化、DDIM加速等技术,让大模型VLA能够在嵌入式设备上实时运行
- 长时程规划:将动作分块与层次化规划结合,实现更长时间跨度的任务执行
- 跨具身泛化:让同一个VLA模型能够控制不同形态的机器人(从机械臂到人形机器人)
正如RT-2论文所展示的:当大模型学会“动手”时,具身智能的商业化时间表被往前挪了三年。而动作分块和扩散策略,正是让这双手“稳得住、动得顺”的关键技术。
Some information may be outdated