RoboTwin 2.0: A Scalable Data Generator and Benchmark with Strong Domain Randomization for Robust Bimanual Robotic Manipulation
Paper: arXiv:2506.18088 Code: RoboTwin-Platform/RoboTwin Code reference:
main@0aeea2d6(2026-04-19)
1. Motivation(研究动机)
RoboTwin 2.0 解决的是双臂机器人数据和评测里一个很具体但长期被低估的问题:只把仿真做成“干净桌面 + 少量对象 + 固定机器人形态”,并不能训练出能在真实环境中稳定执行双臂任务的策略。双臂操作本身就比单臂 pick-and-place 更脆弱,因为它经常要求两只机械臂在时间、空间和接触上协同,例如传递物体、双手搬运、压住再操作、或者把一个物体放到另一个物体上。策略如果只见过固定背景、固定光照、固定桌高、固定语言描述和固定夹爪可达性,就很容易把这些非本质因素当成任务线索,到了真实世界或新的机器人平台就会失败。
论文指出现有合成数据集主要有三类不足。第一,自动生成任务代码时缺少可靠的质量控制:LLM 或手写脚本能生成很多候选程序,但如果没有在仿真里闭环执行、记录失败原因、再用视觉诊断修复,生成的专家轨迹会混入抓取失败、放置失败、左右臂选择错误、不可执行代码等噪声。第二,很多仿真环境过于干净,domain randomization 只停留在浅层纹理或少量物体扰动,无法覆盖真实桌面中的杂物、背景、光照、桌高和语言表达变化。第三,双臂机器人之间的 embodiment 差异很大:Piper 这类低自由度平台更依赖侧向抓取,Franka 或 UR5 这类高自由度机械臂可以更从容地做 top-down grasp;如果数据生成不显式考虑物体 affordance 与机器人可达性,就会把某个 embodiment 的可行轨迹错误地当成通用轨迹。
这篇论文的价值不只是“又做了一个机器人 benchmark”,而是把机器人数据生产线、对象资产库、强随机化、跨 embodiment 适配和真实世界验证串成一个统一系统。作者构建 RoboTwin-OD:731 个对象实例、147 个类别,并标注语义、抓取点、功能点、对象轴、语言描述等操作相关信息;再用 MLLM + simulation-in-the-loop 生成专家级任务代码;最后把这些数据用于 VLA、diffusion policy、ACT、DP3 等策略的训练和评测。换句话说,它把数据集从静态轨迹集合扩展成一个可扩展的数据生成器和评测平台。
Figure 1 解读:这张 overview 强调 RoboTwin 2.0 的四个尺度:50 个双臂任务、731 个对象、5 种机器人 embodiment,以及从仿真到真实世界的训练/评测闭环。图中的 “Only Real-World 9% / Zero-shot 30% / Few-shot 42%” 是论文想传达的核心现象:真实数据很少时,强随机化合成数据可以显著补足鲁棒性;而不是简单追求仿真里的 clean success。
从研究定位看,RoboTwin 2.0 处在 Physical AI / Robot Data & Manipulation 的核心区域。它不提出一个单一策略网络,也不声称解决所有 VLA 架构问题,而是关注“如何稳定地产生多样、可验证、可迁移的双臂专家数据”。这类工作对下游模型很关键,因为 VLA、RDT、π0 等模型的上限很大程度受数据覆盖度和评测分布决定。论文用实验说明:即便模型本身很强,如果训练数据不包含环境扰动,它们在 hard randomized setting 下仍会明显掉点;反过来,合成数据只要足够多样并具备真实世界相关性,就能在 few-shot 甚至 zero-shot 真实场景中带来可观提升。
2. Idea(核心思想)
核心思想可以概括为:把双臂机器人数据生成从“为每个任务手写脚本”改造成“对象资产库 + 技能 API + MLLM 代码生成 + 仿真执行反馈 + 强 domain randomization + embodiment-aware grasp candidates”的闭环系统。真正新颖之处不在某一个模块,而在于这些模块被设计成互相约束:对象库提供可操作的 affordance,API 限制 LLM 生成可执行程序,仿真反馈过滤和修复失败轨迹,随机化扩大训练分布,embodiment-aware 候选动作避免把高自由度机器人的抓取策略强加给低自由度平台。
RoboTwin 2.0 的方法论与传统仿真 benchmark 的区别是“生成器优先”。如果只有固定数据集,研究者只能在已有任务和场景上训练;而生成器可以按任务、机器人、对象和随机化配置重新采样轨迹。论文的实验也围绕这个点展开:第 4.1 节评估代码生成闭环是否更容易产生成功专家程序;第 4.2 节评估 adaptive grasping 是否提升跨 embodiment 数据收集成功率;第 4.3 和 4.4 节评估 domain-randomized synthetic data 是否让策略在 unseen task、unseen background、cluttered scene 和真实世界中更鲁棒;第 4.5 节把 50 个任务作为 benchmark 展示不同策略在 Easy/Hard 条件下的差距。
这篇论文的另一个重要判断是:机器人策略的鲁棒性不是只靠更大的模型自然出现的。Table 5 中 RDT 和 π0 是 pretrained VLA,但在 Hard 条件下仍分别从 Easy 平均值下降 20.8 和 30.1 个百分点;ACT、DP、DP3 这类非大规模预训练策略在 Hard 下更差。作者据此强调,预训练提供了动作和视觉语言先验,但没有覆盖桌面杂物、光照、背景、桌高、语言指令多样性和 embodiment 差异时,仍然会产生明显 domain shift。RoboTwin 2.0 的核心贡献就是把这些扰动系统化地注入到专家数据和评测协议里。
从系统设计角度看,RoboTwin 2.0 也避免了两个常见陷阱。第一个陷阱是只强调 LLM 自动写代码,却不定义可验证的专家质量;RoboTwin 2.0 把代码生成放进仿真执行循环,用 success rate、错误日志和图像观察共同决定候选程序是否可用。第二个陷阱是只强调 domain randomization,却不控制语义一致性;RoboTwin 2.0 的随机化仍然依附于对象 affordance、碰撞约束和任务无歧义性,因此扰动的是背景因素,而不是把任务目标本身随机坏掉。这种思路让“数据生成”和“benchmark 评测”共享同一套环境变量:训练时可以逐步增加扰动强度,评测时也能明确区分 clean Easy 与 randomized Hard,从而把鲁棒性问题具体化。
因此,RoboTwin 2.0 更像是一个面向 VLA 与双臂策略的压力测试平台。它要求模型同时处理四种泛化:对象泛化、场景泛化、语言泛化和 embodiment 泛化。任何一个维度缺失,都可能在 Easy 设置里被掩盖,却会在 Hard 或真实世界设置里暴露。例如 DP3 在 Easy 上受益于高质量点云,但在 Hard 中迅速掉到很低;RDT/π0 有大规模预训练先验,却仍然需要 domain-randomized synthetic data 来补齐环境扰动。这也是为什么本文比单纯的“合成数据更多”更有参考价值:它给出了一个可以诊断数据覆盖缺口的生成-评测闭环。
3. Method(方法)
3.1 总体框架:从语言任务到随机化专家轨迹
Figure 2 解读:整体 pipeline 从语言描述开始,经由 Auto Expert Data Collection Code Gen 生成任务执行代码;代码运行在 RoboTwin-OD 对象库和 API Library 支撑的仿真环境中;执行时会产生 error feedback 与 observation retrieval,MLLM 根据这些反馈修复程序。最终得到的专家轨迹会叠加 cluttered table、background、light、tabletop height、instruction 等随机化因素,用于训练鲁棒机器人策略。
整体流程可以拆成三层。第一层是任务程序生成层:给定任务名和自然语言描述,系统把对象列表、可用 API、示例函数和约束说明拼进 prompt,要求模型生成继承具体任务环境的 Python 类,例如 gpt_handover_block(handover_block) 并实现 play_once()。第二层是仿真验证层:生成程序不是直接接受,而是在仿真中多次执行,记录 success rate、错误消息、失败类型和运行轨迹;失败时会把错误日志和视觉观察反馈重新注入 prompt。第三层是数据多样化层:成功程序在不同 seed、对象扰动、背景、光照、桌高、杂物和语言指令下重放,得到多样化专家轨迹。
代码仓库与论文描述基本对应:code_gen/task_generation_mm.py 实现 multimodal-feedback 版本的生成循环,code_gen/gpt_agent.py 封装 DeepSeek/OpenAI 风格的 chat completions 调用,code_gen/observation_agent.py 负责插入观察点、收集 camera_images 并用视觉模型分析失败,script/collect_data.py 与 collect_data.sh 负责按 task config 搜 seed、执行 play_once()、检查 check_success() 并保存成功轨迹。实际公开代码中还有大量 policy baseline 目录,说明论文不是只发布 paper-level demo,而是把数据生成、采集、评测和多策略接口一起开放。
3.2 自动专家代码生成:LLM 受限于 API,仿真负责验收
Figure 3 解读:这张图展示 expert code generation pipeline。左侧输入包括 task name、language instruction、API list、function examples 和 constraint specification;中间 code-generation agent 生成候选代码;右侧仿真执行后产生 structured log 和 visual observation,VLM observer 根据图像序列判断是否抓错、放错、未完成或违反约束,反馈再回到 code agent。它的关键是把 MLLM 的开放生成限制在机器人 API 空间里,并用仿真结果做硬验收。
代码层面的流程是:generate_code() 首次生成时会写出一个空的 envs_gen/gpt_<task>.py 模板,然后把 BASIC_INFO、task description、actor list、available API、function example 和 current code 拼成 prompt。失败重试时,prompt 中会加入 Last Error Message;在 multimodal 版本里还会加入 Visual Observation Feedback。main() 默认尝试 generate_num = 5 次,阈值 success_threshold = 0.5,每次生成后调用测试函数执行多次,保存 best_code 和 best_success_rate。论文实验中用于度量的 ASR、Top5-ASR、CR-Iter、Token,正对应这类多候选、多轮修复的程序生成设置。
这里的设计直觉是:LLM 很擅长把自然语言任务转成程序结构,但不擅长在没有物理反馈时判断一个抓取/放置序列是否真正成功。仿真执行相当于把程序生成问题变成闭环控制问题:代码只是 proposal,success rate 和错误日志才是 acceptance signal。视觉观察进一步弥补纯错误日志的不足,因为有些失败不是异常,而是“执行了但物体没到位”“左臂抓到了但右臂接不住”“放置姿态不满足任务语义”。这就是论文中 MM FB 能提升 ASR 和 Top5-ASR 的原因。
3.3 RoboTwin-OD:对象库不只是 mesh,而是 manipulation annotation
Figure 7 解读:RoboTwin-OD 的对象数据包含丰富操作标注:grasp point、object axis、functional point,以及多样语言描述。以鞋子为例,系统不仅知道它是 shoe/sneaker,也知道哪些点适合抓、哪些方向可操作、不同语言表达如何指向同一对象。这些标注让 LLM 生成的程序可以调用具体函数点,而不是只在视觉 mesh 上盲目采样。
论文报告 RoboTwin-OD 包含 731 个对象实例、147 个类别。这个对象库的关键不是规模本身,而是语义与操作属性被组织成可被代码生成器使用的输入。description/task_instruction/*.json 给每个任务提供结构化描述和 actor list,例如 handover_block 里包括 self.box、self.target_box、self.block_middle_pose 以及每个对象的功能描述;envs/*.py 则定义具体任务环境、对象初始化和 play_once()。这种“描述文件 + 环境类 + API prompt”的结构让任务生成器可以在多任务之间复用同一套机制。
Figure 8 解读:这张图汇总了 50 个双臂 manipulation tasks,覆盖堆叠、点击、旋转、搬运、开合、放置、悬挂、递交等任务类型。它说明 RoboTwin 2.0 的 benchmark 难点不只是对象数量,而是任务语义和双臂协作模式的多样性;很多任务需要时序协作和中间姿态,而不是一次性抓放。
3.4 强 domain randomization:把真实世界的非任务因素系统化
Figure 4 解读:图 4 可视化了 randomization 的五个主要轴:cluttered table、lighting、table height、background texture 和 language instruction。右侧纹理库来自 1,000 个表面描述经 Stable Diffusion v2 生成 20,000 个样本,再人工过滤到 11,000 个高质量纹理。这样的随机化目标不是让画面“更花”,而是让策略不再依赖单一桌面、单一背景和单一光照。
代码中的 task_config/demo_randomized.yml 是一个可复现实例:episode_num: 50,language_num: 100,random_background: true,cluttered_table: true,clean_background_rate: 0.02,random_table_height: 0.03,random_light: true,crazy_random_light_rate: 0.02,head/wrist camera 都使用 D435 并收集 RGB、endpose、qpos。
对应的 clean 配置 demo_clean.yml 则关闭 background、clutter、table height 和 light 随机化。envs/_base_task.py 在初始化任务时读取这些配置,设置 self.random_background、self.cluttered_table、self.random_table_height、self.random_light 等状态,并在 load_actors() 后按需调用 get_cluttered_table()。
桌高扰动通过 np.random.uniform(low=-self.random_table_height, high=0) 加到 table z bias 上。
randomization 的设计有两个细节值得注意。第一,clutter 不是任意乱放物体;论文说明 distractor 来自 RoboTwin-OD,并排除与目标视觉或语义过于相似的对象,以避免把任务变成歧义识别问题。第二,背景纹理有少量 clean rate,这让模型仍保留对常规场景的适应,而不是只在极端扰动上训练。换句话说,RoboTwin 2.0 的随机化不是无约束噪声,而是尽量模拟真实世界中“会变但不改变任务定义”的因素。
3.5 Embodiment-aware adaptation:同一对象,不同机器人需要不同抓法
Figure 5 解读:图 5 展示五种 RoboTwin 2.0 支持的 embodiment:Aloha-AgileX、ARX-X5、Piper、Franka、UR5。它们的自由度、可达空间、夹爪方向和双臂布局差异明显,所以同一任务不能默认共享一套抓取姿态。
Figure 6 解读:图 6 用 grasp behavior 说明不同机器人对同一对象的可行抓取方向不同。论文的策略是为对象标注多组 candidate manipulation poses,再结合机器人可达性对角度扰动做偏置采样。这样低自由度机器人能获得更多侧向或可达候选,高自由度机器人仍可使用 top-down 或更精细的姿态。
公开代码中与这一思想相关的低层支撑包括 envs/utils 里的 grasp/place API、GRASP_DIRECTION_DIC 与 WORLD_DIRECTION_DIC 等方向字典、ROTATE_NUM = 10 这样的候选旋转设置,以及每个任务环境里对 ArmTag("left") / ArmTag("right") 的选择逻辑。以 envs/handover_block.py 为例,play_once() 会根据 self.box.get_pose().p[0] 选择抓取手臂,再用另一只手完成交接与放置;这体现了双臂任务中动作序列与左右臂分工必须由对象位置和任务约束共同决定。
Figure 11 解读:这张 appendix 图进一步说明 object-centric manipulation 如何在异构双臂系统之间迁移。核心不是让每个机器人模仿同一关节轨迹,而是围绕对象的功能点、目标姿态和可行抓取方向生成机器人特定动作。
Figure 12 解读:这张 benchmark visualization 展示了 heterogeneous dual-arm control 的更多例子。它强调评测不是单一机器人、单一布局,而是让相同任务语义在不同 embodiment 和不同场景扰动下被执行,从而暴露策略对硬件和环境分布的依赖。
3.6 真实实现对应的伪代码
下面伪代码不是论文摘要的转写,而是按公开仓库 main@0aeea2d6 的 code_gen、script/collect_data.py、task_config/demo_randomized.yml 和 envs/_base_task.py 行为抽象。
def generate_task_program_with_multimodal_feedback(task_info, max_attempts=5, success_threshold=0.5):
messages = [{"role": "system", "content": "generate robot task code with provided API"}]
best_code, best_success_rate, best_records = None, 0.0, None
last_error, observation_feedback = None, None
for attempt_id in range(max_attempts):
prompt = build_prompt(
basic_info=BASIC_INFO,
task_description=task_info["task_description"],
actor_list=enrich_actors(task_info["actor_list"]),
available_api=AVAILABLE_ENV_FUNCTION,
function_examples=FUNCTION_EXAMPLE,
current_code=task_info["current_code"],
last_error=last_error,
visual_feedback=observation_feedback,
)
messages.append({"role": "user", "content": prompt})
raw_reply = generate(messages, gpt="deepseek", temperature=0)
code = wrap_as_env_class(raw_reply, base_env=task_info["task_name"])
write_file(f"envs_gen/gpt_{task_info['task_name']}.py", code)
success_rate, last_error, error_count, run_records = run_code(task_info)
if success_rate > best_success_rate:
best_code, best_success_rate, best_records = code, success_rate, run_records
if success_rate >= success_threshold:
break
code_with_observation, step_summary = insert_observation_points(task_info, code, attempt_id)
replay_code_and_save_camera_images(code_with_observation)
observation_feedback = observe_task_execution(
episode_id=choose_failed_episode(run_records),
task_name=task_info["task_name"],
task_info=task_info,
problematic_code=code,
save_dir="./camera_images",
)
task_info["current_code"] = code
task_info["task_description"] += format_failure_hint(last_error, error_count)
return best_code, best_success_rate, best_recordsdef collect_domain_randomized_episodes(task_name, task_config_path, target_success_episodes=50):
args = load_yaml(task_config_path) # demo_randomized.yml or demo_clean.yml
task_env = import_task_env(task_name)
seed, success_count, failed_count = resume_from_seed_file(args["save_path"])
while success_count < args["episode_num"]:
try:
task_env.setup_demo(now_ep_num=success_count, seed=seed, **args)
task_env.play_once()
if task_env.plan_success and task_env.check_success():
task_env.save_traj_data(success_count)
append_seed(seed)
success_count += 1
else:
failed_count += 1
except UnStableError:
failed_count += 1
finally:
task_env.close_env()
seed += 1
convert_cache_to_dataset(save_path=args["save_path"], task_name=task_name)
return success_count, failed_countdef setup_randomized_task_environment(args):
random_setting = args["domain_randomization"]
env.random_background = random_setting.get("random_background", False)
env.cluttered_table = random_setting.get("cluttered_table", False)
env.clean_background_rate = random_setting.get("clean_background_rate", 1.0)
env.random_table_height = random_setting.get("random_table_height", 0.0)
env.random_light = random_setting.get("random_light", False)
env.crazy_random_light_rate = random_setting.get("crazy_random_light_rate", 0.0)
table_z_bias = np.random.uniform(low=-env.random_table_height, high=0.0)
env.create_table_and_wall(table_height=0.74 + table_z_bias)
env.load_robot(**args)
env.load_camera(**args)
env.load_actors()
if env.cluttered_table:
env.get_cluttered_table() # collision-aware distractor placement
assert env.check_stable(), "unstable randomized scene should be rejected"
return envdef choose_embodiment_aware_grasp(actor, robot, task_constraint):
candidates = []
for functional_point in actor.functional_points:
for direction in preferred_operation_directions(actor, task_constraint):
for perturbation in sample_angular_perturbations(num=ROTATE_NUM):
pose = compose_pose(functional_point, direction, perturbation)
if motion_planner_reachable(robot, pose):
candidates.append(score_by_reachability_and_task_margin(pose, robot))
return max(candidates, key=lambda x: x.score).poseCode reference:
main@0aeea2d6(2026-04-19) — pseudocode and mapping based on this commit
| Paper Concept | Source File | Key Class/Function |
|---|---|---|
| MLLM expert code generation with previous error feedback | code_gen/task_generation_mm.py | generate_code(), main() |
| Text-only baseline generation loop | code_gen/task_generation.py | generate_code(), main() |
| Chat-completion backend for code generation | code_gen/gpt_agent.py | generate() |
| Visual observation feedback and observation-point insertion | code_gen/observation_agent.py | observe_task_execution(), insert_observation_points() |
| Generated-code execution and success-rate testing | code_gen/run_code.py, code_gen/test_gen_code.py | run_code(), task execution harness |
| Domain-randomized expert trajectory collection | collect_data.sh, script/collect_data.py | python script/collect_data.py $task_name $task_config |
| Randomized data collection config | task_config/demo_randomized.yml | random_background, cluttered_table, random_table_height, random_light, language_num |
| Clean data collection config | task_config/demo_clean.yml | same schema with randomization disabled |
| Scene initialization and randomization flags | envs/_base_task.py | _init_task_env_(), get_cluttered_table() |
| Clutter actor placement | envs/utils/rand_create_cluttered_actor.py | rand_pose_cluttered(), clutter object creation helpers |
| Object-centric grasp/place API examples | code_gen/prompt.py, envs/utils/* | grasp_actor(), place_actor(), move_by_displacement() |
| Task-level structured instruction | description/task_instruction/*.json | e.g. HANDOVER_BLOCK actor list and task description |
| Concrete task environment | envs/handover_block.py | handover_block.setup_demo(), play_once() |
| ACT baseline training | policy/ACT/train.sh | imitate_episodes.py --policy_class ACT --chunk_size 50 --batch_size 8 --num_epochs 6000 --lr 1e-5 |
| DP/DP3/RDT/π0 baseline interfaces | policy/DP, policy/DP3, policy/RDT, policy/PI0 | training/eval shell entrypoints and deploy configs |
3.7 方法的局限与可复现注意点
论文的生成器思想很强,但也有几个边界。第一,自动代码生成仍依赖 prompt、API 设计和仿真错误诊断;如果任务需要尚未暴露在 API 中的操作,LLM 只能在受限 API 空间内绕行。第二,randomization 的质量取决于对象标注、纹理库和碰撞采样规则;随机化过弱不能训练鲁棒性,过强或语义混淆则会破坏任务定义。第三,DP3 在部分仿真结果中表现强,论文也指出其优势部分来自 perfect point cloud 和 clean background segmentation,这在真实世界不一定成立。第四,真实世界实验覆盖 4 个任务和若干背景/杂物设置,已经比纯仿真有说服力,但还不能证明所有 50 个任务都能无缝 sim-to-real。
4. Experimental Setup(实验设置)
论文的实验分为四组。第一组评估自动专家代码生成。作者在 10 个机器人操作任务上比较 RoboTwin 1.0 和 2.0 的生成效果,并设置 Vanilla、FB、MM FB 三种模式:Vanilla 是一次生成;FB 使用执行日志修复;MM FB 进一步加入视觉观察反馈。指标包括 ASR、Top5-ASR、CR-Iter 和 Token。ASR 可理解为候选程序执行成功率的平均值,Top5-ASR 是 top-5 候选中的成功能力,CR-Iter 是平均修复轮数,Token 是生成程序 token 数。
第二组评估 embodiment-aware grasp augmentation。作者在 50 个 RoboTwin 2.0 任务和五种机器人 embodiment 上比较 RoboTwin 1.0 与 2.0 的自动数据收集成功率。这组实验直接检验:给对象增加多样抓取候选和 affordance 标注,是否真的能缓解低自由度或规划空间受限机器人的失败。
第三组评估 policy robustness。作者先用 32 个任务、每任务 300 条轨迹,共 9,600 条专家轨迹预训练 RDT 和 π0,分别比较 clean synthetic data 与 domain-randomized synthetic data;然后选 5 个 unseen tasks,每个任务 50 条 clean demonstrations 做单任务训练/微调;最后在 domain-randomized 条件下测试 ACT、DP、RDT、π0 等策略。这个设置刻意把训练任务和测试任务分开,用来检查随机化预训练是否形成跨任务鲁棒性。
第四组是真实世界 sim-to-real。作者选择 4 个双臂任务:Stack Bowls、Handover Block、Pick Bottle、Click Bell,每个任务在 seen/unseen background 与 cluttered/not cluttered 四种设置下评估。比较项包括只用 10 条 clean real demonstrations、10 条 clean real + 1k RoboTwin 2.0 synthetic trajectories,以及在 unseen background 上只用 1k RoboTwin 2.0 的 zero-shot。这个设置对论文主张很关键,因为它把“仿真随机化能否帮助真实世界”从仿真 benchmark 中单独拿出来验证。
Figure 10 解读:图 10 展示真实世界四种配置:seen/unseen background 与 cluttered/not cluttered 的组合。它说明论文不是只在视觉上类似训练集的环境评估,而是专门加入未见背景和杂物来放大 domain shift。真实世界结果中,few-shot synthetic augmentation 在 cluttered 与 unseen 背景下提升更大,符合强随机化数据主要提升鲁棒性的预期。
Benchmark 评测使用五类策略:ACT、DP、RDT、π0、DP3。所有 VLA 都从公开 pretrained weights 开始,在单任务设置下 fine-tune。50 个 benchmark tasks 使用 Aloha AgileX 双臂 embodiment;每个任务用 50 条 clean expert demonstrations 训练,在 Easy(clean)和 Hard(domain-randomized,包含 clutter、lighting、textures、height variations)下各测 100 次 rollout。
Appendix D 给出的部分训练细节包括:RDT 在 4.3 中用 8 张 GPU、每 GPU batch size 16 预训练 100,000 steps,单任务 fine-tuning 用 4 张 GPU、每 GPU batch size 16 训练 10,000 steps;π0 预训练 100,000 steps、batch size 32,fine-tuning 30,000 steps;ACT 使用 chunk size 50、batch size 8;DP3 训练 3,000 epochs、batch size 256、planning horizon 8、point cloud resolution 1,024,并使用精确背景和桌面分割。
公开代码中的 policy/ACT/train.sh 与 ACT 的 chunk_size 50、batch_size 8、num_epochs 6000、lr 1e-5 等入口一致;RDT/π0/DP3 目录提供了对应训练和评估脚本,但具体实验 YAML/外部 checkpoint 仍需按文档补齐。
Figure 9 解读:图 9 展示代码生成成功率分布。RoboTwin 2.0 加 MM FB 后分布更集中且中位数更高,说明多模态反馈不仅提升平均值,也减少极端失败任务。对数据生成器而言,稳定性和尾部任务同样重要,因为数据集里少量失败脚本会持续污染后续训练。
5. Experimental Results(实验结果)
5.1 自动代码生成:MM feedback 提升成功率并降低迭代负担
Table 1 的主要结论是多模态反馈有效,并且 RoboTwin 2.0 相比 1.0 更容易生成成功代码。
| Method | ASR | Top5-ASR | CR-Iter | Token |
|---|---|---|---|---|
| R1.0 Vanilla | 47.4% | 57.6% | 1.00 | 1236.6 |
| R1.0 + FB | 60.4% | 71.4% | 2.46 | 1190.4 |
| R1.0 + MM FB | 63.9% | 74.2% | 2.42 | 1465.0 |
| R2.0 Vanilla | 62.1% | 68.0% | 1.00 | 569.4 |
| R2.0 + FB | 66.7% | 73.6% | 1.89 | 581.6 |
| R2.0 + MM FB | 71.3% | 78.6% | 1.76 | 839.7 |
从 ASR 看,RoboTwin 1.0 从 Vanilla 到 MM FB 提升 16.5 个百分点,RoboTwin 2.0 从 62.1% 到 71.3% 提升 9.2 个百分点;论文摘要中称代码生成成功率有 10.9% gain,指向的是整体架构改进带来的经验提升。更重要的是 CR-Iter:R2.0 + MM FB 为 1.76,低于 R1.0 + MM FB 的 2.42,说明更好的对象标注、API 和任务结构让生成器更快收敛。Token 也显示 R2.0 Vanilla 的平均生成程序更短,暗示 prompt/API 约束更有效。
5.2 Adaptive grasping:收益集中在低自由度或受限 embodiment
| Method | Aloha-AgileX | Piper | Franka | UR5 | ARX-X5 | Average |
|---|---|---|---|---|---|---|
| RoboTwin 1.0 | 65.1% | 2.4% | 67.3% | 57.6% | 68.6% | 52.2% |
| RoboTwin 2.0 | 78.8% | 25.1% | 67.2% | 57.1% | 74.2% | 60.5% |
| Difference | +13.7% | +22.7% | -0.1% | -0.5% | +5.6% | +8.3% |
Table 2 说明 embodiment-aware grasp augmentation 不是所有机器人平均受益,而是对受限 embodiment 尤其重要。Piper 从 2.4% 到 25.1%,Aloha-AgileX 从 65.1% 到 78.8%,ARX-X5 增加 5.6%;Franka 和 UR5 几乎不变,论文解释为高自由度机械臂可达空间大,原本就较少受抓取候选不足限制。这个结果支持方法中的一个关键判断:跨 embodiment 泛化不是简单共享任务脚本,而需要对象中心的候选抓取和机器人可达性适配。
5.3 Domain-randomized synthetic pretraining:clean data 不足以处理 hard distribution
Table 3 的平均值最有信息量:ACT 2.0%、DP 0.0%,released pretrained RDT 18.8%、π0 22.5%;加 clean pretraining/fine-tuning 后,RDT + Clean 只有 14.6%,π0 + Clean 24.9%;而 RDT + Rand. 达到 24.8%,π0 + Rand. 达到 29.1%。论文进一步报告 RDT 和 π0 相对提升分别为 31.9% 与 29.3%。这表明 clean synthetic data 不能自动解决 hard randomized 环境;真正有用的是训练阶段见过视觉、空间和语言扰动。
任务级结果也显示提升不是均匀发生。例如 Stack Bowls Two 中 RDT 从 30.0% 提到 49.0%,π0 从 41.0% 提到 62.0%;Open Laptop 中 RDT 从 32.0% 提到 35.0%,π0 从 46.0% 到 50.0%;Place Shoe 中 RDT 从 7.0% 到 30.0%,说明某些对象/姿态相关任务受益更明显。但 Pick Dual Bottles 上 π0 + Rand. 只有 7.0%,低于 π0 released 的 12.0%,提醒我们随机化并非对所有任务无条件有效,任务难度、对象干扰和模型动作先验仍会影响结果。
5.4 真实世界:few-shot 合成数据增强在复杂场景下最明显
真实世界 Table 4 的平均结果如下:
| Background | Cluttered | 10 Clean Real | 10 Clean Real + 1k RoboTwin 2.0 | 1k RoboTwin 2.0 Zero-shot |
|---|---|---|---|---|
| Seen | False | 29.5% | 43.0% (+13.5%) | / |
| Seen | True | 14.0% | 41.5% (+27.5%) | / |
| Unseen | False | 15.5% | 39.0% (+23.5%) | 36.5% (+21.0%) |
| Unseen | True | 9.0% | 42.0% (+33.0%) | 29.5% (+20.5%) |
平均来看,few-shot 设置中 10 条真实数据 + 1,000 条 RoboTwin 2.0 合成数据比只用 10 条真实数据提高 24.4 个百分点;zero-shot 只用合成数据,在 unseen background 的非杂物和杂物场景也分别有 21.0 和 20.5 个百分点提升。最值得注意的是 unseen + cluttered 的提升最大,从 9.0% 到 42.0%,说明 domain-randomized 数据的收益主要体现在真实世界分布更复杂时,而不是只在 seen clean 场景上锦上添花。
具体任务也有有趣差异。Stack Bowls 在所有设置下几乎都受益,unseen + not cluttered 中 zero-shot 达 60.0%,甚至高于 10 real + synthetic 的 50.0%;Handover Block 在 seen + cluttered 中 synthetic augmentation 从 16.0% 降到 12.0%,说明交接类任务可能对真实接触和左右臂同步更敏感;Pick Bottle 和 Click Bell 在 cluttered/unseen 中整体受益明显,但 zero-shot 不总是超过 few-shot。这些差异说明 RoboTwin 2.0 能显著缩小 sim-to-real gap,但少量真实数据仍然是校准接触、相机和动作分布的重要锚点。
5.5 Benchmark:Hard setting 暴露了现有策略的鲁棒性缺口
Table 5 在 50 个任务上报告 Easy/Hard 的子集结果。平均值为:RDT Easy 34.5、Hard 13.7;π0 Easy 46.4、Hard 16.3;ACT Easy 29.7、Hard 1.7;DP Easy 28.0、Hard 0.6;DP3 Easy 55.2、Hard 5.0。RDT 与 π0 在 Hard 下仍明显优于 ACT/DP/DP3,但下降幅度分别为 20.8 和 30.1 个百分点,说明 VLA 预训练提供了先验,但不足以覆盖 RoboTwin 2.0 的强 domain shift。
DP3 的结果需要谨慎解读。它在 Easy 下平均 55.2,是表中最高,但 Hard 下只有 5.0;论文指出它的 few-shot 表现部分来自仿真中的 perfect point clouds 和 clean segmentation。这意味着 3D 信息确实有助于少样本操作,但如果 3D 输入质量依赖仿真 oracle,真实或强随机化环境下就会失去优势。对 benchmark 设计来说,这正是 RoboTwin 2.0 的意义:它不只问“策略在 clean demos 上能不能学会”,还问“策略的感知、语言、动作和数据先验在扰动下是否仍然成立”。
总体结论是,RoboTwin 2.0 的主要贡献可以被实验闭环验证:MM feedback 让专家代码更可靠;embodiment-aware grasping 提升低自由度机器人数据收集;strong domain randomization 让 VLA 在 unseen task 和真实复杂环境中更鲁棒;50-task Easy/Hard benchmark 暴露现有策略在随机化条件下仍有巨大空间。对后续研究,最直接的使用方式是把它当成可控数据引擎:先用 clean/randomized 配置生成不同强度数据,再用同一 50-task benchmark 和真实世界设置检查模型到底学到了任务语义、对象 affordance,还是仅仅记住了干净仿真分布。