前言
随着机器人动作策略预测逐渐成熟,比如 ACT、扩散策略等,为了让机器人具备更好的泛化能力,主流途径之一是利用预训练大语言模型的广泛知识,并增加一个 policy head。早期的模型结构相对简单,例如使用 LSTM 或 MLP(如 RoboFlamingo),随后出现了更多成熟的专用 VLA 模型,如 OpenVLA。
近期 π0 的出现展示了通用机器人控制的新方向,它首次用同一套策略算法操作不同机械臂,这种「预训练 - 微调」模式正成为核心发展方向。虽然 π0 开源较晚,但 OpenVLA 作为早期的重要开源项目,其架构思路依然极具参考价值。本文将深入剖析 OpenVLA 的模型架构、训练过程及源码实现。
OpenVLA:RT-2 的开源替代方案
1. 模型背景与定位
2024 年 6 月,斯坦福大学、UC Berkeley、Google DeepMind 等机构的研究者推出了 OpenVLA。其论文《OpenVLA: An Open-Source Vision-Language-Action Model》详细阐述了这一工作。
当前机器人策略学习存在关键弱点:难以超越训练数据进行泛化。面对场景干扰物或新颖物体时,现有策略往往缺乏鲁棒性。然而,CLIP、SigLIP 和 Llama 2 等基础模型凭借互联网规模的预训练数据,具备了强大的泛化能力。OpenVLA 的核心目标正是利用这些视觉和语言基础模型,构建能泛化到超出训练数据的机器人策略。
与闭源的 RT-2 相比,OpenVLA 解决了两个痛点:
- 透明度低:现有模型架构、训练流程细节有限。
- 部署困难:缺乏在消费级硬件上适配新机器人的最佳实践。
为此,OpenVLA 采用端到端方法,直接对 VLM 进行微调,将机器人动作视为语言模型词汇表中的 token 来生成。实验表明,这种简单可扩展的流程提升了性能和泛化能力。
2. 模型架构设计
OpenVLA 基于 Prismatic-7B VLM 构建,该架构包含三个主要部分:
- 视觉编码器:由 SigLIP 和 DinoV2 组成。Prismatic 使用了两部分视觉编码器,输入图像块分别通过这两个编码器,随后特征向量按通道拼接。添加 DinoV2 有助于改善空间推理,这对机器人控制尤为重要。不过,后续研究如 π0 仅使用 SigLIP 也取得了不错效果,说明这并非绝对必要。
- 投影器:两层小型 MLP,将视觉特征映射到语言模型输入空间。
- 语言模型骨干:70 亿参数的 Llama 2。
作者选择 Prismatic 而非 LLaVA 或 IDEFICS-1,主要是因为融合 SigLIP-DinoV2 主干提供了改进的空间推理能力,且代码库模块化程度高。
关于训练细节:
- 分辨率:224×224 像素。384×384 虽未提升性能,但训练时间增加三倍。
- 轮次:VLA 训练通常需要更多轮次,实际性能直到动作 token 准确率超过 95% 才趋于稳定,最终运行了 27 个轮次。
- 学习率:固定学习率 2e-5 表现最佳,无需预热。
3. 训练与数据处理
3.1 动作离散化
为了训练 OpenVLA,作者将动作预测表述为'视觉 - 语言'任务。连续的机器人动作被映射到语言模型的分词器使用的离散 token。
具体做法是参考 Brohan 等人的方法,将每个动作维度离散化为 256 个区间中的一个。由于 Llama 分词器仅为微调预留了 100 个特殊 token,不足以容纳 256 个动作 token,作者选择覆盖 Llama 分词器词表中频率最低的 256 个 token(即最后 256 个)。
对于每个动作维度,区间宽度设置为均匀划分训练数据中动作的第 1st 和第 99th 分位数之间。使用分位数而非最小 - 最大边界,可以忽略异常动作,避免离散化区间过大降低有效精度。
3.2 数据集整理
训练基于 Open X-Embodiment 数据集,包含超过 70 个单独的机器人数据集,总计 200 万条轨迹。为了训练可行,进行了以下整理:
- 输入输出一致:限制仅包含至少有一个第三人称摄像机的操作数据集,并使用单臂末端执行器控制。
- 平衡 Embodiments:利用 Octo 的数据混合权重处理筛选后的数据集,降低多样性较低的数据集权重,增大任务和场景多样性大的数据集权重。
此外,尝试整合 DROID 数据集,但在训练后期因动作 token 准确率较低而移除,以免影响模型质量。
3.3 基础设施
模型在一个由 64 个 A100 GPU 组成的集群上训练了 14 天。推理过程中,OpenVLA 加载为 bfloat16 精度需要 15GB 显存,在一块 NVIDIA RTX 4090 上以约 6Hz 的速度运行。通过量化可进一步减少内存占用而不影响性能。
4. 实验评估
4.1 与 RT-2 对比
在 BridgeData V2 和 Google Robot 平台测试中,OpenVLA 在大多数任务中表现最强。尽管规模比 RT-2-X 小一个数量级(7B vs 55B),但在通用策略中具有最高的总体成功率。RT-2-X 在语义泛化任务中略胜一筹,这与其更大规模的互联网预训练数据有关。
值得注意的是,原始 BridgeData V2 数据集包含许多全零动作的转换,导致模型频繁预测全零并在评估中卡住。OpenVLA 通过过滤掉每个演示中的第一个转换缓解了这一问题;RT-2-X 则采用了查询第二最可能动作的变通方法。
4.2 与 Diffusion Policy 对比
在单一指令任务中,Diffusion Policy 显示出更平滑和精确的轨迹。但在涉及多个对象并需要语言调节的多样化任务中,预训练的通用策略(如 OpenVLA)表现更佳。这表明大规模机器人预训练对语言基础至关重要的任务有显著帮助。
4.3 参数高效微调
针对 LoRA 微调的实验发现:
- 仅微调最后一层或冻结视觉编码器导致性能不佳。
- Sandwich fine-tuning(解冻视觉编码器、token 嵌入矩阵和最后一层)消耗更少显存但性能不如 LoRA。
- LoRA 在性能和训练内存消耗之间实现了最佳平衡,仅需微调 1.4% 的参数即可匹配完整微调的性能。建议使用默认 rank r = 32,可在单个 A100 上 10-15 小时内完成微调。
Prismatic VLM 设计空间探索
OpenVLA 的基石是 Prismatic VLM。2024 年 2 月,相关研究者发布了《Prismatic VLMs: Investigating the Design Space of Visually-Conditioned Language Models》,探索了四个关键设计轴:优化过程、图像处理、语言模型及扩展属性。
1. 架构与训练
Prismatic 采用通用的 VLM 架构:视觉骨干提取 patch 特征,投影器映射到 LM 嵌入空间,LLM 生成输出。训练使用 LLaVa v1.5 数据混合集,包含约 100 万条图文对和纯文本样本。
2. 关键设计洞察
- 多阶段训练:现有 VLM 常采用两阶段训练(对齐 + 微调)。Prismatic 发现单阶段训练在不影响性能的情况下可减少 20-25% 计算成本。
- 视觉表示:融合来自不同骨干的特征(如 CLIP 和 DINOv2)能提高性能。SigLIP 结合 DinoV2 在各方面均优于单一骨干。
- 图像处理:对于 CLIP,"naive resize"(拉伸)方案表现最佳;对于 SigLIP,"letterbox padding" 与 "naive resize" 相似。高分辨率(336px 或 384px)带来显著提升,但代价是计算复杂度增加。
- 语言模型:基础语言模型(如 Llama-2)与指令微调模型(如 Vicuña)在性能上无显著差异,但指令微调模型更易产生冗长响应和幻觉。
- 扩展属性:两轮训练比一轮训练有显著改进,增加多样数据和延长训练时间均能提升性能。
源码结构简析
OpenVLA 代码库是一个模块化的 PyTorch 实现,支持从单 GPU 微调到多节点集群训练。
核心目录
vla-scripts/:训练、微调及部署脚本。prismatic/:模型加载、训练、数据预处理工具包。
动作分词器实现
在 prismatic/vla/action_tokenizer.py 中,ActionTokenizer 类负责连续动作与离散 token 的转换。
初始化时,创建均匀分布的区间并计算中心值:
def __init__(self, tokenizer: PreTrainedTokenizerBase, bins: int = 256, min_action: float = -1, max_action: float = 1) -> None:
self.tokenizer, self.n_bins, self.min_action, self.max_action = tokenizer, bins, min_action, max_action
# 创建均匀分布的区间
self.bins = np.linspace(min_action, max_action, self.n_bins)
self.bin_centers = (self.bins[:-1] + self.bins[1:]) / 2.0
# 假设总是覆盖词汇表的最后 n_bins 个 token
self.action_token_begin_idx = int(self.tokenizer.vocab_size - (self.n_bins + 1))
调用时,将动作裁剪并离散化为词汇表中最后的 n_bins 个 token:
def __call__(self, action: np.ndarray) -> Union[str, List[str]]:
action = np.clip(action, a_min=float(self.min_action), a_max=float(self.max_action))
discretized_action = np.digitize(action, self.bins)
if len(discretized_action.shape) == 1:
return self.tokenizer.decode(list(self.tokenizer.vocab_size - discretized_action))
else:
return self.tokenizer.batch_decode((self.tokenizer.vocab_size - discretized_action).tolist())
解码时,将 token ID 转换回连续动作值:
def decode_token_ids_to_actions(self, action_token_ids: np.ndarray) -> np.ndarray:
# 映射到新索引范围
discretized_actions = self.tokenizer.vocab_size - action_token_ids
# 限制在有效区间
discretized_actions = np.clip(discretized_actions - 1, a_min=0, a_max=self.bin_centers.shape[0] - 1)
return self.bin_centers[discretized_actions]
这套机制确保了动作预测能够无缝融入标准 LLM 的训练和推理流程。


