RynnVLA-002 方法原理详解

发布时间:2026/7/11 19:02:00
RynnVLA-002 方法原理详解 VLA action model输入文本指令、当前或历史图像、可选机器人状态输出未来一段机器人动作。World model输入历史图像和动作输出下一帧或下一组图像用来预测动作执行后的视觉结果。RynnVLA-002 的关键点不是简单地把两个模型放在一起而是把“动作生成”和“图像生成”都写成多模态对话序列预测问题让同一个 Transformer 学习机器人交互中的条件生成规律。同时它在离散动作 token 之外增加了连续动作头用连续 L1 回归提升动作精度。任务定义2.1 VLA 动作生成给定语言任务指令例如 “put the bowl on top of the cabinet”。当前或若干历史第三视角图像。当前或若干历史腕部相机图像。可选机器人本体状态通常包含末端执行器位置、姿态和夹爪状态。输出一个动作 chunk即未来 T 步动作。每步动作通常是 7 维三维平移、三维旋转、夹爪控制。形式化表示a_{t:tT-1} pi(instruction, state_t, image_history, wrist_image_history)其中 T 在 LIBERO 配置里常见为 5在 LeRobot 实验里可以是 20。2.2 World model 图像生成给定历史第三视角图像。历史腕部图像。与历史转移对应的动作。输出下一时刻第三视角图像。下一时刻腕部图像。形式化表示(I_{t1}^{front}, I_{t1}^{wrist}) W(I_{t}^{front}, I_{t}^{wrist}, a_t)在实现中这个图像生成不是像扩散模型那样逐步去噪而是像语言模型一样逐 token 生成图像 token再通过 Chameleon 的图像 tokenizer/decoder 还原为图片。统一序列表示RynnVLA-002 的核心设计是把不同模态都变成 token并放入同一个自回归序列。3.1 文本 token任务指令和 prompt 使用文本 tokenizer 编码。例如动作任务的自然语言提示大致是What action should the robot take to {task_description}?world model 的提示大致是Generate the next image based on the provided sequence of historical images and corresponding actions.3.2 图像 token图像使用 Chameleon VQGAN tokenizer 编码。流程是输入图像被 resize/crop 到目标分辨率相关的网格尺寸。图像进入 VQGAN tokenizer变成离散图像 token。图像 token 被组织成二维 latent 网格。每行末尾插入换行类 reserved token。图像块外面包上图像开始 token、网格高宽 token、图像结束 token。概念上一个图像块长这样image_start h_grid w_gridimage_token_1 image_token_2 …image_token_… …image_end这使得图像能够像文本一样出现在自回归上下文中也能够作为模型要生成的目标。3.3 动作 token动作有两种表示方式。第一种是离散动作 token。连续动作先按每个维度的统计范围归一化到 [-1, 1]再映射到 256 个均匀 bin最后偏移到 reserved token 区间。动作块结构是action_start action_dim_1_token … action_dim_7_token action_end对于一个动作 chunk目标序列里会重复多个 |action| 占位符每个占位符在 tokenization 时被展开成一个完整动作块。第二种是连续动作回归。模型仍然先生成或定位 action_start 这样的动作起始语义位置但真正输出动作时不只依赖离散 token而是使用 Transformer 隐藏状态接一个连续动作头直接回归 T x action_dim 的连续动作。3.4 状态 token状态也被归一化到 [-1, 1]再离散化到 reserved token 区间。状态块结构类似动作state_start state_dim_1_token … state_dim_N_token state_endLIBERO 里的状态通常由末端执行器状态和夹爪状态拼接得到。源码里对应的状态维度是 8 维。把机器人数据改写成“多模态对话”训练数据不是直接喂 (obs, action) 元组而是被改写成对话格式。这样做有两个好处可以复用大语言模型/多模态语言模型的 instruction-following 形式。动作、图像、状态都通过占位符插入对话最终统一展开成 token 序列。4.1 VLA 对话样本动作生成样本形如{“conversations”: [{“from”: “human”,“value”: “What action should the robot take to open the drawer?|state||image||image||image||image|”},{“from”: “gpt”,“value”: “|action||action||action||action||action|”}],“image”: [“front_t-1.png”,“wrist_t-1.png”,“front_t.png”,“wrist_t.png”],“state”: [“state_t.npy”],“action”: [“action_t.npy”,“action_t1.npy”,“action_t2.npy”,“action_t3.npy”,“action_t4.npy”]}这里的 |image|、|state|、|action| 只是占位符。tokenization 时processor 会依次取 image、state、action 字段里的真实数据把它们展开成对应 token 块。4.2 World model 对话样本世界模型样本形如{“conversations”: [{“from”: “human”,“value”: “Generate the next image based on the provided sequence of historical images and corresponding actions.|image||image||action|”},{“from”: “gpt”,“value”: “|image||image|”}],“image”: [“front_t.png”,“wrist_t.png”,“front_t1.png”,“wrist_t1.png”],“action”: [“action_t.npy”]}这个样本让模型学习看到当前 front/wrist 图像和动作后生成下一时刻 front/wrist 图像。4.3 VLA 样本和 WM 样本如何一起训练VLA 样本和 world model 样本不是在同一个轨迹时刻里强制成对做两次 forward也不是每个 step 同时优化一个 VLA batch 和一个 WM batch。实际训练方式是把两类样本合并到同一个训练数据集中再由 dataloader/sampler 像普通监督数据一样取 batch。也就是说训练集概念上是train_dataset action_samples world_model_samples每个 batch 里可能包含 VLA/action 样本也可能包含 world model 样本具体取决于合并后的样本顺序、shuffle 和 sampler。源码确认pretokenize 流程会把 action 数据 record 和 world model 数据 record concat 成一个统一 recordno-pretokenize 流程里dataset 构造时也会分别 append action sample 和 world sample。这意味着抽到 VLA/action 样本时目标主要是动作 token连续动作头也有有效的 L1 监督。抽到 world model 样本时目标主要是未来图像 token通常没有连续动作头监督。两类任务的训练比例主要由合并进去的样本数量决定当前实现里没有看到额外的任务均衡采样器。4.4 训练标签 mask训练时prompt 部分只作为条件不计算 lossassistant/gpt 部分作为目标计算 loss。也就是说human prompt tokens - label -100gpt target image/action - label token_idpadding - label -100这和语言模型监督微调一致只是目标 token 可能是文本、图像 token、动作 token 或状态 token。模型结构RynnVLA-002 的主干是 Chameleon 条件生成模型外面加了机器人任务相关的配置和动作头。5.1 多模态自回归 Transformer主干 Transformer 接收统一 token 序列x [text tokens, state tokens, image tokens, action tokens, …]训练时它做标准 causal language modelingP(x) product_i P(x_i | x_i)因此只要把目标动作或目标图像放在序列后半段模型就会学习在前文条件下生成它们。5.2 动作离散生成分支离散动作分支完全复用语言模型的 token logits模型预测下一个 token 分布。在动作位置上目标 token 是动作 bin 对应的 reserved token。推理时生成动作 token 序列。将动作 token 反量化回 [-1, 1] 连续动作。再按数据集 min/max 反归一化回机器人环境动作空间。这种方式简单统一但每个动作维度只能落在离散 bin 中精度受量化限制。5.3 连续动作头连续动作头解决动作量化精度问题。它的思路是让大 Transformer 负责理解语言、图像、状态上下文再用一个专门的小网络从 hidden state 回归连续动作。连续动作头内部流程找到序列中第一个动作起始 token action_start 的位置。取该位置之前的所有 Transformer hidden states 作为上下文。拼接一组可学习的 action query token。数量为 time_horizon * action_dim。将上下文和 action query 一起送入一个轻量 Transformer Encoder。取 action query 对应的输出。用 MLP-ResNet 投影为连续动作。reshape 成 [time_horizon, action_dim]。直观地说连续动作头不是让语言模型一个 token 一个 token 猜动作数值而是在“我要开始输出动作”的位置对上下文做一次条件读取然后并行解码未来动作 chunk。训练目标训练总 loss 由几部分组成。6.1 token 交叉熵主 loss 是标准 token-level cross entropyL_ce CE(logits, labels)它会监督world model 的图像 token 生成。离散动作 token 生成。其他 assistant 目标 token。6.2 连续动作 L1 loss当启用连续动作头时模型从 labels 中找到动作 token 块把离散动作 token 反解码成归一化连续动作作为连续动作头的监督目标。L_ct mean_absolute_error(predicted_action, decoded_label_action)源码确认训练中默认总 loss 是L L_ce loss_ct_weights * L_ct optional_z_loss其中 loss_ct_weights 默认是 10。6.3 图像 token loss 权重在 action-world-model 联合训练的某些路径里图像 token 的交叉熵权重会被调小。源码中对 token id [3, 8195) 这段图像 token 区间设置 loss_img_weights默认值是 0.04。这背后的动机是图像 token 数量远多于动作 token。如果不降权训练目标很容易被图像重建主导动作能力被稀释。6.4 可选 z-loss如果配置 z_loss_weight 0会对 logits 的 log-sum-exp 加一个正则项L_z mean(logsumexp(logits)^2)它用于稳定大词表 logits但默认可以为 0。注意力机制细节模型使用 causal attention也就是每个位置只能看自己之前的 token。源码里还针对图像块和动作块构造了额外 attention mask。核心意图是在包含多个动作块和图像块的序列中控制某些动作块之间的可见性避免当前要预测的动作块不恰当地看到后续或不该参考的动作信息。可以把它理解为统一序列虽然把所有模态拼在一起但训练时仍需要保证“条件”和“目标”的时间方向正确。7.1 动作 chunk 内不同动作块的 mask动作 chunk 在离散 token 序列里通常表现为多个连续的 action blockcondition_tokensaction_start action_t_dim1 … action_t_dim7 action_endaction_start action_t1_dim1 … action_t1_dim7 action_endaction_start action_t2_dim1 … action_t2_dim7 action_end…如果只使用标准 causal mask那么后面的动作块会看到前面的动作块。例如 action_t1 的 token 可以 attend 到 action_taction_t2 又可以 attend 到 action_t 和 action_t1。这会让 chunk 内动作形成自回归依赖action_t - action_t1 - action_t2当前实现额外做了一步对位于最后一个 image block 之后的 action block把“当前动作块看之前动作块”的 attention 置 0。伪代码可以理解为初始 mask 是标准 causal mask后面的 token 默认可以看前面的 token。mask causal_lower_triangular_mask(seq_len)找到序列中的所有图像块和动作块。image_blocks find_blocks(input_ids, IMAGE_START_ID, IMAGE_END_ID)action_blocks find_blocks(input_ids, ACTION_START_ID, ACTION_END_ID)只处理位于最后一个图像块之后的动作目标块。last_image_end image_blocks[-1].endfor current_action in action_blocks:if current_action.start last_image_end:previous_actions [block for block in action_blocksif block.end current_action.start]for prev_action in previous_actions: # 禁止当前动作块 attend 到更早的动作块。 # 这样 action_t1 不再直接依赖 action_t 的 token # action_t2 也不再直接依赖 action_t/action_t1 的 token。 mask[current_action.start : current_action.end 1, prev_action.start : prev_action.end 1] 0因此离散动作 chunk 的训练更接近action_t f(condition_tokens)action_t1 f(condition_tokens)action_t2 f(condition_tokens)…而不是严格的action_t1 f(condition_tokens, action_t)action_t2 f(condition_tokens, action_t, action_t1)这里有一个细节这个 mask 切断的是“不同 action block 之间”的依赖不是切断同一个 action block 内部的维度依赖。一个动作块内部仍然是 causal 的action_start dim1 - dim2 - … - dim7 - action_end所以离散动作 token 分支可以概括为chunk 内不同未来动作块主要共同依赖观测/语言/状态上下文但每个动作块内部的 7 个维度仍按 token 顺序生成。连续动作头更进一步它不按动作 token 逐步生成 chunk而是在动作开始位置前取上下文 hidden states拼接 time_horizon * action_dim 个 action query一次性回归整个动作 chunk。因此连续动作头本身就是并行 chunk 预测[action_t, action_t1, …, action_tT-1] ActionHead(condition_hidden_states)8. 训练流程伪代码8.1 数据构造遍历 LIBERO 里的任务集合。每个 task 通常对应一个自然语言目标和若干演示轨迹。for task in LIBERO_tasks:trajectories load_trajectories(task)# 一条轨迹包含按时间排列的图像、状态和动作。 for traj in trajectories: images_front traj.front_images images_wrist traj.wrist_images states traj.robot_states actions traj.actions # 对轨迹中的每个时间步 t 都尝试构造训练样本。 for t in range(len(actions)): # 构造 VLA/action 样本给定当前/历史观测预测未来一段动作 chunk。 # 只有当 t 后面还剩足够 time_horizon 步动作时样本才有效。 if t time_horizon len(actions): # 收集历史图像。若使用双视角则每个时间步会放 front 和 wrist 两张图。 obs_images collect_history( images_front, images_wrist, endt, historyhis ) # 取当前机器人本体状态例如末端执行器位姿和夹爪状态。 state_t states[t] # 监督目标是从 t 开始的未来 time_horizon 步动作。 future_actions actions[t : t time_horizon] add_sample({ # human 侧是条件语言指令 状态占位符 图像占位符。 # 这些占位符之后会被展开成真正的 state/image token。 human: instruction(task) |state| |image| * len(obs_images), # gpt 侧是目标每个 |action| 会展开成一个完整动作 token 块。 gpt: |action| * time_horizon, # image/state/action 字段按占位符出现顺序提供真实数据。 image: obs_images, state: [state_t], action: future_actions, }) # 构造 world model 样本给定当前图像和动作预测下一帧图像。 # 只有当 t1 的未来图像存在时样本才有效。 if t 1 len(images_front): # 条件图像当前 front 和 wrist。 hist_images [images_front[t], images_wrist[t]] # 目标图像下一时刻 front 和 wrist。 next_images [images_front[t 1], images_wrist[t 1]] # 条件动作从当前状态执行的动作。 action_t actions[t] add_sample({ # human 侧描述 world model 任务并给出当前双视角图像和动作。 human: world_prompt |image||image||action|, # gpt 侧要求模型生成下一时刻双视角图像。 gpt: |image||image|, image: hist_images next_images, action: [action_t], })训练时不是分别跑两个 dataloader而是把 action 样本和 world model 样本合并。shuffle/sampler 会决定每个 batch 抽到哪类样本比例主要由两类样本数量决定。train_dataset shuffle(action_samples world_model_samples)8.2 多模态 tokenizationdef tokenize_conversation(sample, trainingTrue):# output_tokens 是模型实际输入的完整 token 序列。output_tokens []# labels 与 output_tokens 等长label-100 的位置不参与 CE loss。 labels [] # 对话模板会把 human/gpt 两侧展开成一段段文本或模态占位符。 # human 通常是条件gpt 通常是训练目标。 for segment in render_conversation_template(sample[conversations]): if segment.is_text: # 普通文本直接用文本 tokenizer 编码。 ids text_tokenizer(segment.text) output_tokens.extend(ids) # 如果这段文本属于目标侧就监督预测如果属于 prompt 条件侧就 mask 掉。 labels.extend(ids if segment.is_target else [-100] * len(ids)) elif segment.placeholder |image|: # 每遇到一个 |image|就按顺序取一个真实图像。 image pop_next(sample[image]) # 图像通过 Chameleon VQGAN tokenizer 变成离散 image token 块。 ids encode_image_as_chameleon_tokens(image) output_tokens.extend(ids) # 条件图像只作为输入目标图像才计算 CE。 labels.extend(ids if segment.is_target else [-100] * len(ids)) elif segment.placeholder |action|: # 每遇到一个 |action|就按顺序取一条真实动作。 action pop_next(sample[action]) # 动作先归一化到 [-1, 1]再量化成 reserved token。 ids encode_action_as_reserved_tokens(action) output_tokens.extend(ids) # VLA 样本的目标动作 token 会参与 CE历史动作条件会被 mask。 labels.extend(ids if segment.is_target else [-100] * len(ids)) elif segment.placeholder |state|: # 状态通常是当前机器人 proprioception例如 ee pose gripper。 state pop_next(sample[state]) # 状态和动作类似也会被归一化并离散化到 reserved token。 ids encode_state_as_reserved_tokens(state) output_tokens.extend(ids) # 当前实现里 state 通常作为条件输入因此大多会被 mask。 labels.extend(ids if segment.is_target else [-100] * len(ids)) # 返回统一序列和监督标签后续训练只需要这两个对象即可计算 CE。 return output_tokens, labels8.3 主训练循环for batch in dataloader:# batch 可能包含 VLA/action 样本也可能包含 world model 样本。# collate 会把不同长度的 token 序列 pad 到同一长度。token_batch, label_batch collate_and_pad(batch)# 主模型做一次完整 forward # - logits 用于 token 级 CE包括图像 token 和离散动作 token。 # - hidden_states 会被连续动作头读取。 outputs model( input_idstoken_batch, labelslabel_batch, output_hidden_statesTrue, trainingTrue, ) # c_loss自回归 token 预测损失。 # 对 VLA 样本它监督动作 token对 WM 样本它监督未来图像 token。 ce_loss outputs.cross_entropy_loss # 最后一层 hidden states 作为连续动作头的输入上下文。 hidden_states outputs.last_hidden_states # 连续动作头会找到 ACTION_START_ID 之前的上下文并回归动作 chunk。 predicted_actions action_head( hidden_stateshidden_states, input_idstoken_batch, target_token_idACTION_START_ID, ) # 从 label 里找到真实动作 token 块。 # 注意当前实现用 label 里的离散动作 token 反解码连续监督目标。 label_action_tokens find_action_blocks(label_batch) target_actions decode_action_tokens(label_action_tokens) # 连续动作头的监督是 L1 回归损失。 # 如果 batch 中没有有效动作目标该项应为 0 或被跳过。 continuous_loss l1(predicted_actions, target_actions) # 总损失 token CE 连续动作 L1 的加权和。 # 源码默认 loss_ct_weight 为 10。 total_loss ce_loss loss_ct_weight * continuous_loss # 可选 logits 稳定正则。 if z_loss_enabled: total_loss z_loss_weight * z_loss(outputs.logits) # 反向传播会同时更新主 Transformer 可训练参数和连续动作头参数。 total_loss.backward() optimizer.step()8.4 连续动作头伪代码def continuous_action_head(hidden_states, input_ids):# hidden_states: [B, L, H]# B 是 batch sizeL 是序列长度H 是主模型 hidden size。# input_ids: [B, L]# 与 hidden_states 对齐用来定位动作开始 token。contexts [] for b in range(B): # 找到当前样本里第一个动作开始 token。 # 这个位置表示“从这里开始要输出动作”。 action_start first_position(input_ids[b] ACTION_START_ID) # 取动作开始 token 之前的所有 hidden states 作为条件上下文。 # 上下文中已经融合了语言、状态、图像等信息。 contexts.append(hidden_states[b, :action_start]) # 创建一组可学习 action query。 # 数量等于 time_horizon * action_dim例如 5 步 * 7 维 35 个 query。 action_queries learned_embedding( shape[time_horizon * action_dim, hidden_size] ) sequences [] for context in contexts: # 把上下文和 action query 拼在一起。 # query 会在后面的 Transformer Encoder 中通过 attention 读取上下文。 sequences.append(concat(context, action_queries)) # 不同样本的上下文长度可能不同需要 pad 成同一长度。 # mask 用来告诉 Transformer 哪些位置是 padding。 padded_sequences, mask pad_to_same_length(sequences) # 先把主模型 hidden size 投影到动作头内部较小的维度。 z linear_projection(padded_sequences) # 用轻量 Transformer Encoder 做条件读取。 # 这里不是 causal LM而是让 action query 充分 attend 到上下文。 z transformer_encoder(z, padding_maskmask) # 取拼接序列末尾的 action query 输出。 # 这些输出就是每个未来动作维度的条件表示。 query_outputs take_last_query_outputs( z, num_queriestime_horizon * action_dim ) # MLP-ResNet 把 query 表示映射成连续动作数值。 actions mlp_resnet(query_outputs) # 整理成 [batch, 未来步数, 每步动作维度]。 actions reshape(actions, [B, time_horizon, action_dim]) return actions推理流程9.1 连续动作推理在 LIBERO 连续动作评估中推理闭环大致如下环境 reset并执行若干步 dummy action让模拟器稳定。读取当前第三视角图像、腕部图像和机器人状态。如果动作缓存为空就构造 VLA prompt。prompt 经过 item processor 展开成 token。模型先生成一个动作起始 token。连续动作头根据 hidden states 输出一个动作 chunk。将归一化动作反归一化回环境动作空间。逐步执行 chunk 中的动作。更新历史图像、腕部图像和历史动作。重复直到成功或达到最大步数。伪代码动作缓存用于保存一次模型调用生成的 action chunk。环境每 step 消耗一个动作缓存空了才重新查询大模型。actions_cache []历史信息会作为下一次决策的上下文。history_front []history_wrist []history_actions []while not done and t max_steps:# 从环境中读取当前观测。obs env.current_observation()# 提取并预处理第三视角图像、腕部图像和机器人状态。 front preprocess_front_image(obs) wrist preprocess_wrist_image(obs) state get_robot_state(obs) # 只有缓存为空时才调用模型避免每个仿真 step 都跑一次大模型。 if len(actions_cache) 0: # 构造 VLA 推理 prompt。 # 其中包含任务文本、当前状态、当前双视角图像以及可选历史图像/动作。 sample build_vla_prompt( task_descriptiontask, statestate, current_frontfront, current_wristwrist, history_fronthistory_front, history_wristhistory_wrist, history_actionshistory_actions, ) # 把 prompt 展开成模型输入 token推理时不需要 labels。 input_ids tokenize_conversation(sample, trainingFalse) # 连续动作头输出一个动作 chunk形状近似 [time_horizon, action_dim]。 action_chunk model.generate_action_head(input_ids) # 将 chunk 放入缓存后续环境 step 逐个取出执行。 actions_cache.extend(action_chunk) # 取出当前要执行的一步归一化动作。 normalized_action actions_cache.pop(0) # 将 [-1, 1] 归一化动作映射回 LIBERO 环境需要的真实动作范围。 env_action unnormalize_action(normalized_action) # 在环境中执行动作。 obs, reward, done, info env.step(env_action) # 更新历史上下文供下一次模型查询使用。 history_front.append(front) history_wrist.append(wrist) history_actions.append(normalized_action)9.2 离散动作推理离散动作推理不走连续动作头而是直接让语言模型生成动作 token离散动作推理只依赖 LM head 生成 token不调用连续动作头。input_ids tokenize_vla_prompt(…)生成若干 reserved action token每个动作块包含 start、各维动作 token、end。generated_tokens model.generate(input_ids, max_new_tokensaction_steps * tokens_per_action)从生成结果中解析出一个或多个动作块。action_blocks parse_action_blocks(generated_tokens)将动作 token 反量化成 [-1, 1] 的连续动作。actions decode_action_tokens(action_blocks)再映射回环境动作空间。env_actions unnormalize_actions(actions)优点是与自回归训练目标完全一致缺点是动作被 256-bin 量化。9.3 World model 推理world model 推理是图像 token 生成构造 world model 推理样本条件是当前 front/wrist 图像和动作目标是让模型继续生成图像 token。sample {“human”: world_prompt “|image||image||action|”,“image”: [front_t, wrist_t],“action”: [action_t],}推理时只需要输入 token不需要 labels。input_ids tokenize_conversation(sample, trainingFalse)自回归生成未来图像 token。generated_image_tokens model.generate_img(input_ids)将 Chameleon 图像 token 解码回 RGB 图像。front_next, wrist_next decode_chameleon_images(generated_image_tokens)生成图像可以用于离线评估也可以作为 action-world-model 的一部分帮助模型学习动作造成的视觉变化。为什么这种统一方法有效10.1 动作和世界变化共享机器人语义动作模型学的是“看到状态后该怎么动”world model 学的是“这样动之后世界会怎么变”。这两件事本质上依赖同一套视觉、语言和物理语义。统一模型让二者共享表示而不是各自训练孤立网络。10.2 图像 token 让未来视觉预测变成语言模型式生成Chameleon 的图像 tokenizer 把图像离散化后图像预测就可以用 next-token prediction 处理。这让 world model 不需要单独设计 diffusion decoder 或 video model而是和文本、动作共用同一个自回归建模范式。10.3 动作 token 让机器人控制进入语言模型词表动作离散化后机器人控制变成了 token 预测问题。模型可以像生成文本一样生成动作序列从而把 VLA 任务纳入同一个因果序列建模框架。10.4 连续动作头弥补离散化精度纯 token 动作会受到 bin 数量限制。连续动作头利用大模型上下文 hidden state 直接回归动作保留统一表示的优点同时改善控制精度。10.5 action chunk 提高闭环控制效率一次预测多个未来动作可以减少每一步都调用大模型的开销并让模型学习短时动作连贯性。执行时通过缓存逐步消费 chunk再根据新观测重新规划。重要超参数和约定项目 含义his 使用多少历史帧作为观测上下文time_horizon / len_action 一次预测多少未来动作action_dim 每步动作维度LIBERO 中通常为 7resolution 图像训练/推理分辨率VLA 常见 256world model 常见 512loss_ct_weights 连续动作 L1 loss 权重默认 10loss_img_weights 图像 token loss 降权默认 0.04ACTION_START_ID 动作开始 token id源码中使用 10004ACTION_END_ID 动作结束 token id源码中使用 15004方法边界和容易误解的地方RynnVLA-002 不是只训练一个 action policy。它同时包含 action model 和 world model 的统一训练思路。world model 生成的是图像 token再解码成图片不是直接回归 RGB。离散动作和连续动作头是两套动作输出机制可以分别用于评估连续动作头不是简单地读取最后一个 token logits。|image|、|action|、|state| 是数据构造里的占位符不是最终模型直接看到的单个普通 token它们会展开成完整模态 token 块。prompt 部分不计算监督 loss只有 assistant/gpt 目标部分计算 loss。图像 token 数量很多联合训练时需要权重平衡否则动作学习容易被图像生成目标压过。推理时 action chunk 会被缓存并逐步执行因此模型查询频率低于环境 step 频率。动作 chunk 不是简单的逐动作自回归链。离散动作 token 分支通过 mask 切断不同 action block 之间的直接 attention连续动作头则一次性并行回归整个 chunk。不过离散动作块内部的动作维度仍保留 causal 顺序。最小可复现的概念版本如果要从零复现这个方法的核心不必先复刻全部工程细节可以按下面的最小版本理解1. 准备机器人轨迹一条轨迹是按时间排列的多模态机器人交互数据。trajectory {“instruction”: text,“front_images”: […],“wrist_images”: […],“states”: […],“actions”: […],}2. 构造两种样本并合并成混合多任务训练集VLA 样本学“看到观测后该怎么动”。vla_samples make_action_prediction_samples(trajectory)World model 样本学“执行动作后视觉世界怎么变化”。world_samples make_next_image_prediction_samples(trajectory)两类样本合并训练不是每一步固定同时训练两类 batch。dataset shuffle(vla_samples world_samples)3. 把所有模态变成 tokenfor sample in dataset:# input_ids 是统一多模态序列labels 标记哪些位置需要计算 CE。input_ids, labels multimodal_tokenize(sample)4. 训练统一自回归模型logits 用于离散 token 预测hidden_states 用于连续动作头。logits, hidden_states transformer(input_ids)CE 同时覆盖离散动作 token 和未来图像 token。loss_ce cross_entropy(logits, labels)5. 额外训练连续动作头找到动作开始 token 前的上下文 hidden states。action_context hidden_states_before_action_start(hidden_states, input_ids)连续动作头根据上下文回归未来动作 chunk。pred_action continuous_action_head(action_context)当前实现从 label 中的离散动作 token 反解码连续监督目标。如果额外保存原始归一化 GT action也可以直接用原始连续 GT 监督。target_action decode_action_labels(labels)连续动作头使用 L1 损失。loss_ct l1(pred_action, target_action)联合优化主 token CE 连续动作 L1。loss loss_ce lambda_ct * loss_ctloss.backward()6. 部署时闭环控制while not task_done:# 读取当前机器人观测。obs get_robot_observation()# 根据当前观测和历史上下文构造 VLA prompt。 prompt build_action_prompt(obs, instruction, history) # 可以选择连续动作头或离散动作 token 推理。 action_chunk model.generate_actions(prompt) # 执行动作 chunk缓存为空或需要重新规划时再查询模型。