在前3期Physical AI详解系列中,我们详细解读了数据采集、扩增、增强的全过程,以及导航模型(X-Mobility)微调训练的全过程。
在本期,我们将针对更复杂的VLA模型(以GR00T-N1.5为例)进行微调,同样需要经过人工演示、数据扩增、模仿学习、在环验证这几个步骤。

但是,相比前例中的BC-RNN和X-Mobility模型,GR00T-N1.5是一个更复杂的模型,需要更大规模的数据合成、模仿学习,并需要独立的服务端-客户端架构进行在环验证。相对于本系列的前3个最佳实践,本例可重点学习以下能力:
-
使用RobotLearningLab公共数据集
-
使用DLC进行分布式大规模数据扩增
-
使用DLC,针对较复杂的VLA模型进行分布式模仿学习
-
组合使用两台DSW进行服务端-客户端架构的软件在环验证
在PAI的Notebook Gallery中,我们已经预置了一个最佳实践,就是这个过程的一个具体示例:
gallery.pai-ml.com/#/preview/d...

下面我们来详细解读这个示例。
1. 环境准备
由于需要交互式环境进行人工演示,我们需要启动DSW。
以北京Region为例,我们可以基于以下镜像启动DSW,其中已经包含了Isaac Lab 2.2及其依赖环境:
bash
dsw-registry-vpc.cn-beijing.cr.aliyuncs.com/pai-training-algorithm/isaac-sim:isaaclab220-nb4-v7-20250916
建议在启动时选择以下规格的公共资源或预付费资源配额,以确保具备Isaac Lab运行所需的RT Core:
-
ecs.ebmgn8is.32xlarge
-
ecs.gn8is-8x.32xlarge
-
ecs.ebmgn8te.32xlarge
-
ecs.ebmgn9t.48xlarge
同时,由于需要使用NVIDIA RobotLearningLab公共数据集,务必在启动时挂载此数据集到/mnt/RobotLearningLab_Dataset/路径,从而避免重复下载:


在环境启动后,使用Notebook中的初始化脚本,配置环境变量并下载GR00T模型
python
import subprocess
import os
from urllib.parse import quote
from pathlib import Path
# 1. 持久&外部存储的路径,默认挂载外部存储到/mnt/data下
os.environ['EXTERNAL_STORAGE_PATH'] = '/mnt/data/isaac_tmp/nb4'
path = os.environ['EXTERNAL_STORAGE_PATH']
os.system(f'mkdir -p "{path}"')
print(f"需要将外部存储挂载到/mnt/data")
print(f"EXTERNAL_STORAGE_PATH: {path}\n")
# 2. Isaac Lab项目路径
os.environ['ROBOT_LEARNING_LAB_PATH'] = "/workspace/RobotLearningLab"
path = os.environ['ROBOT_LEARNING_LAB_PATH']
print(f"ROBOT_LEARNING_LAB_PATH: {path}")
path = os.environ['ISAACLAB_PATH']
print(f"ISAACLAB_PATH: {path}\n")
# 3. 相关数据路径,直接挂载PAI公共数据集RobotLearningLab_Dataset到/mnt/RobotLearningLab_Dataset
os.environ['ROBOT_LEARNING_LAB_DATA_PATH'] = "/mnt/RobotLearningLab_Dataset"
path = os.environ['ROBOT_LEARNING_LAB_DATA_PATH']
print(f"需要将PAI公共数据集RobotLearningLab_Dataset挂载到/mnt/RobotLearningLab_Dataset")
print(f"ROBOT_LEARNING_LAB_DATA_PATH: {path}")
print(f"我们预先采集和处理好的,可以用来训练的数据集位于: {path}/usecase/galbot_stack_cube/lerobot_joint_space\n")
# 4. Isaac-GR00T代码路径
os.environ['ISAAC_GR00T_PATH'] = os.environ['EXTERNAL_STORAGE_PATH']+"/Isaac-GR00T"
path = os.environ['ISAAC_GR00T_PATH']
print(f"需要将代码下载到指定路径")
print(f"ISAAC_GR00T_PATH: {path}\n")
# 5. 相关模型路径
os.environ['ISAAC_GR00T_MODEL_PATH'] = os.environ['EXTERNAL_STORAGE_PATH'] + "/GR00T-N1.5-3B"
path = os.environ['ISAAC_GR00T_MODEL_PATH']
print(f"需要将模型下载到指定路径")
print(f"ISAAC_GR00T_MODEL_PATH: {path}")
os.environ['ISAAC_GR00T_MODEL_POST_PATH'] = os.environ['EXTERNAL_STORAGE_PATH'] + "/checkpoint-40000"
path = os.environ['ISAAC_GR00T_MODEL_POST_PATH']
print(f"ISAAC_GR00T_MODEL_POST_PATH: {path}\n")
!wget https://pai-vision-data-sh.oss-cn-shanghai.aliyuncs.com/aigc-data/isaac/nb4/gr00t.tar -O /tmp/gr00t.tar
!tar -xf /tmp/gr00t.tar -C /tmp/ && rm /tmp/gr00t.tar && mv /tmp/gr00t $ISAAC_GR00T_PATH
path = os.environ['ISAAC_GR00T_PATH']
print(f"代码已下载到ISAAC_GR00T_PATH: {path}")
import os
from pathlib import Path
local_dir = Path("/root/isaac_cache") # 缓存目录
external_dir = os.environ['EXTERNAL_STORAGE_PATH'] #持久化存储目录
print(f"下载模型到: {local_dir}")
if os.path.exists(local_dir):
!rm -rf {local_dir}
local_dir.mkdir(parents=True, exist_ok=True)
print("开始下载模型...")
package = "GR00T-N1.5-3B.tar"
download_from_oss('aigc-data/isaac/nb4/', package, str(local_dir))
print("下载完成")
print("开始解压模型...")
zip_file = os.path.join(local_dir, package)
print(zip_file)
!tar -xf {zip_file} -C {local_dir}
!rm {zip_file}
print("解压完成")
print(f"开始移动到资产目录: {external_dir}") # 持久化存储目录
path = os.environ['ISAAC_GR00T_MODEL_PATH']
if os.path.exists(path):
!rm -rf {path}
!mv {local_dir}/* {external_dir}
print(f"GR00T-N1.5-3B已下载到ISAAC_GR00T_MODEL_PATH: {path}")
2. 人工演示
在DSW WebTerminal中,运行以下命令启动VNC:
bash
/opt/TurboVNC/bin/vncserver :0 -geometry 3840x2160
在本地Terminal中执行以下命令连接DSW:
java
ssh -L 5900:127.0.0.1:5900 root@DSW公网IP地址 -p DSW公网端口
使用TigerVNC等VNC工具打开VNC窗口,并在VNC中执行以下命令启动人工演示界面:
bash
# 步骤a: 创建数据集文件夹
mkdir -p /mnt/data/isaac_tmp/nb4/datasets
# 步骤b: 使用选定的teleoperation设备收集数据
# 可用选项: spacemouse, keyboard
cd /workspace/RobotLearningLab && ./isaaclab.sh -p usecase/scripts/record_demos.py --task Isaac-Stack-Cube-Galbot-Left-Arm-RmpFlow-Rel-v0 --teleop_device keyboard --dataset_file /mnt/data/isaac_tmp/nb4/datasets/dataset.hdf5 --num_demos 10
在人工演示界面中,可以通过以下按键控制机器人夹爪的运动:
-
重置所有命令: R
-
切换夹爪(开/关): K
-
沿x轴移动机械臂: W/S
-
沿y轴移动机械臂: A/D
-
沿z轴移动机械臂: Q/E
-
沿x轴旋转机械臂: Z/X
-
沿y轴旋转机械臂: T/G
-
沿z轴旋转机械臂: C/V
通过控制夹爪,将桌面上的立方体按照蓝色(底部) -> 红色(中间) -> 绿色(顶部)的顺序叠放,大约需要10个成功的演示。以下是一个成功演示的示例:
3. 数据扩增
首先对采集得到的dataset.hdf5文件进行子任务标注:
ini
# 创建数据集目录
path = os.environ.get("ROBOT_LEARNING_LAB_PATH")
output_path_str = os.environ.get("EXTERNAL_STORAGE_PATH")+"/datasets"
output_path = Path(output_path_str)
output_path.mkdir(parents=True, exist_ok=True)
# 标注
annotate_command = f"""
cd {path} && \
./isaaclab.sh -p usecase/scripts/annotate_demos.py \
--task Isaac-Stack-Cube-Galbot-Left-Arm-RmpFlow-Abs-Mimic-v0 \
--device cuda \
--auto \
--input_file {output_path_str}/dataset.hdf5 \
--output_file {output_path_str}/dataset_annotate.hdf5 \
--headless
"""
print("标注命令:")
print(annotate_command)
# 执行
!{annotate_command}

在DSW执行以下代码启动headless数据扩增过程:
ini
# 创建数据集目录
path = os.environ.get("ROBOT_LEARNING_LAB_PATH")
output_path_str = os.environ.get("EXTERNAL_STORAGE_PATH")+"/datasets"
output_path = Path(output_path_str)
output_path.mkdir(parents=True, exist_ok=True)
# 扩增
generate_command = f"""
cd {path} && \
./isaaclab.sh -p usecase/scripts/generate_dataset.py \
--task Isaac-Stack-Cube-Galbot-Left-Arm-RmpFlow-Abs-Mimic-v0 \
--device cuda \
--num_envs 10 \
--generation_num_trials 10000 \
--input_file {output_path_str}/dataset_annotate.hdf5 \
--output_file {output_path_str}/dataset_generate.hdf5 \
--headless
"""
print("扩增命令:")
print(generate_command)
# 执行
!{generate_command}
其中:
-
--num_envs 10 代表同一时刻并行扩增10份数据
-
--generation_num_trials 10000 代表目标生成10000份成功操作的数据
-
--input_file {output_path_str}/dataset_annotate.hdf5 代表输入的数据,即上述步骤中经过子任务标注的数据文件
-
--output_file {output_path_str}/dataset_generate.hdf5 代表输出的数据文件存放位置
-
--device cuda 代表使用GPU卡进行生成加速
-
--headless 代表以无GUI的方式进行后台数据合成
运行过程中会产生如下日志:

其中类似84/144 (58.3%)的数据分别代表成功生成的数据条数、总共尝试的数据合成次数,以及数据合成的成功率。
3.1 在DLC中执行分布式数据扩增
可以看到,在DSW使用单机资源进行数据扩增,任务执行较慢。但其实每份数据的合成过程相互独立,可以使用数据并行(DP)的方式进行分布式处理:
在DLC中创建任务,环境配置如下:

自定义数据集:挂载DSW中相同的自定义数据集,以确保可以读取到annotated_dataset.hdf5
启动命令:
css
/workspace/RobotLearningLab/isaaclab.sh -p /mnt/data/isaac_tmp/nb4/datasets/ray_isaac_new.py
--command "cd /workspace/RobotLearningLab &&
./isaaclab.sh -p
/mnt/data/isaac_tmp/nb4/datasets/generate_dataset_ray.py --task Isaac-Stack-Cube-Galbot-Left-Arm-RmpFlow-Abs-Mimic-v0 --device cuda
--num_envs 10
--generation_num_trials 625
--input_file
/mnt/data/isaac_tmp/nb4/datasets/dataset_annotate.hdf5
--output_file
/mnt/data/isaac_tmp/nb4/datasets/dataset_generate.hdf5
--headless"
--gpu 1
--cpu 10
--memory 80
--num_per_worker 8
其中:
-
--num_per_worker 8 代表一个节点运行8个数据合成任务
-
--gpu 1 --cpu 10 --memory 80 代表每个任务使用1张GPU、10个CPU核心、80GB内存
-
--generation_num_trials 625 代表每个数据合成任务目标生成625份数据
资源配置如下:

其中使用了Ray类型任务,以实现任务分布式执行
设置Ray的Head节点数为1,CPU核数为8,内存数为32GB
设置Ray的Worker节点数为2,每个Worker的GPU卡数为8,CPU核数为90,内存数为700
综上所述,我们总共将启动16个数据合成任务,每个任务:
-
CPU核数:10
-
内存:80GB
-
GPU:1张
-
目标数据合成数量:625条
这样总共使用2台8卡的ecs.ebmgn8te.32xlarge资源即可运行。
任务启动后,可以通过DLC界面右上角的Dashboard打开Ray控制台,查看任务运行的详细信息:

可以看到,16个数据合成任务已经成功启动并运行:

4. 数据处理
在完成数据扩增之后,需要经过合并、重放并转换为Lerobot格式才能用户GR00T N1.5模型的模仿学习。
数据合并:
python
# 创建数据集目录
path = os.environ.get("ROBOT_LEARNING_LAB_PATH")
output_path_str = os.environ.get("EXTERNAL_STORAGE_PATH")+"/datasets"
output_path = Path(output_path_str)
output_path.mkdir(parents=True, exist_ok=True)
# 合并DLC生成的Mimic分片文件
def merge_datasets(input_files, output_file="merged_dataset.hdf5"):
"""
合并多个HDF5数据集
"""
cmd = f"{path}/isaaclab.sh -p {path}/scripts/tools/merge_hdf5_datasets.py \
--input_files {' '.join(input_files)} \
--output_file {output_file}"
print(f"合并数据集: {cmd}")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
print(result.stdout)
return output_file
dataset_path = output_path
base_name = "dataset_generate"
success_files = list(dataset_path.glob(f"{base_name}_*.hdf5"))
success_files = [f for f in success_files if "_failed" not in f.name]
failed_files = list(dataset_path.glob(f"{base_name}_*_failed.hdf5"))
if success_files:
print(f"找到 {len(success_files)} 个成功分片文件,开始合并...")
success_input_files = [str(f) for f in success_files]
success_output = dataset_path / f"{base_name}.hdf5"
merged_dataset = merge_datasets(success_input_files, str(success_output))
if os.path.exists(success_output):
print(f"成功文件合并完成: {merged_dataset}")
for f in success_files:
f.unlink()
mimic_dataset_path = success_output
else:
print(f"合并失败或未找到输出文件 {success_output}。")
if failed_files:
print(f"找到 {len(failed_files)} 个失败分片文件,开始合并...")
failed_input_files = [str(f) for f in failed_files]
failed_output = dataset_path / f"{base_name}_failed.hdf5"
failed_merged_dataset = merge_datasets(failed_input_files, str(failed_output))
if os.path.exists(failed_output):
print(f"失败文件合并完成: {failed_merged_dataset}")
for f in failed_files:
f.unlink()
else:
print(f"合并失败或未找到输出文件 {failed_output}。")
视频重放:
ini
# 创建数据集目录
path = os.environ.get("ROBOT_LEARNING_LAB_PATH")
output_path_str = os.environ.get("EXTERNAL_STORAGE_PATH")+"/datasets"
output_path = Path(output_path_str)
output_path.mkdir(parents=True, exist_ok=True)
# 重放演示命令
command = f"""
cd {path} && \
./isaaclab.sh -p usecase/scripts/replay_demos_with_camera.py \
--task Isaac-Stack-Cube-Galbot-Left-Arm-Image-Based-v0 \
--dataset_file {output_path_str}/dataset_generate.hdf5 \
--num_envs 10 \
--video \
--video_path {output_path_str} \
--camera_view_list ego left_wrist right_wrist \
--headless
"""
print("需要先重播轨迹以生成相应的视频数据,用于视觉模态的输入。")
print("生成后的数据会保存在Isaac-Stack-Cube-Galbot-Left-Arm-Image-Based-v0/videos中。")
print(command)
# 执行
!{command}
转换为Lerobot格式:
ini
# 创建数据集目录
path = os.environ.get("ROBOT_LEARNING_LAB_PATH")
output_path_str = os.environ.get("EXTERNAL_STORAGE_PATH")+"/datasets"
output_path = Path(output_path_str)
output_path.mkdir(parents=True, exist_ok=True)
# 数据格式转换命令
command = f"""
cd {path} && \
./isaaclab.sh -p benchmarks/gr00t/convert_hdf5_to_lerobot_joint_space.py \
--data_root {output_path} \
--hdf5_filename dataset_generate.hdf5 \
--hdf5_file_path {output_path}/dataset_generate.hdf5 \
--lerobot_data_dir {output_path}/lerobot_joint_space
"""
print("需要再将所有模态的数据转成GR00T支持的 LeRobot 格式。")
print(command)
# 执行
!{command}
以下为一段头部与腕部合成视频的示例:
头部:视频演示>>
腕部:视频演示>>
5. 模仿学习
我们可以直接在DLC中启动分布式模仿学习任务:

环境配置:
镜像地址:使用以下预置的镜像,包含了GR00T N1.5模型训练所需的环境
bash
dsw-registry-vpc.cn-beijing.cr.aliyuncs.com/pai-training-algorithm/isaac-sim:gr00t-nb4-v1-20250916
数据集挂载:确保挂载了DSW中使用的自定义数据集,以及RobotLearningLab公共数据集
启动命令:
bash
cd /mnt/data/isaac_tmp/nb4/Isaac-GR00T &&
export WANDB_MODE=offline &&
NCCL_P2P_DISABLE=1 NCCL_IB_DISABLE=1 PYTHONPATH='./'
python scripts/gr00t_finetune.py
--base_model_path /mnt/data/isaac_tmp/nb4/GR00T-N1.5-3B --dataset-path
/mnt/data/isaac_tmp/nb4/datasets/lerobot_joint_space --num-gpus 2
--batch-size 2
--output-dir /mnt/data/isaac_tmp/nb4/datasets/joint_space_2_2 --max-steps 40000
--data-config galbot_joint_space
--video-backend decord
--no-tune-visual &&
sleep 30
其中:
-
--base_model_path /mnt/data/isaac_tmp/nb4/GR00T-N1.5-3B 代表使用GR00T-N1.5-3B模型作为基模进行模仿学习
-
--dataset-path /mnt/data/isaac_tmp/nb4/datasets/lerobot_joint_space 代表使用上述过程中扩增的数据作为训练集
-
--num-gpus 2 代表使用2张GPU作为训练资源,这里可以根据自己的资源配额余量自行调节
-
export WANDB_MODE=offline && NCCL_P2P_DISABLE=1 NCCL_IB_DISABLE=1 这里关闭了在线WandB和卡间互联,这里也可以根据实际环境和需求调整

观察任务日志,等待训练完成:

6. 闭环评估
待模仿学习过程完成后,我们可以组合使用2个DSW实例组合进行模型的闭环评估。
评估过程使用一个新的DSW作为服务端运行微调后的GR00T-N1.5模型,前面运行Isaac Lab的DSW作为客户端运行机器人本体,连接服务端指挥其动作。

6.1 启动服务端DSW
启动一个新的DSW实例,其中:
-
使用GR00T模型服务镜像
dsw-registry-vpc.${regionId}.cr.aliyuncs.com/pai-training-algorithm/isaac-sim:gr00t-nb4-v1-20250916
作为启动镜像
- 确保挂载RobotLearningLab_Dataset和保存微调后模型的自定义数据集

- 确保服务端DSW与客户端DSW位于同一个VPC内
启动后,在服务端DSW中执行以下代码获取服务端IP:
bash
PRI_IP=$(ifconfig eth1 | grep 'inet ' | awk '{print $2}') && echo "我的私网IP是: $PRI_IP"
此处以获取的服务端IP为10.0.0.207为例。
在服务端DSW中启动GR00T-N1.5模型服务:
bash
cd /mnt/data/isaac_tmp/nb4/Isaac-GR00T &&
python gr00t_inference_server.py --port 5555 --model_path /mnt/data/isaac_tmp/nb4/checkpoint-40000 --data_config galbot_joint_space
成功启动后可以观察到如下日志:

6.2 启动客户端DSW
可以复用数据扩增的DSW作为客户端DSW。
在客户端DSW的VNC桌面中,启动Isaac Lab环境,同时连接到服务端DSW的模型服务:
css
cd /workspace/RobotLearningLab && ./isaaclab.sh -p benchmarks/gr00t/gr00t_inference_client.py --server_port 5555 --server_host 10.0.0.207 --num_total_experiments 100 --num_success_steps 8 --policy_type joint_space --task Isaac-Stack-Cube-Galbot-Left-Arm-Joint-Position-Image-Based-v0
请注意将其中的10.0.0.207替换为服务端DSW的实际IP地址。
在启动的Isaac Lab窗口中,可以看到机器人本体在模型的指挥下,开始执行叠方块的动作。
两台DSW组合运行的整体效果如下:
7. 总结
本最佳实践综合使用PAI-DSW、DLC等模块,针对GR00T-N1.5-3B模型,进行了人工演示、数据扩增、模仿学习以及在环验证。相对于本系列的前3个最佳实践,本例可重点学习以下能力:
-
使用RobotLearningLab公共数据集
-
使用DLC进行分布式大规模数据扩增
-
使用DLC,针对较复杂的VLA模型进行分布式模仿学习
-
组合使用两台DSW进行服务端-客户端架构的软件在环验证
经过实际操作可以验证,经过模仿学习的GR00T-N1.5-3B模型,可以很好的模仿人类的"叠方块"动作,实现新技能的学习。