Skip to content

L2 数据生成层:三种轨迹来源调研

目的:在动手设计本仓库 L2 子结构之前,搞清楚 scripted / RL / teleop 三条采集路径在 IsaacLab 里到底怎么实现、差在哪、最终能不能合到同一个 LeRobot v2 dataset

配套阅读:LeRobot 版本研究GR00T 1.7 Dataset 约束LeRobot v2 Schemaobs/action 对齐

TL;DR

来源量级用途本仓库选型Action 写盘
Scripted10⁵+ /天(GPU 并行)底座技能 / 长尾覆盖Warp SM + mimic RecorderManager8d IK-abs
RL rollout10³–10⁴recovery / 长尾 / explorationrsl_rl PPO + joint_pos task + rollout 时录制层覆写[1]8d IK-abs(覆写)
Teleop10²–10³高质量 ground truthSe3Keyboard IK-rel + 脚本侧复合 abs8d IK-abs

三来源 schema 必须对齐到同一个 8d IK-abs,否则下游 LeRobot v2 转换需按 source 分支,破坏 "一个 dataset 三来源" 的契约。

1. 数据金字塔

混合训练时通常对 teleop 上采样(GR00T N1.5、π0 等)。本仓库下游消费方已锁 GR00T N1.7(CLAUDE.md),N1.5/N1.6 兼容性问题不在范围内。本文出现的 "GR00T N1.5" 等版本号仅用于行业惯例描述(数据金字塔、上采样实践),不是消费方约束。

NOTE

未确认(与本仓库无关):scripted/RL/teleop 官方推荐混合比例;GR00T 训练脚本 collator 是否在采样阶段使用 episodes.jsonl 自定义 source 字段做加权。本仓库只产数据,下游 finetune 不在范围。

2. Scripted policy

采集路径

代表实现:IsaacLab/scripts/environments/state_machine/[2]——lift_cube_sm.pylift_teddy_bear.pyopen_cabinet_sm.py。SM 状态枚举如 REST → APPROACH_ABOVE_OBJECT → APPROACH_OBJECT → GRASP_OBJECT → LIFT_OBJECT,整个 SM 用 Warp kernel 在 GPU 上并行 num_envs 个实例。本仓库 l2/scripted/state_machine.py + l2/scripted/collect.py 改编自 lift_cube_sm.py

Motion planner(cuRobo / RmpFlow / SkillGen)走另一条路,集成在 mimic 流水线[3]generate_dataset.py --use_skillgen 走 SkillGen,否则走 mimic 默认线性插值。

WARNING

state_machine/*.py 本身不接 RecorderManager,纯演示用。批量产数据的标准路径是把 SM 当 "种子轨迹生成器" 喂给 ManagerBasedRLMimicEnv,由 mimic 的 datagen[4] replay+扰动后用 RecorderManager 自动写 HDF5。

obs / action 特征

  • Action:IK abs(绝对位姿,7-d pose + 1-d gripper),SM 直接在 world frame 算目标 EE pose。
  • Observation:proprio + 物体姿态(特权信息)为主,RGB 不强制(训练时再开 camera sensor)。
  • 频率:与 env.step 同频,典型 30–60 Hz。

generation_keep_failed 经验法则

MimicEnvCfg.datagen_config.generation_keep_failed 默认 False[5]。各 mimic env_cfg 显式覆写,规律:

  • 默认 True(保留失败轨迹):agibot_*galbot_*franka_stack_ik_abs_mimic_env_cfgfranka_stack_ik_rel_skillgen_env_cfgfranka_stack_ik_rel_visuomotor_cosmos_mimic_env_cfgfranka_stack_ik_rel_blueprint_mimic_env_cfgfranka_bin_stack_ik_rel_mimic_env_cfg
  • 默认 Falsefranka_stack_ik_relfranka_stack_ik_rel_visuomotorpickplace_gr1t2nutpour_gr1t2exhaustpipe_gr1t2locomanipulation_g1

经验:成功率高 / 任务难才打开 keep_failed,把失败轨迹当 recovery data 收。本仓库默认跟成功率高的任务对齐 keep_failed=True

3. RL policy rollout

选型决策

方面选择理由
RL 框架rsl_rlIsaacLab 现成基础设施最完整;rsl-rl-lib==5.0.1 通过 isaaclab_rl extras 装;OnPolicyRunner / RslRlVecEnvWrapper / RslRl PPO cfg 全直接用
训练 taskIsaac-Lift-Cube-Franka-v0(joint_pos)IsaacLab config/franka/__init__.py 只对 joint_pos task 注册了 rsl_rl_cfg_entry_point,IK-Abs task 未注册 PPO cfg
Action 对齐rollout 时录制层重写[1:1]PPO 学 IK-abs(含 4d unit quat)的 box action space 不收敛;写自定义 ActionTerm 的工程成本和教学含金量都不划算

为什么不在 rollout 之前先 train 一个 IK-Abs PPO:IsaacLab 没提供 cfg,从 0 调一周不一定收敛;教学价值低;joint_pos PPO + 录制层重写能让 RL rollout 跟 scripted/teleop 落到同一个 8d schema。

录制基础设施

  • HDF5DatasetFileHandlerIsaacLab/source/isaaclab/isaaclab/utils/datasets/hdf5_dataset_file_handler.py:45[6]
  • 官方 HDF5 schema:顶层 attrs format_version=1(v1=XYZW quat,v0=legacy WXYZ);顶层 group data/(带 total / env_args attrs);每条 episode 是 data/demo_{i} group,attrs num_samples / seed / success;group 内可递归存任意 nested dict。本仓库 data/demos/*.hdf5 schema 与官方对齐——l2/scripted/env_cfg.py / l2/teleop/env_cfg.py 都直接挂 ActionStateRecorderManagerCfg,写出官方 data/demo_{i}/{actions, obs/<key>} 布局[7]。早期试验期用过的 episodes/{i}/... 自造布局已弃用。
  • 典型用法[8]
python
env_cfg.recorders: ActionStateRecorderManagerCfg = ActionStateRecorderManagerCfg()
env_cfg.recorders.dataset_export_dir_path = output_dir
env_cfg.recorders.dataset_filename = output_file_name
env_cfg.recorders.dataset_export_mode = DatasetExportMode.EXPORT_SUCCEEDED_ONLY

obs / action 特征

  • 训练 obs:joint_pos task 的 LiftEnvCfg.ObservationsCfg.PolicyCfg,5 个 obs term,concatenate_terms=True(PPO 要 1-D 向量输入)。
  • rollout obs:跟训练同一组 obs term,concatenate_terms=True 不变(PPO MLPModel 硬要求 1-D obs);自定义 PolicySplitObsRecorderobservation_manager.group_obs_term_dim["policy"] 把 1-D obs 切回 dict 写 obs/<key>,与 scripted HDF5 schema 一致。
  • action:训练时 8d joint_pos(7 个 panda_joint.* + 1 个 panda_finger.* binary gripper);rollout 时录制层覆写为 8d IK-abs 等价[1:2]
  • 频率sim.dt=0.01 × decimation=2 = 50 Hz,跟 scripted 一致。RGB 必须打开 camera sensor,throughput 显著下降。

Episode 元数据

推荐:policy_checkpoint_hashalgo(PPO/SAC/...)、seedstep_returnsuccess bool、action_distribution_stats。RL 失败率天然较高,这正好是 recovery data 的来源——加 success label 后保留是 2026 趋势(AgiBot World 2026 把 "错误恢复轨迹" 列为一等公民)。

4. 人类遥操作(teleop)

设备抽象层

IsaacLab/source/isaaclab/isaaclab/devices/[9]keyboard/spacemouse/gamepad/ 出 6-DoF SE3 delta + gripper;openxr/ 走 VR / 手追踪 + retargeter;haply/ 力反馈。teleop_device_factory.create_teleop_deviceenv_cfg.teleop_devices dispatch。

选型决策(keyboard 最小教学版)

教学定位 + 没有 SpaceMouse / VR 硬件,所以 l2/teleop/ 只支持 keyboard

方面选择理由
设备Se3Keyboard 出 7d delta + gripper不需硬件,零成本上手;接口跟 SpaceMouse 同形(都是 7d SE3 delta),后续接 SpaceMouse 只是替个 device class
TaskIsaac-Lift-Cube-Franka-IK-Abs-v0(与 scripted 同 task)写盘 action 直接 8d IK-abs,HDF5 schema 与 l2/scripted / l2/rl 完全一致,L3 convert.py 不需按 source 分支
Action 对齐在 record loop 里手动 rel→abs 复合Se3Keyboard.advance() 出 IK-rel 7d delta;脚本读 ee_frame 当前 pose 复合:abs_pos = ee_pos_curr + dposabs_quat = quat_mul(delta_quat, ee_quat_curr)、gripper 透传

为什么不直接用 IsaacLab IK-Rel task 让 controller 自己复合:那样写盘 actions 列就是 IK-rel 7d,跟 scripted/RL 的 8d 不一致;L3 转换层就要按 source 分支。把复合做在脚本侧让三来源 schema 对齐才是核心契约。

为什么不沿用 IsaacLab 自带 record_demos.py:它强 import isaaclab_mimic[10] 带额外包依赖;XR / IsaacTeleop / SpaceMouse multi-device 流水线对教学最小版过度设计;自己 ~150 行精简版(l2/teleop/record.py)更可控、注释好嵌入。

success 与 reset 模式

  • success 走 "连续 N 步 + 手动 export"[11]:把 success DoneTerm 从 terminations 抽出 None,主 loop 自己跑 success_term.func(env, ...) 计数。这避免人手抖动让 cube 瞬时进 success 圈触发 auto-reset 把 episode 切短。
  • time_out=None:默认 episode_length_s=5.0 太短,给人留足够操作时间,只在 success / object_dropping / 用户按 R 时 reset。

obs / action 特征

  • 几乎一定带多视角 RGB——这是与 scripted/RL 最大的差别,VLA 训练必需图像。本最小版未加,按 l2/scripted/env_cfg.py:20-23--with_camera 扩展点同步加即可。
  • Action 空间随设备:keyboard / SpaceMouse → IK rel(SE3 delta + gripper)→ 复合成 8d IK-abs;VR 手追踪 → IK abs 或 retarget 到关节(不在本仓库范围);Leader-follower → 直接关节角(不在本仓库范围)。
  • 频率:env step 30 Hz(record_demos.py --step_hz 30 同款),低于 RL 50 Hz,更贴近设备采样率与人类响应延迟。

Episode 元数据

operator_iddevicecloudxr_session_idsuccess flag、recovery_labellanguage_instructionRecorderManager 默认按 success 过滤;generation_keep_failed=True 才留失败 / 恢复轨迹。

5. 三种来源合一个 dataset?

可以,前提是:

  1. 统一 fps / features schema。
  2. action 空间对齐到同一表示(本仓库选 8d IK-abs)。否则 scripted IK-abs / RL joint_pos / teleop IK-rel 直接冲突,只能放不同 dataset 或用不同 modality.json 切片。
  3. task_index 跨来源统一编号。
  4. episodes.jsonl 加自定义 source ∈ {scripted, rl, teleop} 字段做分层采样——lerobot loader 用 [json.loads(line) for line in f] 全量保留 dict,只读 episode_index / length,所以安全添加任意自定义字段[12],只要不 shadow 三个标准 key(episode_index / tasks / length)。GR00T 公开 dataset 的 episodes.jsonl 自带非 spec 字段 trajectory_id,这是先例。

IMPORTANT

具体的 LeRobot v2 文件布局、info.json / modality.json / parquet 列约定不在本文展开——以前的版本写过一份"通用 v2 spec"表,但跟 GR00T 1.7 实际期望存在差异(float32 vs float64next.done/next.reward 写不写、stats schema),容易误导。请直接看 LeRobot v2 SchemaGR00T 1.7 Dataset 约束

IsaacLab 自带 GR00T 转换器(参照实现)

IsaacLab/scripts/imitation_learning/locomanipulation_sdg/gr00t/convert_dataset.py[13]——把 IsaacLab HDF5 转成 GR00T flavor 的 LeRobot v2,约 370 行。这是本仓库 L3 转换器最直接的参照。要点:

  • 自带 create_modality_json(state, action):从 dict-of-arrays 自动算 start/end 切片,加固定 video + annotation 字段。
  • 自带 create_info_json(...):硬编码 codebase_version="v2.0"splits={"train":"0:100"}total_chunks=0注意:跟 v2 标准的 total_chunks=ceil(N/chunks_size) 不一致;GR00T loader 不检查这个,但本仓库要做更严格的实现)。
  • cv2.VideoWriter(*"mp4v") 写视频,fps=20.0 硬编码。mp4v 不是 h264,跟 NVIDIA 公开 dataset 的 video.codec="h264" 不一致;GR00T 当前不强校验 codec,但更规范的做法是用 ffmpeg + h264。
  • pose_to_transform / compute_relative_pose 做坐标系转换(base_pose 相对系),quat 走 scalar-first (w,x,y,z) 约定。

6. 实现难度与教学顺序

实现难度从易到难:Scripted < RL rollout < Teleop

  • Scripted:state_machine 即跑即用,套 mimic RecorderManager 就能批量产数据。
  • RL rollout:训练耗 GPU 时但单脚本简单;难点在 reward shaping 与挂 ActionStateRecorderManagerCfg
  • Teleop:要硬件 / CloudXR 配置 / retargeter 调参,但教学含金量最高(直接对应真实采集流程)。

推荐教学顺序:

scripted lift_cube
    ↓ 用 mimic 扩增成更大数据集
    ↓ 加 RL play 录小数据集(demonstrate recovery 概念)
    ↓ 加 SpaceMouse / keyboard teleop 录 10–30 条
    ↓ 三者合到同一个 GR00T-flavor LeRobot v2
(仓库范围之外)GR00T N1.7 LoRA finetune

  1. rollout 时在 l2/rl/env_cfg.py 的 rollout-only 配置挂自定义 IKAbsEquivalentActionRecorderRecorderTerm 子类):每个 pre_step 用 ee_frame sensor 当前 EE pose + finger joint 二值化 gripper 组成 8d 等价 IK-abs action,覆写默认 PreStepActionsRecorder 写出的 8d joint action 列(7 panda_joint + 1 binary gripper);同时覆写 obs/actions(last_action term)也用 8d。详见 obs/action 对齐 末尾"l2/rl 实现细节"。teleop 实现细节见同文档末尾"l2/teleop 实现细节"。 ↩︎ ↩︎ ↩︎

  2. IsaacLab/scripts/environments/state_machine/lift_cube_sm.py / lift_teddy_bear.py / open_cabinet_sm.py↩︎

  3. IsaacLab/scripts/imitation_learning/isaaclab_mimic/generate_dataset.py--use_skillgen 走 SkillGen);motion planner 实现在 IsaacLab/source/isaaclab_mimic/isaaclab_mimic/motion_planners/↩︎

  4. IsaacLab/source/isaaclab_mimic/isaaclab_mimic/datagen/generation.py↩︎

  5. 基类 MimicEnvCfg.datagen_config.generation_keep_failed 默认 FalseIsaacLab/source/isaaclab/isaaclab/envs/mimic_env_cfg.py:35。各 env_cfg 在 IsaacLab/source/isaaclab_mimic/isaaclab_mimic/envs/* 显式覆写,例如 agibot_place_upright_mug_mimic_env_cfg.py:27↩︎

  6. IsaacLab/source/isaaclab/isaaclab/utils/datasets/hdf5_dataset_file_handler.py:45。Recorder 实现在 IsaacLab/source/isaaclab/isaaclab/envs/mdp/recorders/recorders.pyPreStepActionsRecorder / PreStepFlatPolicyObservationsRecorder / PostStepStatesRecorder / PostStepProcessedActionsRecorder,由 RecorderManager 在 step 钩子调用。 ↩︎

  7. 完整 schema 见 HDF5 Schema↩︎

  8. 用法抄自 IsaacLab/scripts/tools/record_demos.py:260↩︎

  9. IsaacLab/source/isaaclab/isaaclab/devices/keyboard/se3_keyboard.pyspacemouse/se3_spacemouse.pygamepad/se3_gamepad.pyopenxr/haply/teleop_device_factory.py。teleop entry 脚本:IsaacLab/scripts/environments/teleoperation/teleop_se3_agent.py↩︎

  10. IsaacLab/scripts/tools/record_demos.py:123 强 import isaaclab_mimic↩︎

  11. 抄自 IsaacLab/scripts/tools/record_demos.py:240-242391-399↩︎

  12. GR00T 公开 dataset 的 episodes.jsonl 实例:{"episode_index": 0, "tasks": [...], "length": 316, "trajectory_id": "..."}——trajectory_id 是非 spec 字段。Sample 来自 NVIDIA PhysicalAI-Robotics-GR00T-X-Embodiment-Sim。lerobot loader 实现见 HuggingFace LeRobot datasets 模块↩︎

  13. IsaacLab/scripts/imitation_learning/locomanipulation_sdg/gr00t/convert_dataset.py。NVIDIA 数据准备文档:Isaac-GR00T data_preparation.md↩︎