OpenVLA 核心原理深度解析
用一句话建立直觉OpenVLA 的本质是把“机器人看到什么、听到什么指令、下一步怎么动”这个控制问题改写成“多模态语言模型在图像条件下生成一串动作 token”的问题。普通视觉语言模型通常是图片 问题 - 文本回答OpenVLA 是图片 任务指令 - 动作 token - 连续机器人动作类比一下一个传统机器人策略像“直接输出方向盘角度和油门大小”的驾驶控制器。OpenVLA 更像是先发明一套“动作密码本”把方向盘角度和油门大小编码成若干特殊词然后让一个会看图的语言模型根据路况和指令生成这些特殊词。最后系统再把特殊词翻译回真实控制量。这个思路看起来绕但它把机器人动作预测接入了语言模型最擅长的能力根据上下文生成下一个 token。OpenVLA 到底在解决什么问题机器人模仿学习里一条训练数据大致包含三类信息观测相机图像可选的本体状态例如关节角、末端位置、夹爪状态任务人类语言指令例如“把胡萝卜放进碗里”动作机器人专家在这个状态下执行的动作常见形式是连续向量例如[x方向移动, y方向移动, z方向移动, 旋转1, 旋转2, 旋转3, 夹爪开合]模型要学的是一个条件策略policy(image, instruction) - action也就是当前画面 当前任务 - 下一步机器人动作传统做法可能直接训练一个神经网络回归动作图片特征 语言特征 - MLP - 连续动作向量OpenVLA 选择了另一条路图片特征 语言 token - LLM - 动作 token - 连续动作向量这样做的关键收益是可以复用大型语言模型的自回归生成框架。可以复用视觉语言模型的图文对齐能力。可以把“动作预测”纳入通用 token 预测范式。可以用语言指令自然地控制机器人行为。核心思想把机器人控制改写成 token 生成语言模型最擅长的问题是给定前文预测下一个 token。例如输入: The robot should pick up the输出: cupOpenVLA 想让语言模型做输入: 图像 “机器人应该如何执行把杯子拿起来”输出: 动作 token 1, 动作 token 2, …, 动作 token N这里的动作 token 不是普通词语而是人为约定的“动作编码符号”。如果动作是 7 维action [a1, a2, a3, a4, a5, a6, a7]模型就可以生成 7 个动作 tokentokens [t1, t2, t3, t4, t5, t6, t7]每个 token 对应一个动作维度的离散值。于是原问题回归一个连续向量被改写成生成一串离散 token这一步是 OpenVLA 的思想核心。从机器人动作到语言模型 token机器人动作通常是连续数值。比如机械臂末端移动动作dx 0.021dy -0.014dz 0.007语言模型不能天然生成“高精度浮点控制量”。它生成的是 token。因此需要一个动作编码器。第一步归一化动作不同机器人、不同数据集的动作尺度不一样。例如机器人 A:dx 的合理范围可能是 [-0.05, 0.05]机器人 B:dx 的合理范围可能是 [-0.5, 0.5]如果直接混在一起训练模型会很混乱。所以先把动作统一映射到一个标准区间通常是[-1, 1]一种常见的分位数归一化方式是normalized 2 * (raw - q01) / (q99 - q01) - 1其中q01 这个动作维度在数据集中的 1% 分位数q99 这个动作维度在数据集中的 99% 分位数使用分位数而不是最小值/最大值是为了降低异常动作值的影响。q01 和 q99 到底是什么意思分位数可以理解成把一堆数从小到大排序后位于某个百分比位置的值。假设某个数据集里dx 这个动作维度一共有 10000 个样本。把所有 dx 从小到大排序dx_sorted [最小值,第二小,第三小,…最大值]那么q01 排在 1% 位置附近的值q99 排在 99% 位置附近的值更白话地说q01 ≈ “正常小值边界”q99 ≈ “正常大值边界”它们不是最小值和最大值而是把最极端的少量异常值排除掉之后的范围。举个具体例子。假设某个机器人数据集里dx 的一批值从小到大大概是[-10.0, -0.06, -0.05, -0.04, -0.03,-0.02, -0.01, 0.00, 0.01, 0.02,0.03, 0.04, 0.05, 0.06, 0.07,0.08, 0.09, 0.10, 0.11, 8.0]这里 -10.0 和 8.0 很可能是异常值例如传感器错误、数据记录异常、碰撞瞬间动作等。如果用最小值/最大值归一化min -10.0max 8.0那么正常动作值比如 0.05会被压到非常靠近中间的位置。模型会把大量正常动作都挤在很窄的归一化区间里难以区分细微差别。如果用分位数系统会更关注大多数正常数据。例如可以近似认为q01 ≈ -0.06q99 ≈ 0.11这表示绝大多数正常 dx 动作大概落在 [-0.06, 0.11] 之间。然后再把这个正常范围映射到 [-1, 1]。例如q01 -0.06q99 0.11raw 0.025代入公式normalized 2 * (raw - q01) / (q99 - q01) - 1 2 * (0.025 - (-0.06)) / (0.11 - (-0.06)) - 1 2 * 0.085 / 0.17 - 1 0所以 raw 0.025 位于正常范围中间会被映射成 0。边界也很好理解raw -0.06 - normalized -1raw 0.11 - normalized 1因此使用 q01/q99 的含义是不要让最极端的 1% 小值和最极端的 1% 大值决定整个动作范围用中间 98% 的主要数据范围来定义“正常动作尺度”。这能让模型更关注真实控制中常见的动作变化而不是被少量异常值带偏。第二步离散化归一化后的动作在 [-1, 1] 之间。OpenVLA 风格的离散化会把这个区间切成固定数量的桶例如 256 个桶。[-1, 1] - 256 个离散区间于是-1.0 附近 - 第 1 桶0.0 附近 - 中间桶1.0 附近 - 第 256 桶类比连续动作像一把可以无级调节的旋钮离散化像把旋钮改成 256 档。精度少了一点但模型从“预测小数”变成了“选择档位”。第三步把桶映射到 token离散桶还不是语言模型 token。还需要一个映射桶编号 - token id一种简单策略是使用语言模型词表中很少用于自然语言的末尾 token。假设词表大小是vocab_size 32000动作桶数量是n_bins 256那么可以约定词表最后 256 个 token 专门表示动作桶所以第 1 桶 - 某个靠近词表末尾的 token第 128 桶 - 另一个动作 token第 256 桶 - 另一个动作 token这样语言模型不需要新增一个动作输出头。它仍然输出整个词表上的 logits只不过训练会让它在动作位置生成这些动作 token。从图像到语言模型可理解的 embedding语言模型原本只理解 token embedding不理解图片像素。OpenVLA 需要把图片变成可以插入语言模型上下文的向量。整体过程是图片- 视觉编码器- 一串图像 patch 特征- 投影器- 与 LLM hidden size 匹配的图像 embedding- 插入语言模型输入序列视觉编码器视觉编码器通常是 ViT 类模型。它会把图片切成 patch每个 patch 变成一个向量。图片 - [patch_1, patch_2, …, patch_M]每个 patch 向量表示图片某个区域的视觉语义。可以类比成人看一张桌面图片会注意到“左边有杯子”“中间有碗”“右侧有夹爪”。ViT patch 特征就是让模型用一堆局部视觉向量表达这些区域信息。投影器视觉编码器输出的向量维度通常和语言模型 hidden size 不一致。例如视觉特征维度 1024语言模型 hidden size 4096不能直接拼接。因此需要一个投影器projector: vision_dim - llm_hidden_dim投影器可以是线性层也可以是小 MLP。它的角色像翻译器视觉编码器说“视觉向量语言”语言模型说“token embedding 语言”projector 负责把前者翻译成后者插入语言模型上下文处理文本时语言模型本来会得到[BOS], [In], [:], [What], [action], …每个 token 会变成 embedding[text_emb_0, text_emb_1, text_emb_2, …]OpenVLA 会把图片 patch embedding 插进去[BOS],[image_emb_1], [image_emb_2], …, [image_emb_M],[In], [:], [What], [action], …注意图片没有被转换成自然语言描述。它是以 embedding 形式进入语言模型上下文的。训练样本如何构造一条机器人数据图像: 当前相机画面指令: “put the carrot in the bowl”动作: [0.1, -0.2, 0.0, …]会被改写成类似对话的格式输入部分:In: What action should the robot take to put the carrot in the bowl?Out:输出部分:动作token1动作token2…动作tokenN拼起来是In: What action should the robot take to put the carrot in the bowl?Out: 动作token1动作token2…动作tokenN然后整段文本被 tokenizer 编码成input_ids [prompt tokens…, action tokens…]训练时labels 通常从 input_ids 复制而来但 prompt 部分会被屏蔽labels [-100, -100, …, -100, action_token_1, action_token_2, …]其中 -100 是常见的 ignore index表示这些位置不参与 loss。这非常重要。模型不是被训练去复述In: What action should…模型只在动作 token 位置被监督。也就是说训练目标是看到图像和指令后把动作 token 预测对。模型训练时到底在优化什么虽然 OpenVLA 是机器人模型但它的训练目标本质上仍是语言模型的 next-token prediction。只不过预测目标换成了动作 token。训练时模型看到图像 embedding prompt token它要生成动作 token 1动作 token 2…动作 token N损失函数语言模型通常输出每个位置对整个词表的 logits在动作位置上它会对所有 token 给出概率。训练目标要求正确动作 token 的概率尽可能高。所以 loss 是交叉熵loss cross_entropy(predicted_token_distribution, target_action_token)因为 prompt 和图片 patch 的 label 都被忽略所以 loss 主要来自动作 token 位置训练指标除了 loss常见还会看两个动作相关指标。动作 token accuracy看模型生成的动作 token 是否和真实动作 token 完全一致。预测 token 真实 token这个指标衡量的是离散动作桶分类是否准确。连续动作 L1 误差把预测 token 和真实 token 都解码回连续动作然后计算绝对误差。L1 mean(abs(predicted_action - target_action))这个指标更接近机器人控制意义上的误差。举例真实动作桶 128预测动作桶 129token accuracy 会认为错了但连续值可能只差一点点。所以 L1 误差可以补充 token accuracy 的局限。推理时如何从图片和指令得到真实动作推理时没有真实动作只有当前图片任务指令系统要输出真实机器人动作向量完整链路如下构造 prompt处理图片和文本图片变成视觉 embedding文本变成 token embedding图像 embedding 插入语言模型上下文语言模型 generate 动作 token动作 token 解码成 [-1, 1] 范围内的归一化动作用数据集统计量反归一化输出机器人真实动作为什么需要反归一化模型内部动作空间是统一的[-1, 1]但真实机器人控制空间不是。所以需要把normalized_action恢复成real_action常见反归一化公式real 0.5 * (normalized 1) * (q99 - q01) q01其中 q01 和 q99 来自训练数据统计。如果模型混合训练了多个机器人数据集就必须知道当前要使用哪套统计量。可以类比成模型说的是“标准动作语言”向右移动 0.3 个标准单位。但不同机器人 0.3 个标准单位对应的真实距离不同。反归一化就是把标准单位换算成当前机器人实际控制单位。多机器人数据集为什么需要标准化OpenVLA 通常希望利用大量机器人数据。问题是不同数据集差异很大数据集 A:图片字段叫 camera_image指令字段叫 instruction动作是 7 维数据集 B:图片字段叫 image_primary指令字段叫 language_instruction动作是 10 维数据集 C:有腕部相机有深度图gripper 表示方式也不同如果训练代码直接适配每个数据集会变得不可维护。所以系统需要一个统一数据协议。抽象后的样本应该长这样sample:image_primary:主视角 RGB 图像language_instruction:自然语言任务action:统一语义下的动作向量dataset_name:样本来自哪个数据集statistics:动作归一化和反归一化需要的统计量数据标准化做什么数据标准化主要解决四类问题。字段名统一把不同数据集中的图像、语言、动作字段映射到统一名字。workspace_image / camera_rgb / image - image_primarytask_text / instruction / language - language_instruction动作语义统一不同机器人动作可能含义不同。例如前 3 维是末端平移中间 3 维是末端旋转最后 1 维是夹爪系统需要明确每个维度的语义尤其是哪些维度是相对动作哪些维度是绝对动作哪些维度需要归一化哪些维度不该归一化夹爪维度经常比较特殊因为它可能是二值开合不适合像连续位移那样归一化。数值尺度统一动作要归一化到模型内部空间。这一步让多机器人数据能共享一个动作 token 体系。数据混合采样多数据集训练时需要决定每个数据集被采样的比例。抽象地说mixed_dataset sample_from([dataset_A with weight_A,dataset_B with weight_B,dataset_C with weight_C,])这样模型训练时会看到来自多个机器人和任务的数据。两种训练范式全量训练与参数高效微调OpenVLA 类系统一般支持两类训练。全量训练全量训练会更新大量参数可能包括视觉编码器投影器语言模型优点表达能力强适合大规模预训练能深度适配机器人动作生成缺点显存需求高训练成本高分布式工程复杂常见策略还会选择冻结一部分模块冻结视觉编码器只训练投影器和语言模型冻结大部分语言模型只训练最后几层全部解冻端到端训练参数高效微调参数高效微调不直接更新全部大模型参数而是在模型中插入少量可训练参数。常见方式是 LoRA。它的直觉是不改整本书只在关键页贴便签便签数量很少但足以让模型适配新任务。优点显存需求低训练更快适合新任务、新机器人快速适配缺点适配能力可能不如全量训练如果新机器人分布差异太大可能不够两种训练范式的共同核心不变图像 指令 - 动作 token部署服务的抽象链路部署时OpenVLA 通常作为一个远程策略服务。机器人控制端发送当前相机图片当前任务指令可选的动作反归一化配置名模型服务返回动作向量抽象服务流程接收请求解码图片构造 prompt图片和文本预处理模型生成动作 tokentoken 解码成动作动作反归一化返回动作这个服务层本身很薄。真正重要的是processor multimodal model action decoder unnormalizer核心伪代码下面伪代码只表达 OpenVLA 的核心机制不绑定任何具体工程结构。每个关键步骤都包含中文注释。伪代码 1动作归一化def normalize_action(raw_action, statistics, mask):# raw_action:# 真实机器人动作例如机械臂末端位移、旋转、夹爪开合。## statistics:# 从训练数据中统计出的每个动作维度范围。# 这里用 q01/q99 表示 1% 和 99% 分位数用于减少异常值影响。## mask:# 指示哪些动作维度需要归一化。# 例如夹爪维度可能已经是二值信号不一定适合连续归一化。low statistics.q01 high statistics.q99 # 将动作线性映射到 [-1, 1]。 # 当 raw_action low 时normalized 约等于 -1 # 当 raw_action high 时normalized 约等于 1。 normalized 2 * (raw_action - low) / (high - low 1e-8) - 1 # 将极端值裁剪到 [-1, 1]避免离散化时越界。 normalized clip(normalized, -1, 1) # 对不需要归一化的维度保留原始值。 # 这样可以避免破坏夹爪等特殊动作维度的语义。 normalized where(mask, normalized, raw_action) return normalized伪代码 2连续动作编码为动作 tokendef encode_action_as_tokens(normalized_action, tokenizer, n_bins256):# normalized_action:# 已归一化到 [-1, 1] 附近的动作向量。## tokenizer:# 语言模型使用的 tokenizer。## n_bins:# 每个动作维度被切成多少个离散桶。# 创建离散桶边界。 # 例如 n_bins256 时[-1, 1] 会被切成 256 个刻度。 bin_edges linspace(-1, 1, n_bins) # 先裁剪确保所有动作值都在可表示范围内。 clipped_action clip(normalized_action, -1, 1) # 找到每个动作维度落在哪个桶中。 # 这一步把连续控制值变成离散类别编号。 bin_ids digitize(clipped_action, bin_edges) # 将离散桶编号映射到语言模型词表中的动作 token id。 # 一种常见做法是使用词表尾部 token 作为动作 token。 action_token_ids tokenizer.vocab_size - bin_ids # 返回 token id 序列。 # 每个动作维度对应一个 token。 return action_token_ids伪代码 3构造训练样本def build_training_example(image, instruction, raw_action, statistics, tokenizer):# image:# 当前机器人相机图像。## instruction:# 人类语言任务例如“把杯子放到盘子上”。## raw_action:# 专家示范中的真实动作。## statistics:# 用于动作归一化的数据集统计量。# 1. 将真实动作归一化到标准动作空间。 # 这样不同机器人或不同数据集的动作可以共用一套离散 token 体系。 normalized_action normalize_action( raw_action, statisticsstatistics.action, maskstatistics.action_mask, ) # 2. 将归一化动作编码成动作 token。 # 从这里开始机器人动作就被转成语言模型可以预测的 token 序列。 action_token_ids encode_action_as_tokens( normalized_action, tokenizertokenizer, ) # 3. 构造自然语言 prompt。 # 这一步把机器人控制任务包装成语言模型熟悉的问答格式。 prompt In: What action should the robot take to instruction.lower() ?\nOut: # 4. 将 prompt 编码为文本 token。 # 这些 token 提供任务语义但不作为训练目标。 prompt_token_ids tokenizer.encode(prompt) # 5. 拼接完整输入。 # 语言模型看到 prompt并在后续位置学习生成动作 token。 input_ids concat(prompt_token_ids, action_token_ids, [EOS_TOKEN]) # 6. 构造 labels。 # labels 与 input_ids 对齐但 prompt 部分设置为 IGNORE_INDEX。 labels copy(input_ids) # 7. 屏蔽 prompt 部分。 # 这表示模型不需要学习复述问题只需要学习预测动作。 labels[0 : len(prompt_token_ids)] IGNORE_INDEX # 8. 图像预处理。 # 将原始 RGB 图像 resize、归一化并变成视觉编码器可接收的张量。 pixel_values preprocess_image(image) # 9. 返回一个训练样本。 # 后续 batch collator 会负责 padding 和堆叠。 return { pixel_values: pixel_values, input_ids: input_ids, labels: labels, }伪代码 4多模态前向传播def multimodal_forward(model, pixel_values, input_ids, attention_mask, labels):# pixel_values:# 已预处理的图片张量。## input_ids:# prompt token 动作 token 的输入序列。## labels:# prompt 部分被 IGNORE_INDEX 屏蔽只监督动作 token。# 1. 图片进入视觉编码器得到 patch 级视觉特征。 # 每个 patch 表示图片中的一个局部区域。 image_patch_features model.vision_encoder(pixel_values) # 2. 用投影器把视觉特征转换到语言模型 hidden size。 # 只有维度一致图像 embedding 才能和文本 embedding 拼接。 image_embeddings model.projector(image_patch_features) # 3. 文本 token id 进入语言模型 embedding table。 # 这得到 prompt 和动作 token 的文本 embedding。 text_embeddings model.language_model.embed_tokens(input_ids) # 4. 将图像 embedding 插入文本 embedding 序列。 # 常见方式是插到 BOS 之后。 multimodal_embeddings concat( text_embeddings[:, :1, :], image_embeddings, text_embeddings[:, 1:, :], dim1, ) # 5. 为图像 patch 构造 attention mask。 # 图像 patch 是有效上下文所以 mask 为 1。 image_attention_mask ones(batch_size, num_image_patches) # 6. 拼接新的 attention mask。 # 新 mask 必须和 multimodal_embeddings 的序列长度一致。 multimodal_attention_mask concat( attention_mask[:, :1], image_attention_mask, attention_mask[:, 1:], dim1, ) # 7. 为图像 patch 构造 label。 # 图像 patch 不是要生成的 token所以它们的 label 必须忽略。 image_labels full( shape(batch_size, num_image_patches), valueIGNORE_INDEX, ) # 8. 拼接新的 labels。 # 最终 loss 只会在动作 token 位置产生。 multimodal_labels concat( labels[:, :1], image_labels, labels[:, 1:], dim1, ) # 9. 调用语言模型。 # 因为序列中包含图像 embedding它们没有 token id # 所以这里传入 inputs_embeds而不是只传 input_ids。 output model.language_model( inputs_embedsmultimodal_embeddings, attention_maskmultimodal_attention_mask, labelsmultimodal_labels, ) # 10. 输出包含 loss 和 logits。 # loss 用于训练logits 可用于计算动作 token accuracy。 return output伪代码 5训练循环def train_openvla(model, dataset, optimizer):# model:# 由视觉编码器、投影器、语言模型组成的多模态动作生成模型。## dataset:# 已标准化的机器人数据流每条样本包含图片、指令和专家动作。for raw_batch in dataset: # 1. 将原始机器人数据转成模型训练 batch。 # 包括动作归一化、动作 token 化、prompt 构造、图片预处理和 label mask。 batch build_batch(raw_batch) # 2. 多模态前向传播。 # 模型会把图片 embedding 插入语言模型上下文并预测动作 token。 output multimodal_forward( modelmodel, pixel_valuesbatch.pixel_values, input_idsbatch.input_ids, attention_maskbatch.attention_mask, labelsbatch.labels, ) # 3. 读取训练 loss。 # 因为 prompt 和图像 patch 都被忽略所以这个 loss 主要来自动作 token。 loss output.loss # 4. 反向传播。 # 梯度会更新所有被设置为可训练的参数。 loss.backward() # 5. 可选梯度裁剪。 # 大模型训练中梯度裁剪能减少不稳定更新。 clip_grad_norm(model.trainable_parameters) # 6. 参数更新。 optimizer.step() # 7. 清空梯度准备下一步训练。 optimizer.zero_grad() # 8. 计算动作 token 准确率。 # 这是离散 token 层面的监控指标。 token_accuracy compute_action_token_accuracy( logitsoutput.logits, labelsbatch.labels, ) # 9. 计算连续动作误差。 # 把预测 token 解码回连续动作再和真实动作比较。 action_l1 compute_continuous_action_l1( logitsoutput.logits, labelsbatch.labels, ) # 10. 记录指标。 log({ loss: loss, token_accuracy: token_accuracy, action_l1: action_l1, })伪代码 6动作 token 解码为归一化动作def decode_tokens_to_normalized_action(action_token_ids, tokenizer, n_bins256):# action_token_ids:# 模型生成的动作 token id 序列。## tokenizer:# 用于知道词表大小从而反推出桶编号。# 1. 重建桶中心。 # 每个桶中心代表该离散 token 对应的连续动作近似值。 bin_edges linspace(-1, 1, n_bins) bin_centers (bin_edges[:-1] bin_edges[1:]) / 2 # 2. 根据 token id 反推桶编号。 # 编码时使用词表尾部 token因此解码时反向计算。 bin_ids tokenizer.vocab_size - action_token_ids # 3. 将桶编号转换为数组索引。 # 通常 digitize 的桶编号从 1 开始而数组索引从 0 开始。 bin_indices bin_ids - 1 # 4. 裁剪索引避免边界情况下越界。 bin_indices clip(bin_indices, 0, len(bin_centers) - 1) # 5. 查表得到归一化动作。 # 此时动作仍在模型内部标准空间不可直接发给机器人。 normalized_action bin_centers[bin_indices] return normalized_action伪代码 7动作反归一化def unnormalize_action(normalized_action, statistics, mask):# normalized_action:# 模型输出并解码后的 [-1, 1] 动作。## statistics:# 当前机器人或当前数据集对应的动作统计量。## mask:# 指示哪些维度需要从标准空间还原到真实动作空间。low statistics.q01 high statistics.q99 # 将 [-1, 1] 线性映射回真实动作范围。 real_action 0.5 * (normalized_action 1) * (high - low) low # 对不需要反归一化的维度保留 normalized_action。 # 这通常用于夹爪等特殊维度。 real_action where(mask, real_action, normalized_action) return real_action伪代码 8推理流程def predict_action(model, image, instruction, statistics_key):# model:# 已训练好的 OpenVLA 风格模型。## image:# 当前相机图像。## instruction:# 当前任务语言。## statistics_key:# 指定当前应使用哪套动作统计量。# 多机器人或多数据集模型必须知道这一点。# 1. 构造 prompt。 # 使用和训练时一致的问答格式避免训练/推理分布不一致。 prompt In: What action should the robot take to instruction.lower() ?\nOut: # 2. 编码文本。 # 得到 prompt token ids 和 attention mask。 text_inputs tokenize(prompt) # 3. 预处理图片。 # 得到视觉编码器需要的 pixel_values。 pixel_values preprocess_image(image) # 4. 根据当前动作空间维度决定生成多少个 token。 # 7 维动作生成 7 个 token10 维动作生成 10 个 token。 action_dim model.get_action_dim(statistics_key) # 5. 自回归生成动作 token。 # 这里通常使用 deterministic decoding例如 do_sampleFalse。 generated_token_ids model.generate( input_idstext_inputs.input_ids, attention_masktext_inputs.attention_mask, pixel_valuespixel_values, max_new_tokensaction_dim, ) # 6. 取最后 action_dim 个 token。 # 这些就是模型为每个动作维度生成的动作 token。 action_token_ids generated_token_ids[-action_dim:] # 7. 将动作 token 解码为归一化动作。 normalized_action decode_tokens_to_normalized_action( action_token_ids, tokenizermodel.tokenizer, ) # 8. 读取当前机器人或数据集的统计量。 statistics model.action_statistics[statistics_key] # 9. 将归一化动作还原为真实机器人动作。 real_action unnormalize_action( normalized_action, statisticsstatistics, maskstatistics.action_mask, ) # 10. 返回动作给机器人控制器。 return real_action伪代码 9多数据集混合训练数据流def build_mixed_robot_dataset(datasets, sampling_weights):# datasets:# 多个机器人数据集。## sampling_weights:# 每个数据集被采样的相对概率。standardized_datasets [] for dataset in datasets: # 1. 字段标准化。 # 将不同数据集的图片、语言、动作字段映射到统一协议。 dataset standardize_fields(dataset) # 2. 动作语义标准化。 # 确保动作维度含义一致例如前几维是位移、后几维是旋转和夹爪。 dataset standardize_action_semantics(dataset) # 3. 计算动作统计量。 # 这些统计量用于训练时归一化以及推理时反归一化。 dataset.statistics compute_action_statistics(dataset) # 4. 过滤无效样本。 # 例如没有语言指令、动作异常、图片缺失的样本。 dataset filter_invalid_examples(dataset) # 5. 将轨迹展开成训练帧。 # 如果模型预测单步动作每个时间步都可以变成一条训练样本。 dataset flatten_trajectories_to_frames(dataset) standardized_datasets.append(dataset) # 6. 按权重混合采样。 # 训练时模型会以指定比例看到不同机器人和任务的数据。 mixed_dataset weighted_sample( standardized_datasets, weightssampling_weights, ) # 7. 打乱样本。 # 避免训练连续看到同一轨迹或同一数据集的局部相关样本。 mixed_dataset shuffle(mixed_dataset) return mixed_dataset关键设计取舍为什么不直接回归动作直接回归动作当然可以但 OpenVLA 选择 token 化动作有几个好处复用语言模型原生输出头。复用自回归生成机制。统一文本生成和动作生成形式。让动作预测变成分类问题训练稳定性往往更好。代价是连续动作会有量化误差。推理需要 token 解码和反归一化。动作维度越多需要生成的 token 越多。为什么图片要作为 embedding 插入而不是生成图像描述图像描述会丢失细节。机器人控制需要非常具体的空间信息杯子离夹爪多远物体在图像哪个区域夹爪是否已经对齐目标物是否被遮挡这些信息很难通过一句 caption 完整表达。直接插入视觉 embedding 可以保留更多空间和视觉细节。为什么只监督动作 token如果 prompt 也参与 loss模型会花能力学习复述固定模板。而机器人控制真正需要的是根据图像和任务预测动作所以 prompt 和图像 patch 位置都应该被忽略loss 集中到动作 token。为什么需要数据集统计量模型内部只能统一学习标准动作空间。但真实机器人动作尺度因机器人和数据集而异。数据集统计量承担了单位换算功能标准动作单位 - 当前机器人真实动作单位没有这一步模型输出的动作 token 即使语义正确也可能无法驱动真实机器人。常见误解误解一OpenVLA 是普通动作回归模型不是。它的输出首先是 token。连续动作是 token 解码和反归一化之后得到的。误解二动作 token 是自然语言词语不是。动作 token 通常不可读。它们只是借用了语言模型词表中的 token id 来表示动作桶。误解三图片先被描述成文字再给语言模型不是。图片通过视觉编码器变成 embedding然后直接插入语言模型上下文。误解四只要 token accuracy 高机器人效果就一定好不一定。机器人效果还取决于动作反归一化是否正确图像预处理是否和训练一致控制频率是否匹配机器人硬件误差任务是否需要长期闭环纠错误解五多数据集训练只需要把数据拼起来不够。必须统一字段格式动作语义动作尺度采样权重统计量保存否则模型学到的是混乱的动作语言。如果要改造系统应优先理解哪些抽象接口新增机器人数据集你需要定义原始图像字段 - 标准图像字段原始语言字段 - 标准语言字段原始动作字段 - 标准动作向量动作维度语义哪些维度需要归一化数据集统计量如何计算改动作表示你需要同时考虑动作归一化方式离散桶数量桶到 token 的映射token 到连续动作的解码反归一化公式训练指标如何计算推理生成 token 数量这些必须保持一致。只改编码不改解码系统会失效。改视觉编码器你需要保证图片预处理匹配视觉编码器视觉输出是 patch 级特征视觉特征维度已知投影器能映射到语言模型 hidden size图像 patch 数量和 attention mask / label mask 对齐改语言模型你需要注意tokenizer 词表大小特殊 token 规则prompt 格式上下文长度动作 token 是否仍能映射到词表尾部generate 行为是否和训练格式一致语言模型变了动作 token 的含义也可能要重新校准。支持动作 chunk如果想一次生成多个未来动作而不是单步动作需要重新定义动作序列如何展开成 tokenmax_new_tokens 应该是多少解码后如何 reshape 成 [chunk_len, action_dim]训练 labels 如何屏蔽机器人控制器如何执行动作 chunk最终总结OpenVLA 的核心不是某个具体工程实现而是一套建模思想把不同机器人动作统一到标准动作空间把连续动作离散成动作桶把动作桶映射成语言模型 token把图片变成视觉 embedding把视觉 embedding 插入语言模型上下文让语言模型在图像和指令条件下生成动作 token把动作 token 解码成归一化动作用数据集统计量反归一化成真实机器人动作用最短的话说OpenVLA 把机器人控制变成了多模态 token 生成。它的强大之处在于机器人动作不再被看作一个孤立的回归头输出而是被纳入了语言模型统一的生成范式。它的关键工程闭环是