Add Xtrainer2 real robot source and Chinese handoff guide
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +1 -0
- real_robot/AGENTS.md +10 -0
- real_robot/README.md +72 -0
- real_robot/SOURCE_FILES.json +909 -0
- real_robot/source/LICENSE +24 -0
- real_robot/source/NOTICE +40 -0
- real_robot/source/README.md +222 -0
- real_robot/source/UPSTREAM.md +29 -0
- real_robot/source/configs/deepspeed_florence_8gpu.yaml +23 -0
- real_robot/source/configs/florence_xtrainer2_pack_phone_residual_20ep_to_60ep.yaml +101 -0
- real_robot/source/configs/florence_xtrainer2_pack_phone_residual_resume_smoke.yaml +104 -0
- real_robot/source/configs/florence_xtrainer2_pack_phone_smoke.yaml +117 -0
- real_robot/source/configs/florence_xtrainer2_pack_phone_uniform.yaml +116 -0
- real_robot/source/configs/florence_xtrainer2_pick_bottle_rel_aug_smoke.yaml +119 -0
- real_robot/source/configs/florence_xtrainer2_pick_bottle_rel_aug_uniform.yaml +123 -0
- real_robot/source/deployment/__init__.py +0 -0
- real_robot/source/deployment/model_server/__init__.py +0 -0
- real_robot/source/deployment/model_server/policy_norm_processor.py +538 -0
- real_robot/source/deployment/model_server/policy_wrapper.py +280 -0
- real_robot/source/deployment/model_server/server_policy.py +79 -0
- real_robot/source/deployment/model_server/tools/__init__.py +0 -0
- real_robot/source/deployment/model_server/tools/image_tools.py +123 -0
- real_robot/source/deployment/model_server/tools/msgpack_numpy.py +59 -0
- real_robot/source/deployment/model_server/tools/websocket_policy_client.py +75 -0
- real_robot/source/deployment/model_server/tools/websocket_policy_server.py +160 -0
- real_robot/source/deployment/model_server/xtrainer2_command.py +93 -0
- real_robot/source/examples/Xtrainer2/INFERENCE.md +4 -0
- real_robot/source/examples/Xtrainer2/README.md +26 -0
- real_robot/source/examples/Xtrainer2/ROBOT.md +18 -0
- real_robot/source/examples/Xtrainer2/train_files/build_pack_phone_support.py +86 -0
- real_robot/source/examples/Xtrainer2/train_files/build_pick_bottle_support.py +87 -0
- real_robot/source/examples/Xtrainer2/train_files/data_registry/data_config.py +179 -0
- real_robot/source/examples/Xtrainer2/train_files/modality.json +26 -0
- real_robot/source/examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh +111 -0
- real_robot/source/examples/Xtrainer2/train_files/run_xtrainer2_train.sh +98 -0
- real_robot/source/licenses/Diffusion-Policy-LICENSE +21 -0
- real_robot/source/licenses/Isaac-GR00T-LICENSE +190 -0
- real_robot/source/licenses/MODIFIED_FILES.txt +17 -0
- real_robot/source/licenses/OpenVLA-LICENSE +21 -0
- real_robot/source/licenses/X-VLA-LICENSE +201 -0
- real_robot/source/licenses/msgpack-numpy-LICENSE +35 -0
- real_robot/source/pyproject.toml +81 -0
- real_robot/source/requirements-qwen.txt +9 -0
- real_robot/source/requirements-xtrainer2.txt +10 -0
- real_robot/source/requirements.txt +34 -0
- real_robot/source/retime/__init__.py +6 -0
- real_robot/source/retime/__main__.py +4 -0
- real_robot/source/retime/checkpoint.py +19 -0
- real_robot/source/retime/cli.py +272 -0
- real_robot/source/retime/commands/__init__.py +1 -0
README.md
CHANGED
|
@@ -25,6 +25,7 @@ This package contains the server-authoritative Florence–Bridge OPTR code, its
|
|
| 25 |
|
| 26 |
## What is included
|
| 27 |
|
|
|
|
| 28 |
- `common_start_20k/checkpoints/steps_20000_accelerate/`: **full resumable state** (model, optimizer, scheduler metadata and per-rank RNG state). Start every new continuation arm here.
|
| 29 |
- `checkpoints/uniform/steps_20500_pytorch_model.pt`: model-only Uniform +500 snapshot.
|
| 30 |
- `checkpoints/residual/steps_20500_pytorch_model.pt`: model-only Residual-OPTR +500 snapshot.
|
|
|
|
| 25 |
|
| 26 |
## What is included
|
| 27 |
|
| 28 |
+
- [`real_robot/`](real_robot/README.md):Xtrainer2 真机数据转换、训练、双臂动作适配和 policy server 源码,以及中文交接说明。
|
| 29 |
- `common_start_20k/checkpoints/steps_20000_accelerate/`: **full resumable state** (model, optimizer, scheduler metadata and per-rank RNG state). Start every new continuation arm here.
|
| 30 |
- `checkpoints/uniform/steps_20500_pytorch_model.pt`: model-only Uniform +500 snapshot.
|
| 31 |
- `checkpoints/residual/steps_20500_pytorch_model.pt`: model-only Residual-OPTR +500 snapshot.
|
real_robot/AGENTS.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Xtrainer2 真机源码交接
|
| 2 |
+
|
| 3 |
+
本目录用于真机数据处理、训练与 policy server 部署。先阅读 `README.md` 和 `SOURCE_FILES.json`,从 `source/` 进入原始源码根目录。
|
| 4 |
+
|
| 5 |
+
- 使用本目录内的 `starVLA`、`retime` 和 `deployment` 包。
|
| 6 |
+
- 按所选真机配置核对 GPU 数、global batch、训练预算、数据混合和恢复 step。
|
| 7 |
+
- 模型权重、`config.yaml` 和 `dataset_statistics.json` 成套部署。
|
| 8 |
+
- 相机顺序为 high、left wrist、right wrist,机器人侧 16 维 xyzw 布局,模型内部 20 维 rotation-6D。
|
| 9 |
+
- 硬件驱动和执行循环接入实际机械臂客户端,执行动作前核对位置单位、夹爪约定、左右臂顺序和控制频率。
|
| 10 |
+
- 源码及来源文件保留上游内容,新增实验使用独立输出目录。
|
real_robot/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Xtrainer2 真机代码交接
|
| 2 |
+
|
| 3 |
+
这份源码来自你的 GitHub 仓库,整理在 HF 的独立目录 `real_robot/`。训练与推理服务以 `ReTime_source/main` 的 Xtrainer2 实现为主体,原开发仓库 `OPTR-StarVLA/main` 提供 HDF5 数据转换与 LeRobot 格式验证工具。逐文件来源见 [SOURCE_FILES.json](SOURCE_FILES.json)。
|
| 4 |
+
|
| 5 |
+
## 代码入口
|
| 6 |
+
|
| 7 |
+
| 用途 | 文件 |
|
| 8 |
+
|---|---|
|
| 9 |
+
| HDF5 → LeRobot 数据转换 | `source/scripts/convert_xtrainer2_hdf5_to_lerobot.py` |
|
| 10 |
+
| 数据格式与四元数约定验证 | `source/scripts/validate_xtrainer2_lerobot.py` |
|
| 11 |
+
| 数据注册、三相机及双臂状态配置 | `source/examples/Xtrainer2/train_files/data_registry/data_config.py` |
|
| 12 |
+
| 数据 support 构建 | `source/examples/Xtrainer2/train_files/build_pack_phone_support.py` 和 `build_pick_bottle_support.py` |
|
| 13 |
+
| 训练启动 | `source/examples/Xtrainer2/train_files/run_xtrainer2_train.sh` |
|
| 14 |
+
| ReTime proposal 与 continuation | `source/retime/cli.py` 和 `source/scripts/run_xtrainer2_arm.sh` |
|
| 15 |
+
| 真机 policy server 启动 | `source/examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh` |
|
| 16 |
+
| 双臂动作与四元数转换 | `source/deployment/model_server/xtrainer2_command.py` |
|
| 17 |
+
| WebSocket 客户端通信工具 | `source/deployment/model_server/tools/websocket_policy_client.py` |
|
| 18 |
+
|
| 19 |
+
`source/` 保留上游包布局。进入该目录再运行命令,使 `starVLA`、`retime` 和 `deployment` 使用这份源码。
|
| 20 |
+
|
| 21 |
+
## 环境与依赖
|
| 22 |
+
|
| 23 |
+
目标运行环境为 Linux、Python ≥ 3.10。原依赖列表为 [requirements.txt](source/requirements.txt) 和 [requirements-xtrainer2.txt](source/requirements-xtrainer2.txt),其中 PyTorch 为 2.7.1,Transformers 为 4.57.0,Accelerate 为 1.13.0。
|
| 24 |
+
|
| 25 |
+
双臂四元数与 rotation-6D 转换需要匹配 Python、PyTorch、CUDA 的 PyTorch3D。数据转换另需 `h5py`、`pandas`、Pillow 和可执行的 `ffmpeg`。按目标 GPU 环境安装相应依赖。
|
| 26 |
+
|
| 27 |
+
## 数据与训练配置
|
| 28 |
+
|
| 29 |
+
训练配置放在 `source/configs/`,包含 pack-phone、pick-bottle、smoke 和从 pack-phone common start 延续的配置。原配置使用 Florence、8 GPU、每卡 micro-batch 4、累积 4,global batch 为 128。pack-phone 的完整训练预算为 52,380 updates,warmup 为 5,000。continuation 配置从 `steps_17460_accelerate` 延续,训练 seed 为 117465,基础训练 seed 为 42。
|
| 30 |
+
|
| 31 |
+
运行前按配置设置 `FLORENCE_MODEL_PATH`、`XTRAINER2_DATA_ROOT`、`OPTR_RUN_ROOT` 及数据 support 路径。训练入口会检查配置实际引用的环境变量。具体实现见 [Xtrainer2 数据说明](source/examples/Xtrainer2/README.md)。
|
| 32 |
+
|
| 33 |
+
## 推理服务
|
| 34 |
+
|
| 35 |
+
准备真机 checkpoint 对应的运行目录,模型、配置、归一化统计保持成套。
|
| 36 |
+
|
| 37 |
+
```text
|
| 38 |
+
your_real_robot_run/
|
| 39 |
+
├── config.yaml
|
| 40 |
+
├── dataset_statistics.json
|
| 41 |
+
└── checkpoints/
|
| 42 |
+
└── your_model.pt
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
在下载后的 `real_robot/source` 中执行以下命令,将示例路径改为实际路径。
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
export OPTR_PYTHON=/path/to/your/python
|
| 49 |
+
export FLORENCE_MODEL_PATH=/path/to/Florence-2-large
|
| 50 |
+
export TMPDIR="$PWD/runtime_tmp"
|
| 51 |
+
mkdir -p "$TMPDIR"
|
| 52 |
+
|
| 53 |
+
bash examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh \
|
| 54 |
+
/path/to/your_real_robot_run/checkpoints/your_model.pt 0 10093
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
服务使用 BF16,并在连接握手中返回动作维数、相机顺序、四元数约定和 action chunk 长度。
|
| 58 |
+
|
| 59 |
+
## 机械臂客户端接口
|
| 60 |
+
|
| 61 |
+
相机顺序为 `cam_high`、`cam_left_wrist`、`cam_right_wrist`,图像使用 HWC RGB uint8。客户端提供 `image`、`lang` 和原始 16 维 `state`。双臂状态与动作按每臂位置 3 维、四元数 4 维、夹爪 1 维排列,机器人侧四元数顺序为 `xyzw`。服务端负责归一化和坐标表示转换,模型内部使用 20 维 rotation-6D 表示,返回动作使用机器人侧的 16 维布局。
|
| 62 |
+
|
| 63 |
+
pick-bottle 的相对末端动作由服务端结合当前状态还原为绝对动作。真实机械臂驱动、相机采集和执行循环由独立硬件客户端完成,需接入师兄实际使用的控制程序。上游接口说明见 [ROBOT.md](source/examples/Xtrainer2/ROBOT.md)。
|
| 64 |
+
|
| 65 |
+
## 本次交接验证
|
| 66 |
+
|
| 67 |
+
- 129 个源文件与来源清单的字节数一致,逐文件来源见 `SOURCE_FILES.json`。
|
| 68 |
+
- 95 个 Python 文件通过语法检查,7 个 YAML、1 个 JSON 和 1 个 TOML 文件通过解析检查。
|
| 69 |
+
- 8 个 shell 入口通过 `bash -n`,`python -m retime --help` 可正常运行。
|
| 70 |
+
- 使用 jlu66 现有 Python 环境在 CPU 上执行 13 项配置测试,全部通过,覆盖双臂维数、global batch、梯度累积、ZeRO-3 权重保存设置、训练种子和 continuation 参数。
|
| 71 |
+
|
| 72 |
+
实际模型加载、GPU 推理和机械臂动作执行需在配套设备与 checkpoint 上验证。
|
real_robot/SOURCE_FILES.json
ADDED
|
@@ -0,0 +1,909 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"description": "Xtrainer2 real-robot training and policy-serving source handoff",
|
| 4 |
+
"files": [
|
| 5 |
+
{
|
| 6 |
+
"path": "source/configs/deepspeed_florence_8gpu.yaml",
|
| 7 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 8 |
+
"ref": "main",
|
| 9 |
+
"source_path": "configs/deepspeed_florence_8gpu.yaml",
|
| 10 |
+
"bytes": 702
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"path": "source/configs/florence_xtrainer2_pack_phone_residual_20ep_to_60ep.yaml",
|
| 14 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 15 |
+
"ref": "main",
|
| 16 |
+
"source_path": "configs/florence_xtrainer2_pack_phone_residual_20ep_to_60ep.yaml",
|
| 17 |
+
"bytes": 2837
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"path": "source/configs/florence_xtrainer2_pack_phone_residual_resume_smoke.yaml",
|
| 21 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 22 |
+
"ref": "main",
|
| 23 |
+
"source_path": "configs/florence_xtrainer2_pack_phone_residual_resume_smoke.yaml",
|
| 24 |
+
"bytes": 2967
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"path": "source/configs/florence_xtrainer2_pack_phone_smoke.yaml",
|
| 28 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 29 |
+
"ref": "main",
|
| 30 |
+
"source_path": "configs/florence_xtrainer2_pack_phone_smoke.yaml",
|
| 31 |
+
"bytes": 3954
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"path": "source/configs/florence_xtrainer2_pack_phone_uniform.yaml",
|
| 35 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 36 |
+
"ref": "main",
|
| 37 |
+
"source_path": "configs/florence_xtrainer2_pack_phone_uniform.yaml",
|
| 38 |
+
"bytes": 3898
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"path": "source/configs/florence_xtrainer2_pick_bottle_rel_aug_smoke.yaml",
|
| 42 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 43 |
+
"ref": "main",
|
| 44 |
+
"source_path": "configs/florence_xtrainer2_pick_bottle_rel_aug_smoke.yaml",
|
| 45 |
+
"bytes": 3703
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"path": "source/configs/florence_xtrainer2_pick_bottle_rel_aug_uniform.yaml",
|
| 49 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 50 |
+
"ref": "main",
|
| 51 |
+
"source_path": "configs/florence_xtrainer2_pick_bottle_rel_aug_uniform.yaml",
|
| 52 |
+
"bytes": 4104
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"path": "source/deployment/__init__.py",
|
| 56 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 57 |
+
"ref": "main",
|
| 58 |
+
"source_path": "deployment/__init__.py",
|
| 59 |
+
"bytes": 0
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"path": "source/deployment/model_server/__init__.py",
|
| 63 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 64 |
+
"ref": "main",
|
| 65 |
+
"source_path": "deployment/model_server/__init__.py",
|
| 66 |
+
"bytes": 0
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"path": "source/deployment/model_server/policy_norm_processor.py",
|
| 70 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 71 |
+
"ref": "main",
|
| 72 |
+
"source_path": "deployment/model_server/policy_norm_processor.py",
|
| 73 |
+
"bytes": 22282
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"path": "source/deployment/model_server/policy_wrapper.py",
|
| 77 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 78 |
+
"ref": "main",
|
| 79 |
+
"source_path": "deployment/model_server/policy_wrapper.py",
|
| 80 |
+
"bytes": 12974
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"path": "source/deployment/model_server/server_policy.py",
|
| 84 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 85 |
+
"ref": "main",
|
| 86 |
+
"source_path": "deployment/model_server/server_policy.py",
|
| 87 |
+
"bytes": 2741
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"path": "source/deployment/model_server/tools/__init__.py",
|
| 91 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 92 |
+
"ref": "main",
|
| 93 |
+
"source_path": "deployment/model_server/tools/__init__.py",
|
| 94 |
+
"bytes": 0
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"path": "source/deployment/model_server/tools/image_tools.py",
|
| 98 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 99 |
+
"ref": "main",
|
| 100 |
+
"source_path": "deployment/model_server/tools/image_tools.py",
|
| 101 |
+
"bytes": 4765
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"path": "source/deployment/model_server/tools/msgpack_numpy.py",
|
| 105 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 106 |
+
"ref": "main",
|
| 107 |
+
"source_path": "deployment/model_server/tools/msgpack_numpy.py",
|
| 108 |
+
"bytes": 2016
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"path": "source/deployment/model_server/tools/websocket_policy_client.py",
|
| 112 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 113 |
+
"ref": "main",
|
| 114 |
+
"source_path": "deployment/model_server/tools/websocket_policy_client.py",
|
| 115 |
+
"bytes": 2728
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"path": "source/deployment/model_server/tools/websocket_policy_server.py",
|
| 119 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 120 |
+
"ref": "main",
|
| 121 |
+
"source_path": "deployment/model_server/tools/websocket_policy_server.py",
|
| 122 |
+
"bytes": 5813
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"path": "source/deployment/model_server/xtrainer2_command.py",
|
| 126 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 127 |
+
"ref": "main",
|
| 128 |
+
"source_path": "deployment/model_server/xtrainer2_command.py",
|
| 129 |
+
"bytes": 3595
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"path": "source/examples/Xtrainer2/INFERENCE.md",
|
| 133 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 134 |
+
"ref": "main",
|
| 135 |
+
"source_path": "examples/Xtrainer2/INFERENCE.md",
|
| 136 |
+
"bytes": 180
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"path": "source/examples/Xtrainer2/README.md",
|
| 140 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 141 |
+
"ref": "main",
|
| 142 |
+
"source_path": "examples/Xtrainer2/README.md",
|
| 143 |
+
"bytes": 1511
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"path": "source/examples/Xtrainer2/ROBOT.md",
|
| 147 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 148 |
+
"ref": "main",
|
| 149 |
+
"source_path": "examples/Xtrainer2/ROBOT.md",
|
| 150 |
+
"bytes": 1124
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"path": "source/examples/Xtrainer2/train_files/build_pack_phone_support.py",
|
| 154 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 155 |
+
"ref": "main",
|
| 156 |
+
"source_path": "examples/Xtrainer2/train_files/build_pack_phone_support.py",
|
| 157 |
+
"bytes": 3018
|
| 158 |
+
},
|
| 159 |
+
{
|
| 160 |
+
"path": "source/examples/Xtrainer2/train_files/build_pick_bottle_support.py",
|
| 161 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 162 |
+
"ref": "main",
|
| 163 |
+
"source_path": "examples/Xtrainer2/train_files/build_pick_bottle_support.py",
|
| 164 |
+
"bytes": 3071
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"path": "source/examples/Xtrainer2/train_files/data_registry/data_config.py",
|
| 168 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 169 |
+
"ref": "main",
|
| 170 |
+
"source_path": "examples/Xtrainer2/train_files/data_registry/data_config.py",
|
| 171 |
+
"bytes": 6348
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"path": "source/examples/Xtrainer2/train_files/modality.json",
|
| 175 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 176 |
+
"ref": "main",
|
| 177 |
+
"source_path": "examples/Xtrainer2/train_files/modality.json",
|
| 178 |
+
"bytes": 1877
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"path": "source/examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh",
|
| 182 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 183 |
+
"ref": "main",
|
| 184 |
+
"source_path": "examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh",
|
| 185 |
+
"bytes": 4398
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"path": "source/examples/Xtrainer2/train_files/run_xtrainer2_train.sh",
|
| 189 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 190 |
+
"ref": "main",
|
| 191 |
+
"source_path": "examples/Xtrainer2/train_files/run_xtrainer2_train.sh",
|
| 192 |
+
"bytes": 3016
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"path": "source/LICENSE",
|
| 196 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 197 |
+
"ref": "main",
|
| 198 |
+
"source_path": "LICENSE",
|
| 199 |
+
"bytes": 1387
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"path": "source/licenses/Diffusion-Policy-LICENSE",
|
| 203 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 204 |
+
"ref": "main",
|
| 205 |
+
"source_path": "licenses/Diffusion-Policy-LICENSE",
|
| 206 |
+
"bytes": 1106
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
"path": "source/licenses/Isaac-GR00T-LICENSE",
|
| 210 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 211 |
+
"ref": "main",
|
| 212 |
+
"source_path": "licenses/Isaac-GR00T-LICENSE",
|
| 213 |
+
"bytes": 10808
|
| 214 |
+
},
|
| 215 |
+
{
|
| 216 |
+
"path": "source/licenses/MODIFIED_FILES.txt",
|
| 217 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 218 |
+
"ref": "main",
|
| 219 |
+
"source_path": "licenses/MODIFIED_FILES.txt",
|
| 220 |
+
"bytes": 765
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"path": "source/licenses/msgpack-numpy-LICENSE",
|
| 224 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 225 |
+
"ref": "main",
|
| 226 |
+
"source_path": "licenses/msgpack-numpy-LICENSE",
|
| 227 |
+
"bytes": 1551
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"path": "source/licenses/OpenVLA-LICENSE",
|
| 231 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 232 |
+
"ref": "main",
|
| 233 |
+
"source_path": "licenses/OpenVLA-LICENSE",
|
| 234 |
+
"bytes": 1105
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"path": "source/licenses/X-VLA-LICENSE",
|
| 238 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 239 |
+
"ref": "main",
|
| 240 |
+
"source_path": "licenses/X-VLA-LICENSE",
|
| 241 |
+
"bytes": 11357
|
| 242 |
+
},
|
| 243 |
+
{
|
| 244 |
+
"path": "source/NOTICE",
|
| 245 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 246 |
+
"ref": "main",
|
| 247 |
+
"source_path": "NOTICE",
|
| 248 |
+
"bytes": 2060
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"path": "source/pyproject.toml",
|
| 252 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 253 |
+
"ref": "main",
|
| 254 |
+
"source_path": "pyproject.toml",
|
| 255 |
+
"bytes": 1790
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"path": "source/README.md",
|
| 259 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 260 |
+
"ref": "main",
|
| 261 |
+
"source_path": "README.md",
|
| 262 |
+
"bytes": 10922
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"path": "source/requirements-qwen.txt",
|
| 266 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 267 |
+
"ref": "main",
|
| 268 |
+
"source_path": "requirements-qwen.txt",
|
| 269 |
+
"bytes": 269
|
| 270 |
+
},
|
| 271 |
+
{
|
| 272 |
+
"path": "source/requirements-xtrainer2.txt",
|
| 273 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 274 |
+
"ref": "main",
|
| 275 |
+
"source_path": "requirements-xtrainer2.txt",
|
| 276 |
+
"bytes": 440
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"path": "source/requirements.txt",
|
| 280 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 281 |
+
"ref": "main",
|
| 282 |
+
"source_path": "requirements.txt",
|
| 283 |
+
"bytes": 574
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"path": "source/retime/__init__.py",
|
| 287 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 288 |
+
"ref": "main",
|
| 289 |
+
"source_path": "retime/__init__.py",
|
| 290 |
+
"bytes": 160
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"path": "source/retime/__main__.py",
|
| 294 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 295 |
+
"ref": "main",
|
| 296 |
+
"source_path": "retime/__main__.py",
|
| 297 |
+
"bytes": 79
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"path": "source/retime/checkpoint.py",
|
| 301 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 302 |
+
"ref": "main",
|
| 303 |
+
"source_path": "retime/checkpoint.py",
|
| 304 |
+
"bytes": 864
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"path": "source/retime/cli.py",
|
| 308 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 309 |
+
"ref": "main",
|
| 310 |
+
"source_path": "retime/cli.py",
|
| 311 |
+
"bytes": 13905
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
"path": "source/retime/commands/__init__.py",
|
| 315 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 316 |
+
"ref": "main",
|
| 317 |
+
"source_path": "retime/commands/__init__.py",
|
| 318 |
+
"bytes": 43
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
"path": "source/retime/commands/proposal.py",
|
| 322 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 323 |
+
"ref": "main",
|
| 324 |
+
"source_path": "retime/commands/proposal.py",
|
| 325 |
+
"bytes": 25822
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"path": "source/retime/commands/shuffle.py",
|
| 329 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 330 |
+
"ref": "main",
|
| 331 |
+
"source_path": "retime/commands/shuffle.py",
|
| 332 |
+
"bytes": 1482
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
"path": "source/retime/objective.py",
|
| 336 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 337 |
+
"ref": "main",
|
| 338 |
+
"source_path": "retime/objective.py",
|
| 339 |
+
"bytes": 6506
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"path": "source/retime/prepare.py",
|
| 343 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 344 |
+
"ref": "main",
|
| 345 |
+
"source_path": "retime/prepare.py",
|
| 346 |
+
"bytes": 1145
|
| 347 |
+
},
|
| 348 |
+
{
|
| 349 |
+
"path": "source/retime/pretrain.py",
|
| 350 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 351 |
+
"ref": "main",
|
| 352 |
+
"source_path": "retime/pretrain.py",
|
| 353 |
+
"bytes": 7183
|
| 354 |
+
},
|
| 355 |
+
{
|
| 356 |
+
"path": "source/retime/proposal.py",
|
| 357 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 358 |
+
"ref": "main",
|
| 359 |
+
"source_path": "retime/proposal.py",
|
| 360 |
+
"bytes": 9294
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"path": "source/retime/sampler.py",
|
| 364 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 365 |
+
"ref": "main",
|
| 366 |
+
"source_path": "retime/sampler.py",
|
| 367 |
+
"bytes": 13229
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
"path": "source/retime/scoring/__init__.py",
|
| 371 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 372 |
+
"ref": "main",
|
| 373 |
+
"source_path": "retime/scoring/__init__.py",
|
| 374 |
+
"bytes": 218
|
| 375 |
+
},
|
| 376 |
+
{
|
| 377 |
+
"path": "source/retime/scoring/cheap_proposals.py",
|
| 378 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 379 |
+
"ref": "main",
|
| 380 |
+
"source_path": "retime/scoring/cheap_proposals.py",
|
| 381 |
+
"bytes": 7068
|
| 382 |
+
},
|
| 383 |
+
{
|
| 384 |
+
"path": "source/retime/scoring/cnf_likelihood.py",
|
| 385 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 386 |
+
"ref": "main",
|
| 387 |
+
"source_path": "retime/scoring/cnf_likelihood.py",
|
| 388 |
+
"bytes": 10056
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"path": "source/retime/scoring/discrete_flow_likelihood.py",
|
| 392 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 393 |
+
"ref": "main",
|
| 394 |
+
"source_path": "retime/scoring/discrete_flow_likelihood.py",
|
| 395 |
+
"bytes": 7055
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"path": "source/retime/scoring/flow_residual.py",
|
| 399 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 400 |
+
"ref": "main",
|
| 401 |
+
"source_path": "retime/scoring/flow_residual.py",
|
| 402 |
+
"bytes": 9357
|
| 403 |
+
},
|
| 404 |
+
{
|
| 405 |
+
"path": "source/retime/scoring/partial_gradient.py",
|
| 406 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 407 |
+
"ref": "main",
|
| 408 |
+
"source_path": "retime/scoring/partial_gradient.py",
|
| 409 |
+
"bytes": 4067
|
| 410 |
+
},
|
| 411 |
+
{
|
| 412 |
+
"path": "source/retime/scoring/residual_cache.py",
|
| 413 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 414 |
+
"ref": "main",
|
| 415 |
+
"source_path": "retime/scoring/residual_cache.py",
|
| 416 |
+
"bytes": 2815
|
| 417 |
+
},
|
| 418 |
+
{
|
| 419 |
+
"path": "source/retime/scoring/shuffle.py",
|
| 420 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 421 |
+
"ref": "main",
|
| 422 |
+
"source_path": "retime/scoring/shuffle.py",
|
| 423 |
+
"bytes": 4229
|
| 424 |
+
},
|
| 425 |
+
{
|
| 426 |
+
"path": "source/retime/scoring/starvla_likelihood.py",
|
| 427 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 428 |
+
"ref": "main",
|
| 429 |
+
"source_path": "retime/scoring/starvla_likelihood.py",
|
| 430 |
+
"bytes": 5643
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"path": "source/retime/scoring/utility_audit.py",
|
| 434 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 435 |
+
"ref": "main",
|
| 436 |
+
"source_path": "retime/scoring/utility_audit.py",
|
| 437 |
+
"bytes": 18264
|
| 438 |
+
},
|
| 439 |
+
{
|
| 440 |
+
"path": "source/scripts/build_residual_temporal_proposal.py",
|
| 441 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 442 |
+
"ref": "main",
|
| 443 |
+
"source_path": "scripts/build_residual_temporal_proposal.py",
|
| 444 |
+
"bytes": 243
|
| 445 |
+
},
|
| 446 |
+
{
|
| 447 |
+
"path": "source/scripts/build_shuffle_temporal_proposal.py",
|
| 448 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 449 |
+
"ref": "main",
|
| 450 |
+
"source_path": "scripts/build_shuffle_temporal_proposal.py",
|
| 451 |
+
"bytes": 252
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"path": "source/scripts/convert_xtrainer2_hdf5_to_lerobot.py",
|
| 455 |
+
"repository": "miaochengy-pngu/OPTR-StarVLA",
|
| 456 |
+
"ref": "main",
|
| 457 |
+
"source_path": "scripts/convert_xtrainer2_hdf5_to_lerobot.py",
|
| 458 |
+
"bytes": 26880
|
| 459 |
+
},
|
| 460 |
+
{
|
| 461 |
+
"path": "source/scripts/launch_pack_phone_optr_proposal.sh",
|
| 462 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 463 |
+
"ref": "main",
|
| 464 |
+
"source_path": "scripts/launch_pack_phone_optr_proposal.sh",
|
| 465 |
+
"bytes": 247
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"path": "source/scripts/launch_pack_phone_residual_20ep_to_60ep.sh",
|
| 469 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 470 |
+
"ref": "main",
|
| 471 |
+
"source_path": "scripts/launch_pack_phone_residual_20ep_to_60ep.sh",
|
| 472 |
+
"bytes": 2187
|
| 473 |
+
},
|
| 474 |
+
{
|
| 475 |
+
"path": "source/scripts/launch_pack_phone_uniform.sh",
|
| 476 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 477 |
+
"ref": "main",
|
| 478 |
+
"source_path": "scripts/launch_pack_phone_uniform.sh",
|
| 479 |
+
"bytes": 1885
|
| 480 |
+
},
|
| 481 |
+
{
|
| 482 |
+
"path": "source/scripts/launch_pick_bottle_rel_aug_smoke.sh",
|
| 483 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 484 |
+
"ref": "main",
|
| 485 |
+
"source_path": "scripts/launch_pick_bottle_rel_aug_smoke.sh",
|
| 486 |
+
"bytes": 1156
|
| 487 |
+
},
|
| 488 |
+
{
|
| 489 |
+
"path": "source/scripts/launch_pick_bottle_rel_aug_uniform.sh",
|
| 490 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 491 |
+
"ref": "main",
|
| 492 |
+
"source_path": "scripts/launch_pick_bottle_rel_aug_uniform.sh",
|
| 493 |
+
"bytes": 1441
|
| 494 |
+
},
|
| 495 |
+
{
|
| 496 |
+
"path": "source/scripts/run_xtrainer2_arm.sh",
|
| 497 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 498 |
+
"ref": "main",
|
| 499 |
+
"source_path": "scripts/run_xtrainer2_arm.sh",
|
| 500 |
+
"bytes": 6276
|
| 501 |
+
},
|
| 502 |
+
{
|
| 503 |
+
"path": "source/scripts/validate_xtrainer2_lerobot.py",
|
| 504 |
+
"repository": "miaochengy-pngu/OPTR-StarVLA",
|
| 505 |
+
"ref": "main",
|
| 506 |
+
"source_path": "scripts/validate_xtrainer2_lerobot.py",
|
| 507 |
+
"bytes": 5871
|
| 508 |
+
},
|
| 509 |
+
{
|
| 510 |
+
"path": "source/starVLA/__init__.py",
|
| 511 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 512 |
+
"ref": "main",
|
| 513 |
+
"source_path": "starVLA/__init__.py",
|
| 514 |
+
"bytes": 0
|
| 515 |
+
},
|
| 516 |
+
{
|
| 517 |
+
"path": "source/starVLA/dataloader/__init__.py",
|
| 518 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 519 |
+
"ref": "main",
|
| 520 |
+
"source_path": "starVLA/dataloader/__init__.py",
|
| 521 |
+
"bytes": 4672
|
| 522 |
+
},
|
| 523 |
+
{
|
| 524 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/__init__.py",
|
| 525 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 526 |
+
"ref": "main",
|
| 527 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/__init__.py",
|
| 528 |
+
"bytes": 0
|
| 529 |
+
},
|
| 530 |
+
{
|
| 531 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/data_config.py",
|
| 532 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 533 |
+
"ref": "main",
|
| 534 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/data_config.py",
|
| 535 |
+
"bytes": 35501
|
| 536 |
+
},
|
| 537 |
+
{
|
| 538 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/datasets.py",
|
| 539 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 540 |
+
"ref": "main",
|
| 541 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/datasets.py",
|
| 542 |
+
"bytes": 144422
|
| 543 |
+
},
|
| 544 |
+
{
|
| 545 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/embodiment_tags.py",
|
| 546 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 547 |
+
"ref": "main",
|
| 548 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/embodiment_tags.py",
|
| 549 |
+
"bytes": 2459
|
| 550 |
+
},
|
| 551 |
+
{
|
| 552 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/mixtures.py",
|
| 553 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 554 |
+
"ref": "main",
|
| 555 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/mixtures.py",
|
| 556 |
+
"bytes": 19387
|
| 557 |
+
},
|
| 558 |
+
{
|
| 559 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/registry.py",
|
| 560 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 561 |
+
"ref": "main",
|
| 562 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/registry.py",
|
| 563 |
+
"bytes": 5960
|
| 564 |
+
},
|
| 565 |
+
{
|
| 566 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/schema.py",
|
| 567 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 568 |
+
"ref": "main",
|
| 569 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/schema.py",
|
| 570 |
+
"bytes": 8590
|
| 571 |
+
},
|
| 572 |
+
{
|
| 573 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/transform/__init__.py",
|
| 574 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 575 |
+
"ref": "main",
|
| 576 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/transform/__init__.py",
|
| 577 |
+
"bytes": 1261
|
| 578 |
+
},
|
| 579 |
+
{
|
| 580 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/transform/base.py",
|
| 581 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 582 |
+
"ref": "main",
|
| 583 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/transform/base.py",
|
| 584 |
+
"bytes": 5154
|
| 585 |
+
},
|
| 586 |
+
{
|
| 587 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/transform/concat.py",
|
| 588 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 589 |
+
"ref": "main",
|
| 590 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/transform/concat.py",
|
| 591 |
+
"bytes": 9371
|
| 592 |
+
},
|
| 593 |
+
{
|
| 594 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/transform/state_action.py",
|
| 595 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 596 |
+
"ref": "main",
|
| 597 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/transform/state_action.py",
|
| 598 |
+
"bytes": 27803
|
| 599 |
+
},
|
| 600 |
+
{
|
| 601 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/transform/video.py",
|
| 602 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 603 |
+
"ref": "main",
|
| 604 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/transform/video.py",
|
| 605 |
+
"bytes": 24512
|
| 606 |
+
},
|
| 607 |
+
{
|
| 608 |
+
"path": "source/starVLA/dataloader/gr00t_lerobot/video.py",
|
| 609 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 610 |
+
"ref": "main",
|
| 611 |
+
"source_path": "starVLA/dataloader/gr00t_lerobot/video.py",
|
| 612 |
+
"bytes": 14043
|
| 613 |
+
},
|
| 614 |
+
{
|
| 615 |
+
"path": "source/starVLA/dataloader/infoflow_sampler.py",
|
| 616 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 617 |
+
"ref": "main",
|
| 618 |
+
"source_path": "starVLA/dataloader/infoflow_sampler.py",
|
| 619 |
+
"bytes": 180
|
| 620 |
+
},
|
| 621 |
+
{
|
| 622 |
+
"path": "source/starVLA/dataloader/lerobot_datasets.py",
|
| 623 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 624 |
+
"ref": "main",
|
| 625 |
+
"source_path": "starVLA/dataloader/lerobot_datasets.py",
|
| 626 |
+
"bytes": 6050
|
| 627 |
+
},
|
| 628 |
+
{
|
| 629 |
+
"path": "source/starVLA/dataloader/relative_pose.py",
|
| 630 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 631 |
+
"ref": "main",
|
| 632 |
+
"source_path": "starVLA/dataloader/relative_pose.py",
|
| 633 |
+
"bytes": 3359
|
| 634 |
+
},
|
| 635 |
+
{
|
| 636 |
+
"path": "source/starVLA/dataloader/temporal_proposal.py",
|
| 637 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 638 |
+
"ref": "main",
|
| 639 |
+
"source_path": "starVLA/dataloader/temporal_proposal.py",
|
| 640 |
+
"bytes": 181
|
| 641 |
+
},
|
| 642 |
+
{
|
| 643 |
+
"path": "source/starVLA/model/framework/__init__.py",
|
| 644 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 645 |
+
"ref": "main",
|
| 646 |
+
"source_path": "starVLA/model/framework/__init__.py",
|
| 647 |
+
"bytes": 202
|
| 648 |
+
},
|
| 649 |
+
{
|
| 650 |
+
"path": "source/starVLA/model/framework/base_framework.py",
|
| 651 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 652 |
+
"ref": "main",
|
| 653 |
+
"source_path": "starVLA/model/framework/base_framework.py",
|
| 654 |
+
"bytes": 11351
|
| 655 |
+
},
|
| 656 |
+
{
|
| 657 |
+
"path": "source/starVLA/model/framework/share_tools.py",
|
| 658 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 659 |
+
"ref": "main",
|
| 660 |
+
"source_path": "starVLA/model/framework/share_tools.py",
|
| 661 |
+
"bytes": 21898
|
| 662 |
+
},
|
| 663 |
+
{
|
| 664 |
+
"path": "source/starVLA/model/framework/VLM4A/__init__.py",
|
| 665 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 666 |
+
"ref": "main",
|
| 667 |
+
"source_path": "starVLA/model/framework/VLM4A/__init__.py",
|
| 668 |
+
"bytes": 296
|
| 669 |
+
},
|
| 670 |
+
{
|
| 671 |
+
"path": "source/starVLA/model/framework/VLM4A/QwenGR00T.py",
|
| 672 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 673 |
+
"ref": "main",
|
| 674 |
+
"source_path": "starVLA/model/framework/VLM4A/QwenGR00T.py",
|
| 675 |
+
"bytes": 15342
|
| 676 |
+
},
|
| 677 |
+
{
|
| 678 |
+
"path": "source/starVLA/model/modules/action_model/__init__.py",
|
| 679 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 680 |
+
"ref": "main",
|
| 681 |
+
"source_path": "starVLA/model/modules/action_model/__init__.py",
|
| 682 |
+
"bytes": 122
|
| 683 |
+
},
|
| 684 |
+
{
|
| 685 |
+
"path": "source/starVLA/model/modules/action_model/flow_matching_head/__init__.py",
|
| 686 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 687 |
+
"ref": "main",
|
| 688 |
+
"source_path": "starVLA/model/modules/action_model/flow_matching_head/__init__.py",
|
| 689 |
+
"bytes": 679
|
| 690 |
+
},
|
| 691 |
+
{
|
| 692 |
+
"path": "source/starVLA/model/modules/action_model/flow_matching_head/action_encoder.py",
|
| 693 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 694 |
+
"ref": "main",
|
| 695 |
+
"source_path": "starVLA/model/modules/action_model/flow_matching_head/action_encoder.py",
|
| 696 |
+
"bytes": 3431
|
| 697 |
+
},
|
| 698 |
+
{
|
| 699 |
+
"path": "source/starVLA/model/modules/action_model/flow_matching_head/cross_attention_dit.py",
|
| 700 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 701 |
+
"ref": "main",
|
| 702 |
+
"source_path": "starVLA/model/modules/action_model/flow_matching_head/cross_attention_dit.py",
|
| 703 |
+
"bytes": 14773
|
| 704 |
+
},
|
| 705 |
+
{
|
| 706 |
+
"path": "source/starVLA/model/modules/action_model/GR00T_ActionHeader.py",
|
| 707 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 708 |
+
"ref": "main",
|
| 709 |
+
"source_path": "starVLA/model/modules/action_model/GR00T_ActionHeader.py",
|
| 710 |
+
"bytes": 21024
|
| 711 |
+
},
|
| 712 |
+
{
|
| 713 |
+
"path": "source/starVLA/model/modules/vlm/__init__.py",
|
| 714 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 715 |
+
"ref": "main",
|
| 716 |
+
"source_path": "starVLA/model/modules/vlm/__init__.py",
|
| 717 |
+
"bytes": 481
|
| 718 |
+
},
|
| 719 |
+
{
|
| 720 |
+
"path": "source/starVLA/model/modules/vlm/Florence2.py",
|
| 721 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 722 |
+
"ref": "main",
|
| 723 |
+
"source_path": "starVLA/model/modules/vlm/Florence2.py",
|
| 724 |
+
"bytes": 12888
|
| 725 |
+
},
|
| 726 |
+
{
|
| 727 |
+
"path": "source/starVLA/model/modules/vlm/QWen3.py",
|
| 728 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 729 |
+
"ref": "main",
|
| 730 |
+
"source_path": "starVLA/model/modules/vlm/QWen3.py",
|
| 731 |
+
"bytes": 7253
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"path": "source/starVLA/model/tools.py",
|
| 735 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 736 |
+
"ref": "main",
|
| 737 |
+
"source_path": "starVLA/model/tools.py",
|
| 738 |
+
"bytes": 17659
|
| 739 |
+
},
|
| 740 |
+
{
|
| 741 |
+
"path": "source/starVLA/scoring/__init__.py",
|
| 742 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 743 |
+
"ref": "main",
|
| 744 |
+
"source_path": "starVLA/scoring/__init__.py",
|
| 745 |
+
"bytes": 209
|
| 746 |
+
},
|
| 747 |
+
{
|
| 748 |
+
"path": "source/starVLA/scoring/cheap_proposals.py",
|
| 749 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 750 |
+
"ref": "main",
|
| 751 |
+
"source_path": "starVLA/scoring/cheap_proposals.py",
|
| 752 |
+
"bytes": 196
|
| 753 |
+
},
|
| 754 |
+
{
|
| 755 |
+
"path": "source/starVLA/scoring/cnf_likelihood.py",
|
| 756 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 757 |
+
"ref": "main",
|
| 758 |
+
"source_path": "starVLA/scoring/cnf_likelihood.py",
|
| 759 |
+
"bytes": 195
|
| 760 |
+
},
|
| 761 |
+
{
|
| 762 |
+
"path": "source/starVLA/scoring/discrete_flow_likelihood.py",
|
| 763 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 764 |
+
"ref": "main",
|
| 765 |
+
"source_path": "starVLA/scoring/discrete_flow_likelihood.py",
|
| 766 |
+
"bytes": 205
|
| 767 |
+
},
|
| 768 |
+
{
|
| 769 |
+
"path": "source/starVLA/scoring/flow_residual.py",
|
| 770 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 771 |
+
"ref": "main",
|
| 772 |
+
"source_path": "starVLA/scoring/flow_residual.py",
|
| 773 |
+
"bytes": 194
|
| 774 |
+
},
|
| 775 |
+
{
|
| 776 |
+
"path": "source/starVLA/scoring/partial_gradient.py",
|
| 777 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 778 |
+
"ref": "main",
|
| 779 |
+
"source_path": "starVLA/scoring/partial_gradient.py",
|
| 780 |
+
"bytes": 197
|
| 781 |
+
},
|
| 782 |
+
{
|
| 783 |
+
"path": "source/starVLA/scoring/residual_cache.py",
|
| 784 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 785 |
+
"ref": "main",
|
| 786 |
+
"source_path": "starVLA/scoring/residual_cache.py",
|
| 787 |
+
"bytes": 195
|
| 788 |
+
},
|
| 789 |
+
{
|
| 790 |
+
"path": "source/starVLA/scoring/shuffle.py",
|
| 791 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 792 |
+
"ref": "main",
|
| 793 |
+
"source_path": "starVLA/scoring/shuffle.py",
|
| 794 |
+
"bytes": 188
|
| 795 |
+
},
|
| 796 |
+
{
|
| 797 |
+
"path": "source/starVLA/scoring/starvla_likelihood.py",
|
| 798 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 799 |
+
"ref": "main",
|
| 800 |
+
"source_path": "starVLA/scoring/starvla_likelihood.py",
|
| 801 |
+
"bytes": 199
|
| 802 |
+
},
|
| 803 |
+
{
|
| 804 |
+
"path": "source/starVLA/scoring/utility_audit.py",
|
| 805 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 806 |
+
"ref": "main",
|
| 807 |
+
"source_path": "starVLA/scoring/utility_audit.py",
|
| 808 |
+
"bytes": 194
|
| 809 |
+
},
|
| 810 |
+
{
|
| 811 |
+
"path": "source/starVLA/training/__init__.py",
|
| 812 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 813 |
+
"ref": "main",
|
| 814 |
+
"source_path": "starVLA/training/__init__.py",
|
| 815 |
+
"bytes": 0
|
| 816 |
+
},
|
| 817 |
+
{
|
| 818 |
+
"path": "source/starVLA/training/infoflow.py",
|
| 819 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 820 |
+
"ref": "main",
|
| 821 |
+
"source_path": "starVLA/training/infoflow.py",
|
| 822 |
+
"bytes": 182
|
| 823 |
+
},
|
| 824 |
+
{
|
| 825 |
+
"path": "source/starVLA/training/train_starvla.py",
|
| 826 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 827 |
+
"ref": "main",
|
| 828 |
+
"source_path": "starVLA/training/train_starvla.py",
|
| 829 |
+
"bytes": 99854
|
| 830 |
+
},
|
| 831 |
+
{
|
| 832 |
+
"path": "source/starVLA/training/trainer_utils/__init__.py",
|
| 833 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 834 |
+
"ref": "main",
|
| 835 |
+
"source_path": "starVLA/training/trainer_utils/__init__.py",
|
| 836 |
+
"bytes": 44
|
| 837 |
+
},
|
| 838 |
+
{
|
| 839 |
+
"path": "source/starVLA/training/trainer_utils/config_tracker.py",
|
| 840 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 841 |
+
"ref": "main",
|
| 842 |
+
"source_path": "starVLA/training/trainer_utils/config_tracker.py",
|
| 843 |
+
"bytes": 21344
|
| 844 |
+
},
|
| 845 |
+
{
|
| 846 |
+
"path": "source/starVLA/training/trainer_utils/monkey_patch.py",
|
| 847 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 848 |
+
"ref": "main",
|
| 849 |
+
"source_path": "starVLA/training/trainer_utils/monkey_patch.py",
|
| 850 |
+
"bytes": 19353
|
| 851 |
+
},
|
| 852 |
+
{
|
| 853 |
+
"path": "source/starVLA/training/trainer_utils/overwatch.py",
|
| 854 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 855 |
+
"ref": "main",
|
| 856 |
+
"source_path": "starVLA/training/trainer_utils/overwatch.py",
|
| 857 |
+
"bytes": 6848
|
| 858 |
+
},
|
| 859 |
+
{
|
| 860 |
+
"path": "source/starVLA/training/trainer_utils/trainer_tools.py",
|
| 861 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 862 |
+
"ref": "main",
|
| 863 |
+
"source_path": "starVLA/training/trainer_utils/trainer_tools.py",
|
| 864 |
+
"bytes": 23838
|
| 865 |
+
},
|
| 866 |
+
{
|
| 867 |
+
"path": "source/tests/test_xtrainer2_optr_config.py",
|
| 868 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 869 |
+
"ref": "main",
|
| 870 |
+
"source_path": "tests/test_xtrainer2_optr_config.py",
|
| 871 |
+
"bytes": 6916
|
| 872 |
+
},
|
| 873 |
+
{
|
| 874 |
+
"path": "source/tests/test_xtrainer2_pack_phone_config.py",
|
| 875 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 876 |
+
"ref": "main",
|
| 877 |
+
"source_path": "tests/test_xtrainer2_pack_phone_config.py",
|
| 878 |
+
"bytes": 11364
|
| 879 |
+
},
|
| 880 |
+
{
|
| 881 |
+
"path": "source/tests/test_xtrainer2_pick_bottle_config.py",
|
| 882 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 883 |
+
"ref": "main",
|
| 884 |
+
"source_path": "tests/test_xtrainer2_pick_bottle_config.py",
|
| 885 |
+
"bytes": 4381
|
| 886 |
+
},
|
| 887 |
+
{
|
| 888 |
+
"path": "source/tests/test_xtrainer2_policy_server_contract.py",
|
| 889 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 890 |
+
"ref": "main",
|
| 891 |
+
"source_path": "tests/test_xtrainer2_policy_server_contract.py",
|
| 892 |
+
"bytes": 9812
|
| 893 |
+
},
|
| 894 |
+
{
|
| 895 |
+
"path": "source/third_party/frameskip/LICENSE",
|
| 896 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 897 |
+
"ref": "main",
|
| 898 |
+
"source_path": "third_party/frameskip/LICENSE",
|
| 899 |
+
"bytes": 1387
|
| 900 |
+
},
|
| 901 |
+
{
|
| 902 |
+
"path": "source/UPSTREAM.md",
|
| 903 |
+
"repository": "miaochengy-pngu/ReTime_source",
|
| 904 |
+
"ref": "main",
|
| 905 |
+
"source_path": "UPSTREAM.md",
|
| 906 |
+
"bytes": 1460
|
| 907 |
+
}
|
| 908 |
+
]
|
| 909 |
+
}
|
real_robot/source/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) StarVLA Team.
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
Rebases are allowed for forks and feature branches. When rebasing from upstream StarVLA, use descriptive commit messages, e.g., "chore: clone from StarVLA".
|
| 16 |
+
Preserve attribution: keep at least the two latest upstream StarVLA commits as separate.
|
| 17 |
+
|
| 18 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 19 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 20 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 21 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 22 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 23 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 24 |
+
SOFTWARE
|
real_robot/source/NOTICE
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ReTime is a research extension of StarVLA, not an official StarVLA release.
|
| 2 |
+
|
| 3 |
+
StarVLA: https://github.com/starVLA/starVLA
|
| 4 |
+
Base commit: 3422b9f2387b6f682cf02802904a77b23ab13afd
|
| 5 |
+
Copyright (c) StarVLA Team.
|
| 6 |
+
The complete upstream LICENSE is retained, including its additional attribution
|
| 7 |
+
and commit-preservation language. The original base commit and its preceding
|
| 8 |
+
upstream commit remain separate in the supplied Git history.
|
| 9 |
+
|
| 10 |
+
Modifications in this artifact include temporal sampling/proposals, ordinary
|
| 11 |
+
importance correction, scoring utilities, model/data/trainer integration,
|
| 12 |
+
portable configurations, and the ReTime command-line interface.
|
| 13 |
+
Original ReTime additions in this artifact are distributed under the root
|
| 14 |
+
LICENSE terms. Their authors' identities are withheld during anonymous review.
|
| 15 |
+
Existing upstream author/copyright notices identify the respective third-party
|
| 16 |
+
contributors.
|
| 17 |
+
|
| 18 |
+
NVIDIA Isaac-GR00T-derived code retains its source notices and Apache-2.0 terms.
|
| 19 |
+
See licenses/Isaac-GR00T-LICENSE. Modified source files carry a modification notice.
|
| 20 |
+
https://github.com/NVIDIA/Isaac-GR00T
|
| 21 |
+
|
| 22 |
+
OpenVLA/Prismatic-derived logging code retains its attribution.
|
| 23 |
+
See licenses/OpenVLA-LICENSE (MIT).
|
| 24 |
+
https://github.com/openvla/openvla
|
| 25 |
+
|
| 26 |
+
The Florence wrapper attributes X-VLA-derived code in its source.
|
| 27 |
+
See licenses/X-VLA-LICENSE (Apache-2.0).
|
| 28 |
+
https://github.com/2toinf/X-VLA
|
| 29 |
+
|
| 30 |
+
The minimal FrameSkip source snapshot retains third_party/frameskip/LICENSE.
|
| 31 |
+
Rotation conversion code in starVLA/dataloader/gr00t_lerobot/transform/state_action.py
|
| 32 |
+
derives from Diffusion Policy. Its MIT license is retained in
|
| 33 |
+
licenses/Diffusion-Policy-LICENSE.
|
| 34 |
+
The adapted msgpack-numpy utility in deployment/model_server/tools/msgpack_numpy.py
|
| 35 |
+
retains the original BSD license in licenses/msgpack-numpy-LICENSE.
|
| 36 |
+
Third-party components remain subject to their respective licenses; the root
|
| 37 |
+
license does not replace these component-specific terms.
|
| 38 |
+
|
| 39 |
+
No datasets, pretrained model weights, checkpoints, benchmark repositories,
|
| 40 |
+
or robot-client contents are redistributed in the working source tree.
|
real_robot/source/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ReTime for StarVLA
|
| 2 |
+
|
| 3 |
+
ReTime is an opt-in temporal-sampling extension for the accompanying StarVLA
|
| 4 |
+
backend. It preserves the dataset/trajectory distribution and changes the
|
| 5 |
+
within-trajectory temporal proposal:
|
| 6 |
+
|
| 7 |
+
```text
|
| 8 |
+
q(t | e) = (1 - alpha) p(t | e) + alpha r(t | e)
|
| 9 |
+
training loss = mean((p / q) * per_sample_loss)
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
The weights are ordinary importance weights, not batch-normalized weights.
|
| 13 |
+
`K` controls the frozen-policy scoring grid. `shuffle` permutes the residual
|
| 14 |
+
masses within each trajectory; `uniform` uses the baseline draw stream.
|
| 15 |
+
|
| 16 |
+
## Install
|
| 17 |
+
|
| 18 |
+
Use Linux, Python 3.11, a CUDA driver compatible with PyTorch 2.7.1, and the
|
| 19 |
+
dependencies in `requirements.txt`. The core training packages are pinned to
|
| 20 |
+
the versions used in the GPU integration test. From this source tree:
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
python -m pip install -r requirements.txt
|
| 24 |
+
python -m pip install -e .
|
| 25 |
+
retime --help
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
The supplied Qwen-GR00T configurations additionally use `AdamW8bit` and
|
| 29 |
+
`flash_attention_2`. Install their optional dependencies after the base
|
| 30 |
+
environment and select a Flash Attention build compatible with your CUDA and
|
| 31 |
+
PyTorch installation:
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
python -m pip install -r requirements-qwen.txt
|
| 35 |
+
python -m pip install flash-attn==2.7.4.post1 --no-build-isolation
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
Flash Attention may require a local CUDA toolkit and build tools. The version
|
| 39 |
+
above was present in the GPU validation environments. Both production Qwen
|
| 40 |
+
continuation configurations use Flash Attention 2. The bounded Qwen Bridge
|
| 41 |
+
integration run used PyTorch SDPA on the same model/data path.
|
| 42 |
+
|
| 43 |
+
This release supports the supplied Florence/Qwen GR00T configurations and
|
| 44 |
+
LeRobot data layouts. The backend includes the per-example loss and sampler
|
| 45 |
+
integration required by ReTime; it is not a claim of compatibility with every
|
| 46 |
+
unmodified StarVLA release or arbitrary policy architecture.
|
| 47 |
+
|
| 48 |
+
## Training
|
| 49 |
+
|
| 50 |
+
The trained common-start checkpoints used in the paper are not redistributed
|
| 51 |
+
with this anonymous artifact. Reviewers should either train their own
|
| 52 |
+
common-start policy using `retime-pretrain` or provide a compatible complete
|
| 53 |
+
checkpoint. The backbone, dataset, and benchmark assets must also be provided
|
| 54 |
+
locally.
|
| 55 |
+
|
| 56 |
+
Select a configuration matching the policy architecture, action dimensions,
|
| 57 |
+
dataset mixture, and training budget. Matching supplied pairs are:
|
| 58 |
+
|
| 59 |
+
| Setting | Policy configuration | Start → stop | Global batch | Accelerate configuration |
|
| 60 |
+
| --- | --- | ---: | ---: | --- |
|
| 61 |
+
| Florence × Bridge/RT-1 | `configs/florence_bridge_20k_plus500.yaml` | 20K → 20.5K | 64 | `configs/deepspeed_florence_2gpu.yaml` |
|
| 62 |
+
| Qwen × Bridge/RT-1 | `configs/qwen_bridge_29k_plus500.yaml` | 29K → 29.5K | 48 | `configs/qwen_single_1gpu.yaml` |
|
| 63 |
+
| Florence × RoboTwin | `configs/florence_robotwin_10k_plus500.yaml` | 10K → 10.5K | 64 | `configs/deepspeed_florence_robotwin_2gpu.yaml` |
|
| 64 |
+
| Qwen × RoboTwin | `configs/qwen_robotwin_11k_plus500.yaml` | 11K → 11.5K | 40 | `configs/deepspeed_qwen_robotwin_2gpu.yaml` |
|
| 65 |
+
|
| 66 |
+
These are the four main simulation continuation contracts. Every arm restores
|
| 67 |
+
the complete shared starting state and performs exactly 500 optimizer updates.
|
| 68 |
+
Bridge runs additionally require `--statistics-path`.
|
| 69 |
+
|
| 70 |
+
### Train the base policy locally
|
| 71 |
+
|
| 72 |
+
Reviewers do not need the authors' original common-start checkpoint.
|
| 73 |
+
`retime-pretrain` can train a reviewer-owned common-start policy with the same
|
| 74 |
+
architecture, optimizer, scheduler, batch geometry, and dataset-sampling
|
| 75 |
+
contract as the selected continuation configuration. For Florence Bridge:
|
| 76 |
+
|
| 77 |
+
```bash
|
| 78 |
+
retime-pretrain \
|
| 79 |
+
--config configs/florence_bridge_20k_plus500.yaml \
|
| 80 |
+
--accelerate-config configs/deepspeed_florence_2gpu.yaml \
|
| 81 |
+
--base-steps 20000 \
|
| 82 |
+
--vlm-path /path/to/Florence-2-large \
|
| 83 |
+
--data-root /path/to/OXE_LEROBOT_DATASET \
|
| 84 |
+
--statistics-path /path/to/dataset_statistics.json \
|
| 85 |
+
--omit-data-identity-files \
|
| 86 |
+
--output /path/to/florence-bridge-base
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
Use `--base-steps 29000`, `10000`, or `11000` with the other three configuration
|
| 90 |
+
pairs. The command builds and saves `manifest.json` before training. Two-process
|
| 91 |
+
configurations produce `checkpoints/steps_<N>_accelerate`; Qwen Bridge produces
|
| 92 |
+
`checkpoints/steps_29000_pytorch_model.pt` with adjacent training-state and
|
| 93 |
+
commit files. Both formats contain the model, optimizer, scheduler, RNG state,
|
| 94 |
+
sampling position, and completed-update counter required by exact continuation.
|
| 95 |
+
|
| 96 |
+
`--omit-data-identity-files` stores the computed dataset inventory inside the
|
| 97 |
+
manifest. Use the same option for continuation. This avoids requiring separate
|
| 98 |
+
provenance and fingerprint files while still checking the dataset inventory
|
| 99 |
+
against the frozen manifest.
|
| 100 |
+
|
| 101 |
+
For the Florence Bridge configuration:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
retime \
|
| 105 |
+
--config configs/florence_bridge_20k_plus500.yaml \
|
| 106 |
+
--accelerate-config configs/deepspeed_florence_2gpu.yaml \
|
| 107 |
+
--base-policy /path/to/steps_20000_accelerate \
|
| 108 |
+
--start resume \
|
| 109 |
+
--manifest /path/to/florence-bridge-base/manifest.json \
|
| 110 |
+
--vlm-path /path/to/Florence-2-large \
|
| 111 |
+
--data-root /path/to/OXE_LEROBOT_DATASET \
|
| 112 |
+
--statistics-path /path/to/dataset_statistics.json \
|
| 113 |
+
--omit-data-identity-files \
|
| 114 |
+
--alpha 0.2 --K 8 \
|
| 115 |
+
--mode retime uniform shuffle \
|
| 116 |
+
--output /path/to/new-run
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
Choose one mode or several. The command prepares valid-anchor support from the
|
| 120 |
+
dataset, generates the residual proposal once, constructs Shuffle when selected,
|
| 121 |
+
and trains each selected arm sequentially from the same base policy. A
|
| 122 |
+
uniform-only run does not score a proposal. Invalid probabilities,
|
| 123 |
+
incompatible tensor shapes, incomplete
|
| 124 |
+
checkpoint files, and mismatched data support still produce errors.
|
| 125 |
+
|
| 126 |
+
`--base-policy` denotes the reviewer-provided common-start VLA checkpoint.
|
| 127 |
+
It is not bundled with this anonymous artifact and is not downloaded
|
| 128 |
+
automatically. `--vlm-path` denotes its Florence/Qwen backbone. They are
|
| 129 |
+
distinct inputs. The main configurations use
|
| 130 |
+
`--start resume`, which is required for the reported +500 continuation.
|
| 131 |
+
Florence Bridge and both RoboTwin settings accept a complete Accelerate
|
| 132 |
+
checkpoint directory. Qwen Bridge accepts its committed single-process model
|
| 133 |
+
file together with the adjacent training-state and commit files.
|
| 134 |
+
|
| 135 |
+
Exact resume restores model, optimizer, scheduler, RNG, sampler position/data
|
| 136 |
+
order, and completed-update counter. Supply the `manifest.json` produced by
|
| 137 |
+
the base-policy run and keep its `config.full.yaml` in the original run layout.
|
| 138 |
+
When separate provenance/fingerprint files are available, omit
|
| 139 |
+
`--omit-data-identity-files` and set the corresponding environment variables in
|
| 140 |
+
both commands. A single-process model file is accepted only when its adjacent
|
| 141 |
+
`*_training_state.pt` and `*_checkpoint.json` files form a complete committed
|
| 142 |
+
checkpoint.
|
| 143 |
+
|
| 144 |
+
`--start weights` remains available for a deliberate fresh optimizer/scheduler
|
| 145 |
+
run from model weights. It starts at update zero, so it does not reproduce any
|
| 146 |
+
of the four +500 experiments even when a continuation configuration is selected.
|
| 147 |
+
|
| 148 |
+
The launcher never changes microbatch, world size, accumulation, optimizer,
|
| 149 |
+
schedule, or random seeds to select an arm. The supplied Bridge pairing remains
|
| 150 |
+
`4 x 2 x 8 = 64`. The table above supplies matching Accelerate configurations
|
| 151 |
+
for the other three settings. Select a smaller dataset mixture with `--data-mix`
|
| 152 |
+
when checking a subset; the default RoboTwin configurations cover all 50 tasks.
|
| 153 |
+
|
| 154 |
+
Add `--dry-run` to validate inputs and print the command plan without writing or
|
| 155 |
+
launching. Successful runs produce `config.yaml`, `plan.json`, a proposal/cache,
|
| 156 |
+
and separate `runs/retime`, `runs/uniform`, and `runs/shuffle` directories. Use a
|
| 157 |
+
fresh output directory for each experiment. Existing proposal caches are bound
|
| 158 |
+
to the checkpoint content and support; incompatible caches cannot be reused.
|
| 159 |
+
|
| 160 |
+
## Layout
|
| 161 |
+
|
| 162 |
+
```text
|
| 163 |
+
retime/ method implementation and command-line entrypoint
|
| 164 |
+
pretrain.py reviewer-owned common-start policy training
|
| 165 |
+
objective.py ordinary p/q correction
|
| 166 |
+
sampler.py deterministic hierarchical draw stream
|
| 167 |
+
proposal.py full-support temporal proposal
|
| 168 |
+
scoring/ frozen-policy scoring and Shuffle control
|
| 169 |
+
commands/ proposal generation
|
| 170 |
+
starVLA/ attributed compatible training/model/data backend
|
| 171 |
+
scripts/ legacy entrypoints and optional analysis utilities
|
| 172 |
+
configs/ model, data, and training settings
|
| 173 |
+
licenses/ third-party license texts
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
Legacy `starVLA.*` method imports forward to `retime.*` for compatibility.
|
| 177 |
+
Analysis utilities are optional research tools and are not training prerequisites.
|
| 178 |
+
|
| 179 |
+
## Compatibility
|
| 180 |
+
|
| 181 |
+
| Interface | Support |
|
| 182 |
+
| --- | --- |
|
| 183 |
+
| StarVLA training | The original `starVLA/training/train_starvla.py` entrypoint is retained. |
|
| 184 |
+
| Legacy Python imports | Objective, sampler, proposal, and scoring modules forward to ReTime implementations. |
|
| 185 |
+
| Proposal and manifest files | Existing schemas and probability semantics are preserved. |
|
| 186 |
+
| Model initialization | Full model weights initialize a fresh optimizer and scheduler. |
|
| 187 |
+
| Full-state continuation | `--start resume` restores a complete Accelerate directory or committed single-process checkpoint. |
|
| 188 |
+
| Distributed training | Matching one- and two-GPU configurations are supplied for all four settings. |
|
| 189 |
+
|
| 190 |
+
Florence-GR00T and Qwen-GR00T were exercised on GPUs with both Bridge/RT-1 and
|
| 191 |
+
RoboTwin data. Other policy architectures require a compatible per-sample loss
|
| 192 |
+
and dataset adapter.
|
| 193 |
+
Historical `OPTR_*` environment variables and `InfoFlow` module names in
|
| 194 |
+
example configurations are compatibility identifiers for existing datasets and
|
| 195 |
+
checkpoints. The supported command-line entrypoint for this artifact is `retime`.
|
| 196 |
+
|
| 197 |
+
## Tests and scope
|
| 198 |
+
|
| 199 |
+
```bash
|
| 200 |
+
python -m pip install pytest
|
| 201 |
+
python -m pytest tests/test_retime_plugin.py -q
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
These CPU tests check proposal generation, objective correction, deterministic
|
| 205 |
+
sampling, Shuffle invariants, legacy imports, CLI dispatch, and error propagation.
|
| 206 |
+
A separate GPU integration test completed proposal generation, two optimizer
|
| 207 |
+
updates, checkpoint saving, and strict model reload for each of the three modes
|
| 208 |
+
in all four settings. See `VALIDATION.md` for details.
|
| 209 |
+
Datasets, trained weights, checkpoints, and benchmark repositories are not
|
| 210 |
+
redistributed in the working source tree.
|
| 211 |
+
|
| 212 |
+
## Attribution and anonymous review
|
| 213 |
+
|
| 214 |
+
Built on [StarVLA](https://github.com/starVLA/starVLA), pinned to
|
| 215 |
+
`3422b9f2387b6f682cf02802904a77b23ab13afd`. See `UPSTREAM.md`, `LICENSE`, and
|
| 216 |
+
`NOTICE`. Retain the complete license, source copyright notices, and the
|
| 217 |
+
separate original upstream commits in the accompanying Git history.
|
| 218 |
+
|
| 219 |
+
Third-party attribution is intentionally visible. The anonymous additions have
|
| 220 |
+
no author-account remote or personal development history. Publish through an
|
| 221 |
+
anonymous review endpoint, not an author's personal repository URL. Avoid
|
| 222 |
+
uploading local training outputs: they can contain machine paths and metadata.
|
real_robot/source/UPSTREAM.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Upstream provenance
|
| 2 |
+
|
| 3 |
+
- Repository: https://github.com/starVLA/starVLA
|
| 4 |
+
- Original branch: `starVLA`
|
| 5 |
+
- Base: `3422b9f2387b6f682cf02802904a77b23ab13afd`
|
| 6 |
+
- Preceding upstream commit: `61ea14279a22739316f9c1ca74c18d535247fe05`
|
| 7 |
+
- Base LICENSE SHA256: `6ca01970c4e66d2d5bebd0a00b55de5b4081017e0f0c97ddb415cda28e3e8ffa`
|
| 8 |
+
|
| 9 |
+
The base revision was recovered from the original development snapshot's
|
| 10 |
+
provenance record and checked against the public upstream Git object and license.
|
| 11 |
+
The two original upstream commits are retained unchanged. A separate anonymous
|
| 12 |
+
commit contains the review artifact and modifications. Personal development
|
| 13 |
+
commits and remotes are not part of this distribution.
|
| 14 |
+
|
| 15 |
+
The source tree is a selected, modified research backend, not an unmodified
|
| 16 |
+
checkout of that revision. Per-file notices and NOTICE describe the derivative
|
| 17 |
+
work. Do not replace LICENSE with a generic MIT template: this upstream text
|
| 18 |
+
contains additional attribution/commit-preservation language.
|
| 19 |
+
|
| 20 |
+
The distribution ZIP includes `review-history.bundle`, containing the public
|
| 21 |
+
upstream ancestry and the anonymous review commit. Restore a Git repository with:
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
git clone --branch anonymous-review review-history.bundle retime-review
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
This preserves the original upstream commit objects and creates no relationship
|
| 28 |
+
to the authors' personal development repository. Use the restored history when
|
| 29 |
+
publishing to an anonymous repository; do not squash away the upstream commits.
|
real_robot/source/configs/deepspeed_florence_8gpu.yaml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
compute_environment: LOCAL_MACHINE
|
| 2 |
+
debug: false
|
| 3 |
+
deepspeed_config:
|
| 4 |
+
gradient_accumulation_steps: 4
|
| 5 |
+
offload_optimizer_device: none
|
| 6 |
+
offload_param_device: none
|
| 7 |
+
zero3_init_flag: true
|
| 8 |
+
# Required: _save_accelerate_checkpoint() calls accelerator.get_state_dict(),
|
| 9 |
+
# which raises under ZeRO-3 unless the 16-bit weights are gathered on save.
|
| 10 |
+
# Without this the run trains fine and then dies at the first save_steps entry.
|
| 11 |
+
zero3_save_16bit_model: true
|
| 12 |
+
zero_stage: 3
|
| 13 |
+
distributed_type: DEEPSPEED
|
| 14 |
+
downcast_bf16: "no"
|
| 15 |
+
enable_cpu_affinity: false
|
| 16 |
+
machine_rank: 0
|
| 17 |
+
main_training_function: main
|
| 18 |
+
mixed_precision: bf16
|
| 19 |
+
num_machines: 1
|
| 20 |
+
num_processes: 8
|
| 21 |
+
rdzv_backend: static
|
| 22 |
+
same_network: true
|
| 23 |
+
use_cpu: false
|
real_robot/source/configs/florence_xtrainer2_pack_phone_residual_20ep_to_60ep.yaml
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
run_id: florence-xtrainer2-pack-phone-residual-20ep-to-60ep
|
| 2 |
+
run_root_dir: ${oc.env:OPTR_RUN_ROOT}
|
| 3 |
+
seed: 117465
|
| 4 |
+
wandb_entity: null
|
| 5 |
+
wandb_project: OPTR-Florence-Xtrainer2
|
| 6 |
+
wandb_group: pack-phone-optr
|
| 7 |
+
wandb_job_type: train
|
| 8 |
+
wandb_tags: [florence, xtrainer2, pack-phone, optr, residual, 20ep-common-start]
|
| 9 |
+
is_debug: false
|
| 10 |
+
version_id: "0.21"
|
| 11 |
+
|
| 12 |
+
framework:
|
| 13 |
+
name: QwenGR00T
|
| 14 |
+
qwenvl:
|
| 15 |
+
base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
|
| 16 |
+
attn_implementation: eager
|
| 17 |
+
vl_hidden_dim: 1024
|
| 18 |
+
selected_view_indices: [0, 1, 2]
|
| 19 |
+
compile_vision_tower: false
|
| 20 |
+
action_model:
|
| 21 |
+
action_model_type: DiT-B
|
| 22 |
+
hidden_size: 1024
|
| 23 |
+
add_pos_embed: true
|
| 24 |
+
max_seq_len: 1024
|
| 25 |
+
action_dim: 20
|
| 26 |
+
state_dim: 20
|
| 27 |
+
action_horizon: 16
|
| 28 |
+
future_action_window_size: 15
|
| 29 |
+
noise_beta_alpha: 1.5
|
| 30 |
+
noise_beta_beta: 1.0
|
| 31 |
+
noise_s: 0.999
|
| 32 |
+
num_timestep_buckets: 1000
|
| 33 |
+
num_inference_timesteps: 4
|
| 34 |
+
num_target_vision_tokens: 32
|
| 35 |
+
diffusion_model_cfg:
|
| 36 |
+
cross_attention_dim: 1024
|
| 37 |
+
dropout: 0.2
|
| 38 |
+
final_dropout: true
|
| 39 |
+
interleave_self_attention: true
|
| 40 |
+
norm_type: ada_norm
|
| 41 |
+
num_layers: 16
|
| 42 |
+
output_dim: 1024
|
| 43 |
+
positional_embeddings: null
|
| 44 |
+
|
| 45 |
+
datasets:
|
| 46 |
+
vla_data:
|
| 47 |
+
dataset_py: lerobot_datasets
|
| 48 |
+
data_root_dir: ${oc.env:XTRAINER2_DATA_ROOT}
|
| 49 |
+
data_mix: xtrainer2_pack_phone
|
| 50 |
+
action_type: abs_eef
|
| 51 |
+
action_mode: abs
|
| 52 |
+
include_state: true
|
| 53 |
+
per_device_batch_size: 4
|
| 54 |
+
image_size: [224, 224]
|
| 55 |
+
video_backend: pyav
|
| 56 |
+
num_workers: 8
|
| 57 |
+
expected_world_size: 8
|
| 58 |
+
traceable_sampling: true
|
| 59 |
+
dataset_provenance_path: ${oc.env:XTRAINER2_PACK_PHONE_PROVENANCE_PATH}
|
| 60 |
+
dataset_content_fingerprint_path: ${oc.env:XTRAINER2_PACK_PHONE_FINGERPRINT_PATH}
|
| 61 |
+
valid_anchor_manifest_path: ${oc.env:XTRAINER2_PACK_PHONE_MANIFEST_PATH}
|
| 62 |
+
temporal_proposal_path: ${oc.env:OPTR_TEMPORAL_PROPOSAL_PATH}
|
| 63 |
+
transform_rng_seed: 298226
|
| 64 |
+
|
| 65 |
+
trainer:
|
| 66 |
+
use_deepspeed: true
|
| 67 |
+
max_train_steps: 52380
|
| 68 |
+
num_warmup_steps: 5000
|
| 69 |
+
save_interval: null
|
| 70 |
+
save_steps: [26190, 34920, 43650, 52380]
|
| 71 |
+
eval_interval: null
|
| 72 |
+
repeated_diffusion_steps: 4
|
| 73 |
+
pretrained_checkpoint: null
|
| 74 |
+
is_resume: true
|
| 75 |
+
allow_model_only_resume: false
|
| 76 |
+
resume_accelerate_checkpoint: ${oc.env:XTRAINER2_PACK_PHONE_RESUME_DIR}
|
| 77 |
+
branch_parent_seed: 42
|
| 78 |
+
branch_rng_seed: 117465
|
| 79 |
+
model_rng_seed: 588643
|
| 80 |
+
stop_after_steps: 52380
|
| 81 |
+
preflight_validate_first_batch: true
|
| 82 |
+
learning_rate:
|
| 83 |
+
base: 1.0e-05
|
| 84 |
+
qwen_vl_interface: 1.0e-05
|
| 85 |
+
action_model: 1.0e-04
|
| 86 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 87 |
+
scheduler_specific_kwargs:
|
| 88 |
+
min_lr: 5.0e-07
|
| 89 |
+
freeze_modules: ""
|
| 90 |
+
trainable_modules: ""
|
| 91 |
+
frozen_modules_eval: false
|
| 92 |
+
logging_frequency: 50
|
| 93 |
+
gradient_clipping: 1.0
|
| 94 |
+
gradient_accumulation_steps: 4
|
| 95 |
+
save_format: pt
|
| 96 |
+
save_final_checkpoint: false
|
| 97 |
+
optimizer:
|
| 98 |
+
name: AdamW
|
| 99 |
+
betas: [0.9, 0.95]
|
| 100 |
+
eps: 1.0e-08
|
| 101 |
+
weight_decay: 1.0e-08
|
real_robot/source/configs/florence_xtrainer2_pack_phone_residual_resume_smoke.yaml
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# External exact-resume smoke. It uses the production batch geometry and
|
| 2 |
+
# common-start, but writes to an independent run and performs at most two
|
| 3 |
+
# optimizer updates.
|
| 4 |
+
run_id: florence-xtrainer2-pack-phone-residual-resume-smoke
|
| 5 |
+
run_root_dir: ${oc.env:OPTR_RUN_ROOT}
|
| 6 |
+
seed: 117465
|
| 7 |
+
wandb_entity: null
|
| 8 |
+
wandb_project: OPTR-Florence-Xtrainer2
|
| 9 |
+
wandb_group: pack-phone-optr
|
| 10 |
+
wandb_job_type: smoke
|
| 11 |
+
wandb_tags: [florence, xtrainer2, pack-phone, residual, resume-smoke]
|
| 12 |
+
is_debug: false
|
| 13 |
+
version_id: "0.21"
|
| 14 |
+
|
| 15 |
+
framework:
|
| 16 |
+
name: QwenGR00T
|
| 17 |
+
qwenvl:
|
| 18 |
+
base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
|
| 19 |
+
attn_implementation: eager
|
| 20 |
+
vl_hidden_dim: 1024
|
| 21 |
+
selected_view_indices: [0, 1, 2]
|
| 22 |
+
compile_vision_tower: false
|
| 23 |
+
action_model:
|
| 24 |
+
action_model_type: DiT-B
|
| 25 |
+
hidden_size: 1024
|
| 26 |
+
add_pos_embed: true
|
| 27 |
+
max_seq_len: 1024
|
| 28 |
+
action_dim: 20
|
| 29 |
+
state_dim: 20
|
| 30 |
+
action_horizon: 16
|
| 31 |
+
future_action_window_size: 15
|
| 32 |
+
noise_beta_alpha: 1.5
|
| 33 |
+
noise_beta_beta: 1.0
|
| 34 |
+
noise_s: 0.999
|
| 35 |
+
num_timestep_buckets: 1000
|
| 36 |
+
num_inference_timesteps: 4
|
| 37 |
+
num_target_vision_tokens: 32
|
| 38 |
+
diffusion_model_cfg:
|
| 39 |
+
cross_attention_dim: 1024
|
| 40 |
+
dropout: 0.2
|
| 41 |
+
final_dropout: true
|
| 42 |
+
interleave_self_attention: true
|
| 43 |
+
norm_type: ada_norm
|
| 44 |
+
num_layers: 16
|
| 45 |
+
output_dim: 1024
|
| 46 |
+
positional_embeddings: null
|
| 47 |
+
|
| 48 |
+
datasets:
|
| 49 |
+
vla_data:
|
| 50 |
+
dataset_py: lerobot_datasets
|
| 51 |
+
data_root_dir: ${oc.env:XTRAINER2_DATA_ROOT}
|
| 52 |
+
data_mix: xtrainer2_pack_phone
|
| 53 |
+
action_type: abs_eef
|
| 54 |
+
action_mode: abs
|
| 55 |
+
include_state: true
|
| 56 |
+
per_device_batch_size: 4
|
| 57 |
+
image_size: [224, 224]
|
| 58 |
+
video_backend: pyav
|
| 59 |
+
num_workers: 8
|
| 60 |
+
expected_world_size: 8
|
| 61 |
+
traceable_sampling: true
|
| 62 |
+
dataset_provenance_path: ${oc.env:XTRAINER2_PACK_PHONE_PROVENANCE_PATH}
|
| 63 |
+
dataset_content_fingerprint_path: ${oc.env:XTRAINER2_PACK_PHONE_FINGERPRINT_PATH}
|
| 64 |
+
valid_anchor_manifest_path: ${oc.env:XTRAINER2_PACK_PHONE_MANIFEST_PATH}
|
| 65 |
+
temporal_proposal_path: ${oc.env:OPTR_TEMPORAL_PROPOSAL_PATH}
|
| 66 |
+
transform_rng_seed: 298226
|
| 67 |
+
|
| 68 |
+
trainer:
|
| 69 |
+
use_deepspeed: true
|
| 70 |
+
max_train_steps: 52380
|
| 71 |
+
num_warmup_steps: 5000
|
| 72 |
+
save_interval: null
|
| 73 |
+
save_steps: []
|
| 74 |
+
eval_interval: null
|
| 75 |
+
repeated_diffusion_steps: 4
|
| 76 |
+
pretrained_checkpoint: null
|
| 77 |
+
is_resume: true
|
| 78 |
+
allow_model_only_resume: false
|
| 79 |
+
resume_accelerate_checkpoint: ${oc.env:XTRAINER2_PACK_PHONE_RESUME_DIR}
|
| 80 |
+
branch_parent_seed: 42
|
| 81 |
+
branch_rng_seed: 117465
|
| 82 |
+
model_rng_seed: 588643
|
| 83 |
+
stop_after_steps: 17462
|
| 84 |
+
preflight_validate_first_batch: true
|
| 85 |
+
learning_rate:
|
| 86 |
+
base: 1.0e-05
|
| 87 |
+
qwen_vl_interface: 1.0e-05
|
| 88 |
+
action_model: 1.0e-04
|
| 89 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 90 |
+
scheduler_specific_kwargs:
|
| 91 |
+
min_lr: 5.0e-07
|
| 92 |
+
freeze_modules: ""
|
| 93 |
+
trainable_modules: ""
|
| 94 |
+
frozen_modules_eval: false
|
| 95 |
+
logging_frequency: 1
|
| 96 |
+
gradient_clipping: 1.0
|
| 97 |
+
gradient_accumulation_steps: 4
|
| 98 |
+
save_format: pt
|
| 99 |
+
save_final_checkpoint: false
|
| 100 |
+
optimizer:
|
| 101 |
+
name: AdamW
|
| 102 |
+
betas: [0.9, 0.95]
|
| 103 |
+
eps: 1.0e-08
|
| 104 |
+
weight_decay: 1.0e-08
|
real_robot/source/configs/florence_xtrainer2_pack_phone_smoke.yaml
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Smoke test for the pack_phone uniform run. Six optimizer steps with a
|
| 2 |
+
# checkpoint at step 4, which exercises the ZeRO-3 gather-and-consolidate save
|
| 3 |
+
# path that would otherwise only be reached after ~10 epochs.
|
| 4 |
+
# Keeps per_device_batch_size, accumulation, and world size identical to the
|
| 5 |
+
# real config so memory and the global batch of 128 are representative.
|
| 6 |
+
# vision-tower compile is off here to keep startup fast; the real run enables it.
|
| 7 |
+
run_id: florence-xtrainer2-pack-phone-smoke
|
| 8 |
+
run_root_dir: ${oc.env:OPTR_RUN_ROOT}
|
| 9 |
+
seed: 42
|
| 10 |
+
wandb_entity: null
|
| 11 |
+
wandb_project: OPTR-Florence-Xtrainer2
|
| 12 |
+
wandb_group: pack-phone
|
| 13 |
+
wandb_job_type: smoke
|
| 14 |
+
wandb_tags:
|
| 15 |
+
- florence
|
| 16 |
+
- xtrainer2
|
| 17 |
+
- pack-phone
|
| 18 |
+
- smoke
|
| 19 |
+
is_debug: false
|
| 20 |
+
version_id: "0.21"
|
| 21 |
+
|
| 22 |
+
framework:
|
| 23 |
+
name: QwenGR00T
|
| 24 |
+
qwenvl:
|
| 25 |
+
# NOTE: the backbone is selected by substring match on this path
|
| 26 |
+
# (starVLA/model/modules/vlm/__init__.py). The resolved value MUST contain
|
| 27 |
+
# "florence" or startup fails with NotImplementedError.
|
| 28 |
+
base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
|
| 29 |
+
attn_implementation: eager
|
| 30 |
+
vl_hidden_dim: 1024
|
| 31 |
+
selected_view_indices: [0, 1, 2]
|
| 32 |
+
compile_vision_tower: false
|
| 33 |
+
action_model:
|
| 34 |
+
action_model_type: DiT-B
|
| 35 |
+
hidden_size: 1024
|
| 36 |
+
add_pos_embed: true
|
| 37 |
+
max_seq_len: 1024
|
| 38 |
+
action_dim: 20
|
| 39 |
+
state_dim: 20
|
| 40 |
+
action_horizon: 16
|
| 41 |
+
future_action_window_size: 15
|
| 42 |
+
# NOTE: the effective flow-matching repeat factor is trainer.repeated_diffusion_steps.
|
| 43 |
+
# A framework-level key here would be silently ignored; do not re-add it.
|
| 44 |
+
noise_beta_alpha: 1.5
|
| 45 |
+
noise_beta_beta: 1.0
|
| 46 |
+
noise_s: 0.999
|
| 47 |
+
num_timestep_buckets: 1000
|
| 48 |
+
num_inference_timesteps: 4
|
| 49 |
+
num_target_vision_tokens: 32
|
| 50 |
+
diffusion_model_cfg:
|
| 51 |
+
cross_attention_dim: 1024
|
| 52 |
+
dropout: 0.2
|
| 53 |
+
final_dropout: true
|
| 54 |
+
interleave_self_attention: true
|
| 55 |
+
norm_type: ada_norm
|
| 56 |
+
num_layers: 16
|
| 57 |
+
output_dim: 1024
|
| 58 |
+
positional_embeddings: null
|
| 59 |
+
|
| 60 |
+
datasets:
|
| 61 |
+
vla_data:
|
| 62 |
+
dataset_py: lerobot_datasets
|
| 63 |
+
data_root_dir: ${oc.env:XTRAINER2_DATA_ROOT}
|
| 64 |
+
data_mix: xtrainer2_pack_phone
|
| 65 |
+
action_type: abs_eef
|
| 66 |
+
action_mode: abs
|
| 67 |
+
include_state: true
|
| 68 |
+
# 4 x 8 GPUs x accum 4 = global batch 128. Measured peaks on idle 80 GB
|
| 69 |
+
# cards: batch 4 = 60.3 GB, batch 5 = 73.4 GB, batch 6 OOMs. Holding
|
| 70 |
+
# global batch at 128 also requires per_device * accum == 16, so 4/4 is
|
| 71 |
+
# the largest usable microbatch; 8/2 does not fit in memory.
|
| 72 |
+
# See scripts/probe_batch_size.sh.
|
| 73 |
+
per_device_batch_size: 4
|
| 74 |
+
image_size: [224, 224]
|
| 75 |
+
video_backend: pyav
|
| 76 |
+
num_workers: 2
|
| 77 |
+
expected_world_size: 8
|
| 78 |
+
traceable_sampling: true
|
| 79 |
+
dataset_provenance_path: ${oc.env:XTRAINER2_PACK_PHONE_PROVENANCE_PATH}
|
| 80 |
+
dataset_content_fingerprint_path: ${oc.env:XTRAINER2_PACK_PHONE_FINGERPRINT_PATH}
|
| 81 |
+
valid_anchor_manifest_path: null
|
| 82 |
+
temporal_proposal_path: null
|
| 83 |
+
|
| 84 |
+
trainer:
|
| 85 |
+
use_deepspeed: true
|
| 86 |
+
max_train_steps: 6
|
| 87 |
+
num_warmup_steps: 2
|
| 88 |
+
save_interval: null
|
| 89 |
+
save_steps: [4]
|
| 90 |
+
eval_interval: null
|
| 91 |
+
# Effective flow-matching repeat factor (framework.action_model has no say).
|
| 92 |
+
# Per-DiT micro-batch is per_device_batch_size * this = 4 * 4 = 16.
|
| 93 |
+
repeated_diffusion_steps: 4
|
| 94 |
+
learning_rate:
|
| 95 |
+
base: 1.0e-05
|
| 96 |
+
qwen_vl_interface: 1.0e-05
|
| 97 |
+
action_model: 1.0e-04
|
| 98 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 99 |
+
scheduler_specific_kwargs:
|
| 100 |
+
min_lr: 5.0e-07
|
| 101 |
+
freeze_modules: ""
|
| 102 |
+
trainable_modules: ""
|
| 103 |
+
frozen_modules_eval: false
|
| 104 |
+
logging_frequency: 1
|
| 105 |
+
gradient_clipping: 1.0
|
| 106 |
+
# MUST equal deepspeed_config.gradient_accumulation_steps in the accelerate
|
| 107 |
+
# yaml. DeepSpeed's value governs the optimizer; this one sizes the dataloader
|
| 108 |
+
# truncation. A mismatch is only a warning, so keep them in sync by hand.
|
| 109 |
+
# Global batch = per_device 4 * 8 GPUs * accum 4 = 128.
|
| 110 |
+
gradient_accumulation_steps: 4
|
| 111 |
+
save_format: pt
|
| 112 |
+
save_final_checkpoint: false
|
| 113 |
+
optimizer:
|
| 114 |
+
name: AdamW
|
| 115 |
+
betas: [0.9, 0.95]
|
| 116 |
+
eps: 1.0e-08
|
| 117 |
+
weight_decay: 1.0e-08
|
real_robot/source/configs/florence_xtrainer2_pack_phone_uniform.yaml
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
run_id: florence-xtrainer2-pack-phone-uniform
|
| 2 |
+
run_root_dir: ${oc.env:OPTR_RUN_ROOT}
|
| 3 |
+
seed: 42
|
| 4 |
+
wandb_entity: null
|
| 5 |
+
wandb_project: OPTR-Florence-Xtrainer2
|
| 6 |
+
wandb_group: pack-phone
|
| 7 |
+
wandb_job_type: train
|
| 8 |
+
wandb_tags:
|
| 9 |
+
- florence
|
| 10 |
+
- xtrainer2
|
| 11 |
+
- pack-phone
|
| 12 |
+
- train
|
| 13 |
+
is_debug: false
|
| 14 |
+
version_id: "0.21"
|
| 15 |
+
|
| 16 |
+
framework:
|
| 17 |
+
name: QwenGR00T
|
| 18 |
+
qwenvl:
|
| 19 |
+
# NOTE: the backbone is selected by substring match on this path
|
| 20 |
+
# (starVLA/model/modules/vlm/__init__.py). The resolved value MUST contain
|
| 21 |
+
# "florence" or startup fails with NotImplementedError.
|
| 22 |
+
base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
|
| 23 |
+
attn_implementation: eager
|
| 24 |
+
vl_hidden_dim: 1024
|
| 25 |
+
selected_view_indices: [0, 1, 2]
|
| 26 |
+
# Must stay false: torch.compile with mode="reduce-overhead" (CUDA graphs,
|
| 27 |
+
# Florence2.py:150-156) fails under ZeRO-3 with
|
| 28 |
+
# "AssertionError: wrong number of dimensions" from cudagraph_trees'
|
| 29 |
+
# assert_size_stride, because gathered parameter shapes differ between the
|
| 30 |
+
# trace and the replay. Verified failing on 8 ranks.
|
| 31 |
+
compile_vision_tower: false
|
| 32 |
+
action_model:
|
| 33 |
+
action_model_type: DiT-B
|
| 34 |
+
hidden_size: 1024
|
| 35 |
+
add_pos_embed: true
|
| 36 |
+
max_seq_len: 1024
|
| 37 |
+
action_dim: 20
|
| 38 |
+
state_dim: 20
|
| 39 |
+
action_horizon: 16
|
| 40 |
+
future_action_window_size: 15
|
| 41 |
+
# NOTE: the effective flow-matching repeat factor is trainer.repeated_diffusion_steps.
|
| 42 |
+
# A framework-level key here would be silently ignored; do not re-add it.
|
| 43 |
+
noise_beta_alpha: 1.5
|
| 44 |
+
noise_beta_beta: 1.0
|
| 45 |
+
noise_s: 0.999
|
| 46 |
+
num_timestep_buckets: 1000
|
| 47 |
+
num_inference_timesteps: 4
|
| 48 |
+
num_target_vision_tokens: 32
|
| 49 |
+
diffusion_model_cfg:
|
| 50 |
+
cross_attention_dim: 1024
|
| 51 |
+
dropout: 0.2
|
| 52 |
+
final_dropout: true
|
| 53 |
+
interleave_self_attention: true
|
| 54 |
+
norm_type: ada_norm
|
| 55 |
+
num_layers: 16
|
| 56 |
+
output_dim: 1024
|
| 57 |
+
positional_embeddings: null
|
| 58 |
+
|
| 59 |
+
datasets:
|
| 60 |
+
vla_data:
|
| 61 |
+
dataset_py: lerobot_datasets
|
| 62 |
+
data_root_dir: ${oc.env:XTRAINER2_DATA_ROOT}
|
| 63 |
+
data_mix: xtrainer2_pack_phone
|
| 64 |
+
action_type: abs_eef
|
| 65 |
+
action_mode: abs
|
| 66 |
+
include_state: true
|
| 67 |
+
# 4 x 8 GPUs x accum 4 = global batch 128. Measured peaks on idle 80 GB
|
| 68 |
+
# cards: batch 4 = 60.3 GB, batch 5 = 73.4 GB, batch 6 OOMs. Holding
|
| 69 |
+
# global batch at 128 also requires per_device * accum == 16, so 4/4 is
|
| 70 |
+
# the largest usable microbatch; 8/2 does not fit in memory.
|
| 71 |
+
# See scripts/probe_batch_size.sh.
|
| 72 |
+
per_device_batch_size: 4
|
| 73 |
+
image_size: [224, 224]
|
| 74 |
+
video_backend: pyav
|
| 75 |
+
num_workers: 8
|
| 76 |
+
expected_world_size: 8
|
| 77 |
+
traceable_sampling: true
|
| 78 |
+
dataset_provenance_path: ${oc.env:XTRAINER2_PACK_PHONE_PROVENANCE_PATH}
|
| 79 |
+
dataset_content_fingerprint_path: ${oc.env:XTRAINER2_PACK_PHONE_FINGERPRINT_PATH}
|
| 80 |
+
valid_anchor_manifest_path: null
|
| 81 |
+
temporal_proposal_path: null
|
| 82 |
+
|
| 83 |
+
trainer:
|
| 84 |
+
use_deepspeed: true
|
| 85 |
+
max_train_steps: 52380
|
| 86 |
+
num_warmup_steps: 5000
|
| 87 |
+
save_interval: null
|
| 88 |
+
save_steps: [8730, 17460, 26190, 34920, 43650, 52380]
|
| 89 |
+
eval_interval: null
|
| 90 |
+
# Effective flow-matching repeat factor (framework.action_model has no say).
|
| 91 |
+
# Per-DiT micro-batch is per_device_batch_size * this = 4 * 4 = 16.
|
| 92 |
+
repeated_diffusion_steps: 4
|
| 93 |
+
learning_rate:
|
| 94 |
+
base: 1.0e-05
|
| 95 |
+
qwen_vl_interface: 1.0e-05
|
| 96 |
+
action_model: 1.0e-04
|
| 97 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 98 |
+
scheduler_specific_kwargs:
|
| 99 |
+
min_lr: 5.0e-07
|
| 100 |
+
freeze_modules: ""
|
| 101 |
+
trainable_modules: ""
|
| 102 |
+
frozen_modules_eval: false
|
| 103 |
+
logging_frequency: 50
|
| 104 |
+
gradient_clipping: 1.0
|
| 105 |
+
# MUST equal deepspeed_config.gradient_accumulation_steps in the accelerate
|
| 106 |
+
# yaml. DeepSpeed's value governs the optimizer; this one sizes the dataloader
|
| 107 |
+
# truncation. A mismatch is only a warning, so keep them in sync by hand.
|
| 108 |
+
# Global batch = per_device 4 * 8 GPUs * accum 4 = 128.
|
| 109 |
+
gradient_accumulation_steps: 4
|
| 110 |
+
save_format: pt
|
| 111 |
+
save_final_checkpoint: false
|
| 112 |
+
optimizer:
|
| 113 |
+
name: AdamW
|
| 114 |
+
betas: [0.9, 0.95]
|
| 115 |
+
eps: 1.0e-08
|
| 116 |
+
weight_decay: 1.0e-08
|
real_robot/source/configs/florence_xtrainer2_pick_bottle_rel_aug_smoke.yaml
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Smoke test for pick_bottle rel-EE + image-aug. Six optimizer steps with a
|
| 2 |
+
# checkpoint at step 4, which exercises relative-action statistics, random
|
| 3 |
+
# crop, and the ZeRO-3 gather-and-consolidate save path.
|
| 4 |
+
run_id: florence-xtrainer2-pick-bottle-rel-aug-smoke
|
| 5 |
+
run_root_dir: ${oc.env:OPTR_RUN_ROOT}
|
| 6 |
+
seed: 42
|
| 7 |
+
wandb_entity: null
|
| 8 |
+
wandb_project: OPTR-Florence-Xtrainer2
|
| 9 |
+
wandb_group: pick-bottle
|
| 10 |
+
wandb_job_type: smoke
|
| 11 |
+
wandb_tags:
|
| 12 |
+
- florence
|
| 13 |
+
- xtrainer2
|
| 14 |
+
- pick-bottle
|
| 15 |
+
- rel-eef
|
| 16 |
+
- image-aug
|
| 17 |
+
- smoke
|
| 18 |
+
is_debug: false
|
| 19 |
+
version_id: "0.21"
|
| 20 |
+
|
| 21 |
+
framework:
|
| 22 |
+
name: QwenGR00T
|
| 23 |
+
qwenvl:
|
| 24 |
+
# NOTE: the backbone is selected by substring match on this path
|
| 25 |
+
# (starVLA/model/modules/vlm/__init__.py). The resolved value MUST contain
|
| 26 |
+
# "florence" or startup fails with NotImplementedError.
|
| 27 |
+
base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
|
| 28 |
+
attn_implementation: eager
|
| 29 |
+
vl_hidden_dim: 1024
|
| 30 |
+
selected_view_indices: [0, 1, 2]
|
| 31 |
+
compile_vision_tower: false
|
| 32 |
+
action_model:
|
| 33 |
+
action_model_type: DiT-B
|
| 34 |
+
hidden_size: 1024
|
| 35 |
+
add_pos_embed: true
|
| 36 |
+
max_seq_len: 1024
|
| 37 |
+
action_dim: 20
|
| 38 |
+
state_dim: 20
|
| 39 |
+
action_horizon: 16
|
| 40 |
+
future_action_window_size: 15
|
| 41 |
+
# NOTE: the effective flow-matching repeat factor is trainer.repeated_diffusion_steps.
|
| 42 |
+
# A framework-level key here would be silently ignored; do not re-add it.
|
| 43 |
+
noise_beta_alpha: 1.5
|
| 44 |
+
noise_beta_beta: 1.0
|
| 45 |
+
noise_s: 0.999
|
| 46 |
+
num_timestep_buckets: 1000
|
| 47 |
+
num_inference_timesteps: 4
|
| 48 |
+
num_target_vision_tokens: 32
|
| 49 |
+
diffusion_model_cfg:
|
| 50 |
+
cross_attention_dim: 1024
|
| 51 |
+
dropout: 0.2
|
| 52 |
+
final_dropout: true
|
| 53 |
+
interleave_self_attention: true
|
| 54 |
+
norm_type: ada_norm
|
| 55 |
+
num_layers: 16
|
| 56 |
+
output_dim: 1024
|
| 57 |
+
positional_embeddings: null
|
| 58 |
+
|
| 59 |
+
datasets:
|
| 60 |
+
vla_data:
|
| 61 |
+
dataset_py: lerobot_datasets
|
| 62 |
+
data_root_dir: ${oc.env:XTRAINER2_DATA_ROOT}
|
| 63 |
+
data_mix: xtrainer2_pick_bottle
|
| 64 |
+
action_type: rel_eef
|
| 65 |
+
action_mode: rel
|
| 66 |
+
action_mode_apply_keys:
|
| 67 |
+
- action.left_position
|
| 68 |
+
- action.left_rotation
|
| 69 |
+
- action.right_position
|
| 70 |
+
- action.right_rotation
|
| 71 |
+
include_state: true
|
| 72 |
+
# 4 x 8 GPUs x accum 4 = global batch 128. Same memory envelope as the
|
| 73 |
+
# production run.
|
| 74 |
+
per_device_batch_size: 4
|
| 75 |
+
image_size: [224, 224]
|
| 76 |
+
video_backend: pyav
|
| 77 |
+
num_workers: 2
|
| 78 |
+
expected_world_size: 8
|
| 79 |
+
traceable_sampling: true
|
| 80 |
+
transform_rng_seed: 298226
|
| 81 |
+
dataset_provenance_path: ${oc.env:XTRAINER2_PICK_BOTTLE_PROVENANCE_PATH}
|
| 82 |
+
dataset_content_fingerprint_path: ${oc.env:XTRAINER2_PICK_BOTTLE_FINGERPRINT_PATH}
|
| 83 |
+
valid_anchor_manifest_path: null
|
| 84 |
+
temporal_proposal_path: null
|
| 85 |
+
|
| 86 |
+
trainer:
|
| 87 |
+
use_deepspeed: true
|
| 88 |
+
max_train_steps: 6
|
| 89 |
+
num_warmup_steps: 2
|
| 90 |
+
save_interval: null
|
| 91 |
+
save_steps: [4]
|
| 92 |
+
eval_interval: null
|
| 93 |
+
# Effective flow-matching repeat factor (framework.action_model has no say).
|
| 94 |
+
# Per-DiT micro-batch is per_device_batch_size * this = 4 * 4 = 16.
|
| 95 |
+
repeated_diffusion_steps: 4
|
| 96 |
+
learning_rate:
|
| 97 |
+
base: 1.0e-05
|
| 98 |
+
qwen_vl_interface: 1.0e-05
|
| 99 |
+
action_model: 1.0e-04
|
| 100 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 101 |
+
scheduler_specific_kwargs:
|
| 102 |
+
min_lr: 5.0e-07
|
| 103 |
+
freeze_modules: ""
|
| 104 |
+
trainable_modules: ""
|
| 105 |
+
frozen_modules_eval: false
|
| 106 |
+
logging_frequency: 1
|
| 107 |
+
gradient_clipping: 1.0
|
| 108 |
+
# MUST equal deepspeed_config.gradient_accumulation_steps in the accelerate
|
| 109 |
+
# yaml. DeepSpeed's value governs the optimizer; this one sizes the dataloader
|
| 110 |
+
# truncation. A mismatch is only a warning, so keep them in sync by hand.
|
| 111 |
+
# Global batch = per_device 4 * 8 GPUs * accum 4 = 128.
|
| 112 |
+
gradient_accumulation_steps: 4
|
| 113 |
+
save_format: pt
|
| 114 |
+
save_final_checkpoint: false
|
| 115 |
+
optimizer:
|
| 116 |
+
name: AdamW
|
| 117 |
+
betas: [0.9, 0.95]
|
| 118 |
+
eps: 1.0e-08
|
| 119 |
+
weight_decay: 1.0e-08
|
real_robot/source/configs/florence_xtrainer2_pick_bottle_rel_aug_uniform.yaml
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
run_id: florence-xtrainer2-pick-bottle-rel-aug-uniform
|
| 2 |
+
run_root_dir: ${oc.env:OPTR_RUN_ROOT}
|
| 3 |
+
seed: 42
|
| 4 |
+
wandb_entity: null
|
| 5 |
+
wandb_project: OPTR-Florence-Xtrainer2
|
| 6 |
+
wandb_group: pick-bottle
|
| 7 |
+
wandb_job_type: train
|
| 8 |
+
wandb_tags:
|
| 9 |
+
- florence
|
| 10 |
+
- xtrainer2
|
| 11 |
+
- pick-bottle
|
| 12 |
+
- rel-eef
|
| 13 |
+
- image-aug
|
| 14 |
+
- train
|
| 15 |
+
is_debug: false
|
| 16 |
+
version_id: "0.21"
|
| 17 |
+
|
| 18 |
+
framework:
|
| 19 |
+
name: QwenGR00T
|
| 20 |
+
qwenvl:
|
| 21 |
+
# NOTE: the backbone is selected by substring match on this path
|
| 22 |
+
# (starVLA/model/modules/vlm/__init__.py). The resolved value MUST contain
|
| 23 |
+
# "florence" or startup fails with NotImplementedError.
|
| 24 |
+
base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
|
| 25 |
+
attn_implementation: eager
|
| 26 |
+
vl_hidden_dim: 1024
|
| 27 |
+
selected_view_indices: [0, 1, 2]
|
| 28 |
+
# Must stay false: torch.compile with mode="reduce-overhead" (CUDA graphs,
|
| 29 |
+
# Florence2.py:150-156) fails under ZeRO-3 with
|
| 30 |
+
# "AssertionError: wrong number of dimensions" from cudagraph_trees'
|
| 31 |
+
# assert_size_stride, because gathered parameter shapes differ between the
|
| 32 |
+
# trace and the replay. Verified failing on 8 ranks.
|
| 33 |
+
compile_vision_tower: false
|
| 34 |
+
action_model:
|
| 35 |
+
action_model_type: DiT-B
|
| 36 |
+
hidden_size: 1024
|
| 37 |
+
add_pos_embed: true
|
| 38 |
+
max_seq_len: 1024
|
| 39 |
+
action_dim: 20
|
| 40 |
+
state_dim: 20
|
| 41 |
+
action_horizon: 16
|
| 42 |
+
future_action_window_size: 15
|
| 43 |
+
# NOTE: the effective flow-matching repeat factor is trainer.repeated_diffusion_steps.
|
| 44 |
+
# A framework-level key here would be silently ignored; do not re-add it.
|
| 45 |
+
noise_beta_alpha: 1.5
|
| 46 |
+
noise_beta_beta: 1.0
|
| 47 |
+
noise_s: 0.999
|
| 48 |
+
num_timestep_buckets: 1000
|
| 49 |
+
num_inference_timesteps: 4
|
| 50 |
+
num_target_vision_tokens: 32
|
| 51 |
+
diffusion_model_cfg:
|
| 52 |
+
cross_attention_dim: 1024
|
| 53 |
+
dropout: 0.2
|
| 54 |
+
final_dropout: true
|
| 55 |
+
interleave_self_attention: true
|
| 56 |
+
norm_type: ada_norm
|
| 57 |
+
num_layers: 16
|
| 58 |
+
output_dim: 1024
|
| 59 |
+
positional_embeddings: null
|
| 60 |
+
|
| 61 |
+
datasets:
|
| 62 |
+
vla_data:
|
| 63 |
+
dataset_py: lerobot_datasets
|
| 64 |
+
data_root_dir: ${oc.env:XTRAINER2_DATA_ROOT}
|
| 65 |
+
data_mix: xtrainer2_pick_bottle
|
| 66 |
+
action_type: rel_eef
|
| 67 |
+
action_mode: rel
|
| 68 |
+
# Gripper stays absolute; xyz subtracts the current EE; quaternions use
|
| 69 |
+
# q_rel = inv(q_s0) ⊗ q_a rather than component-wise subtraction.
|
| 70 |
+
action_mode_apply_keys:
|
| 71 |
+
- action.left_position
|
| 72 |
+
- action.left_rotation
|
| 73 |
+
- action.right_position
|
| 74 |
+
- action.right_rotation
|
| 75 |
+
include_state: true
|
| 76 |
+
# 4 x 8 GPUs x accum 4 = global batch 128. Same memory envelope as pack_phone.
|
| 77 |
+
per_device_batch_size: 4
|
| 78 |
+
image_size: [224, 224]
|
| 79 |
+
video_backend: pyav
|
| 80 |
+
num_workers: 8
|
| 81 |
+
expected_world_size: 8
|
| 82 |
+
traceable_sampling: true
|
| 83 |
+
transform_rng_seed: 298226
|
| 84 |
+
dataset_provenance_path: ${oc.env:XTRAINER2_PICK_BOTTLE_PROVENANCE_PATH}
|
| 85 |
+
dataset_content_fingerprint_path: ${oc.env:XTRAINER2_PICK_BOTTLE_FINGERPRINT_PATH}
|
| 86 |
+
valid_anchor_manifest_path: null
|
| 87 |
+
temporal_proposal_path: null
|
| 88 |
+
|
| 89 |
+
trainer:
|
| 90 |
+
use_deepspeed: true
|
| 91 |
+
# 75758 frames / global batch 128 ≈ 591.86 → 592 optimizer steps per data epoch.
|
| 92 |
+
max_train_steps: 35520
|
| 93 |
+
num_warmup_steps: 3500
|
| 94 |
+
save_interval: null
|
| 95 |
+
save_steps: [5920, 11840, 17760, 23680, 29600, 35520]
|
| 96 |
+
eval_interval: null
|
| 97 |
+
# Effective flow-matching repeat factor (framework.action_model has no say).
|
| 98 |
+
# Per-DiT micro-batch is per_device_batch_size * this = 4 * 4 = 16.
|
| 99 |
+
repeated_diffusion_steps: 4
|
| 100 |
+
learning_rate:
|
| 101 |
+
base: 1.0e-05
|
| 102 |
+
qwen_vl_interface: 1.0e-05
|
| 103 |
+
action_model: 1.0e-04
|
| 104 |
+
lr_scheduler_type: cosine_with_min_lr
|
| 105 |
+
scheduler_specific_kwargs:
|
| 106 |
+
min_lr: 5.0e-07
|
| 107 |
+
freeze_modules: ""
|
| 108 |
+
trainable_modules: ""
|
| 109 |
+
frozen_modules_eval: false
|
| 110 |
+
logging_frequency: 50
|
| 111 |
+
gradient_clipping: 1.0
|
| 112 |
+
# MUST equal deepspeed_config.gradient_accumulation_steps in the accelerate
|
| 113 |
+
# yaml. DeepSpeed's value governs the optimizer; this one sizes the dataloader
|
| 114 |
+
# truncation. A mismatch is only a warning, so keep them in sync by hand.
|
| 115 |
+
# Global batch = per_device 4 * 8 GPUs * accum 4 = 128.
|
| 116 |
+
gradient_accumulation_steps: 4
|
| 117 |
+
save_format: pt
|
| 118 |
+
save_final_checkpoint: false
|
| 119 |
+
optimizer:
|
| 120 |
+
name: AdamW
|
| 121 |
+
betas: [0.9, 0.95]
|
| 122 |
+
eps: 1.0e-08
|
| 123 |
+
weight_decay: 1.0e-08
|
real_robot/source/deployment/__init__.py
ADDED
|
File without changes
|
real_robot/source/deployment/model_server/__init__.py
ADDED
|
File without changes
|
real_robot/source/deployment/model_server/policy_norm_processor.py
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License.
|
| 3 |
+
"""PolicyNormProcessor — reuse the training-time ComposedModalityTransform.
|
| 4 |
+
|
| 5 |
+
This class replaces the hand-rolled un-normalization math that previously
|
| 6 |
+
lived in every eval client. It rebuilds the *exact* ``ComposedModalityTransform``
|
| 7 |
+
used at training time from a checkpoint:
|
| 8 |
+
|
| 9 |
+
1. Read ``config.yaml`` next to the ckpt → resolve ``data_mix`` →
|
| 10 |
+
look up ``robot_type`` from ``DATASET_NAMED_MIXTURES`` →
|
| 11 |
+
fetch the ``DataConfig`` from ``ROBOT_TYPE_CONFIG_MAP``.
|
| 12 |
+
2. Build the transform pipeline via ``data_config.transform()``.
|
| 13 |
+
3. Reconstruct a ``DatasetMetadata`` from the saved
|
| 14 |
+
``dataset_statistics.json`` (which stores **combined** per-modality
|
| 15 |
+
arrays of length ``D``) by splitting it into per-key entries that match
|
| 16 |
+
``data_config.action_keys`` / ``state_keys``.
|
| 17 |
+
4. ``set_metadata(...)`` binds the metadata into every transform.
|
| 18 |
+
|
| 19 |
+
After construction the caller simply invokes :meth:`unapply_actions` /
|
| 20 |
+
:meth:`apply_state` to get/normalize tensors using the same code path as
|
| 21 |
+
training — there is no second source of truth for normalization math.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
from __future__ import annotations
|
| 25 |
+
|
| 26 |
+
import logging
|
| 27 |
+
from typing import Any, Dict, List, Optional, Sequence
|
| 28 |
+
|
| 29 |
+
import numpy as np
|
| 30 |
+
import torch
|
| 31 |
+
|
| 32 |
+
from starVLA.dataloader.gr00t_lerobot.registry import (
|
| 33 |
+
DATASET_NAMED_MIXTURES,
|
| 34 |
+
ROBOT_TYPE_CONFIG_MAP,
|
| 35 |
+
)
|
| 36 |
+
from starVLA.dataloader.gr00t_lerobot.schema import (
|
| 37 |
+
DatasetMetadata,
|
| 38 |
+
StateActionMetadata,
|
| 39 |
+
)
|
| 40 |
+
from starVLA.dataloader.gr00t_lerobot.transform.base import ComposedModalityTransform
|
| 41 |
+
from starVLA.model.framework.share_tools import read_mode_config
|
| 42 |
+
|
| 43 |
+
logger = logging.getLogger(__name__)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _resolve_robot_type(
|
| 47 |
+
model_cfg: dict,
|
| 48 |
+
unnorm_key: Optional[str] = None,
|
| 49 |
+
) -> str:
|
| 50 |
+
"""Look up the training robot_type from the saved cfg.
|
| 51 |
+
|
| 52 |
+
Convention used by starVLA train scripts:
|
| 53 |
+
``cfg.datasets.vla_data.data_mix`` is a key in
|
| 54 |
+
``DATASET_NAMED_MIXTURES`` whose value is a list of
|
| 55 |
+
``(dataset_name, weight, robot_type)`` tuples.
|
| 56 |
+
|
| 57 |
+
When a data_mix contains entries from multiple robot types (e.g.
|
| 58 |
+
``bridge_rt_1`` covers ``oxe_bridge`` + ``oxe_rt1``), ``unnorm_key``
|
| 59 |
+
is used to identify which embodiment is requested. In those mixtures
|
| 60 |
+
the ``robot_type`` field of each entry **matches** the top-level key in
|
| 61 |
+
``dataset_statistics.json``, so ``unnorm_key`` serves as the selector.
|
| 62 |
+
"""
|
| 63 |
+
try:
|
| 64 |
+
data_mix = model_cfg["datasets"]["vla_data"]["data_mix"]
|
| 65 |
+
except (KeyError, TypeError) as e:
|
| 66 |
+
raise KeyError(
|
| 67 |
+
"ckpt config.yaml is missing `datasets.vla_data.data_mix`; "
|
| 68 |
+
"cannot resolve training-time robot_type."
|
| 69 |
+
) from e
|
| 70 |
+
|
| 71 |
+
if data_mix not in DATASET_NAMED_MIXTURES:
|
| 72 |
+
raise KeyError(
|
| 73 |
+
f"data_mix={data_mix!r} not in DATASET_NAMED_MIXTURES "
|
| 74 |
+
f"(available: {sorted(DATASET_NAMED_MIXTURES.keys())[:20]} ...). "
|
| 75 |
+
"Did you forget to register the example under examples/<bench>/train_files/data_registry/?"
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
mixture = DATASET_NAMED_MIXTURES[data_mix]
|
| 79 |
+
robot_types = sorted({entry[2] for entry in mixture})
|
| 80 |
+
|
| 81 |
+
if len(robot_types) == 1:
|
| 82 |
+
return robot_types[0]
|
| 83 |
+
|
| 84 |
+
# Multiple robot types in the mixture.
|
| 85 |
+
# Use unnorm_key as a direct selector: for multi-robot mixtures the
|
| 86 |
+
# dataset_statistics.json top-level keys equal the robot_type values.
|
| 87 |
+
if unnorm_key is not None and unnorm_key in robot_types:
|
| 88 |
+
return unnorm_key
|
| 89 |
+
|
| 90 |
+
raise ValueError(
|
| 91 |
+
f"data_mix={data_mix!r} contains multiple robot_types {robot_types}. "
|
| 92 |
+
"Pass `unnorm_key` matching one of them to disambiguate "
|
| 93 |
+
f"(e.g. unnorm_key={robot_types[0]!r})."
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _infer_key_dims(
|
| 98 |
+
data_config: Any,
|
| 99 |
+
combined_stats: Dict[str, Any],
|
| 100 |
+
modality_keys: Sequence[str],
|
| 101 |
+
modality: str,
|
| 102 |
+
) -> Dict[str, int]:
|
| 103 |
+
"""Compute per-key dimensions for splitting combined stats arrays.
|
| 104 |
+
|
| 105 |
+
Lookup priority:
|
| 106 |
+
1. ``data_config.<modality>_key_dims`` — explicit dict classvar on the DataConfig
|
| 107 |
+
(required for DataConfigs with non-uniform or multi-dim keys).
|
| 108 |
+
2. Infer uniformly from stats array length: if ``D_total / n_keys`` is an
|
| 109 |
+
integer, use that as the uniform per-key dim.
|
| 110 |
+
3. Fall back to dim=1 when no stats are available (empty combined dict).
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
data_config: The DataConfig instance for the current robot type.
|
| 114 |
+
combined_stats: The raw stats dict for the chosen unnorm_key (the value
|
| 115 |
+
of ``norm_stats[unnorm_key]`` from ``dataset_statistics.json``).
|
| 116 |
+
modality_keys: Ordered list of full keys for this modality
|
| 117 |
+
(e.g. ``["action.left_joints", ...]``).
|
| 118 |
+
modality: ``"action"`` or ``"state"``.
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
Dict mapping each full key to its integer dimension.
|
| 122 |
+
"""
|
| 123 |
+
attr = f"{modality}_key_dims"
|
| 124 |
+
if hasattr(data_config, attr):
|
| 125 |
+
return dict(getattr(data_config, attr))
|
| 126 |
+
|
| 127 |
+
combined = combined_stats.get(modality, {})
|
| 128 |
+
stat_arr = next((v for k, v in combined.items() if k != "mask"), None)
|
| 129 |
+
n_keys = len(modality_keys)
|
| 130 |
+
if stat_arr is not None and n_keys > 0:
|
| 131 |
+
D_total = len(stat_arr)
|
| 132 |
+
if D_total == n_keys:
|
| 133 |
+
return {k: 1 for k in modality_keys}
|
| 134 |
+
elif n_keys > 0 and D_total % n_keys == 0:
|
| 135 |
+
dim = D_total // n_keys
|
| 136 |
+
return {k: dim for k in modality_keys}
|
| 137 |
+
else:
|
| 138 |
+
raise ValueError(
|
| 139 |
+
f"Cannot infer per-key dims for modality={modality!r}: "
|
| 140 |
+
f"D_total={D_total} is not evenly divisible by n_keys={n_keys}. "
|
| 141 |
+
f"Add `{attr} = {{key: dim, ...}}` to the DataConfig "
|
| 142 |
+
f"(keys={list(modality_keys)})."
|
| 143 |
+
)
|
| 144 |
+
return {k: 1 for k in modality_keys}
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
#: Width of each rotation representation, used to size a key after
|
| 148 |
+
#: ``StateActionTransform`` has converted it (see ``target_rotations``).
|
| 149 |
+
ROTATION_DIMS: Dict[str, int] = {
|
| 150 |
+
"axis_angle": 3,
|
| 151 |
+
"euler_angles": 3,
|
| 152 |
+
"quaternion": 4,
|
| 153 |
+
"rotation_6d": 6,
|
| 154 |
+
"matrix": 9,
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def _rotation_dim(rotation_type: str) -> int:
|
| 159 |
+
"""Width of ``rotation_type``; ``euler_angles_*`` carries a convention suffix."""
|
| 160 |
+
if rotation_type.startswith("euler_angles"):
|
| 161 |
+
return ROTATION_DIMS["euler_angles"]
|
| 162 |
+
if rotation_type not in ROTATION_DIMS:
|
| 163 |
+
raise ValueError(
|
| 164 |
+
f"Unknown rotation representation {rotation_type!r}; "
|
| 165 |
+
f"expected one of {sorted(ROTATION_DIMS)}"
|
| 166 |
+
)
|
| 167 |
+
return ROTATION_DIMS[rotation_type]
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _target_rotations(transform: ComposedModalityTransform) -> Dict[str, str]:
|
| 171 |
+
"""Collect ``key -> target rotation`` from every StateActionTransform."""
|
| 172 |
+
targets: Dict[str, str] = {}
|
| 173 |
+
for sub_transform in getattr(transform, "transforms", []):
|
| 174 |
+
for key, rotation in getattr(sub_transform, "target_rotations", {}).items():
|
| 175 |
+
targets[key] = rotation
|
| 176 |
+
return targets
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def _model_space_dims(
|
| 180 |
+
raw_key_dims: Dict[str, int],
|
| 181 |
+
target_rotations: Dict[str, str],
|
| 182 |
+
) -> Dict[str, int]:
|
| 183 |
+
"""Per-key widths *after* the training transforms.
|
| 184 |
+
|
| 185 |
+
The stats file and the robot speak raw widths (a quaternion is 4); the model
|
| 186 |
+
tensor is what ``target_rotations`` produced (rotation_6d is 6). For
|
| 187 |
+
xtrainer2 this is the 16 -> 20 expansion.
|
| 188 |
+
"""
|
| 189 |
+
return {
|
| 190 |
+
key: _rotation_dim(target_rotations[key]) if key in target_rotations else dim
|
| 191 |
+
for key, dim in raw_key_dims.items()
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def _build_dataset_metadata(
|
| 196 |
+
stats_for_key: Dict[str, Any],
|
| 197 |
+
embodiment_tag: Any,
|
| 198 |
+
action_keys: Sequence[str],
|
| 199 |
+
state_keys: Sequence[str],
|
| 200 |
+
action_key_dims: Optional[Dict[str, int]] = None,
|
| 201 |
+
state_key_dims: Optional[Dict[str, int]] = None,
|
| 202 |
+
rotation_types: Optional[Dict[str, str]] = None,
|
| 203 |
+
) -> DatasetMetadata:
|
| 204 |
+
"""Convert the *combined* stats arrays from ``dataset_statistics.json``
|
| 205 |
+
back into a per-subkey :class:`DatasetMetadata` matching what the training
|
| 206 |
+
pipeline produced.
|
| 207 |
+
|
| 208 |
+
The saved ``dataset_statistics.json`` stores stats as flat arrays of
|
| 209 |
+
length ``D = sum(per_key_dims)``. For each ``"action.<sub>"`` key we
|
| 210 |
+
slice out ``dim_k`` elements starting at the current cursor and store them
|
| 211 |
+
under ``statistics.action.<sub> = {"min": [v0..v_{k-1}], ...}``.
|
| 212 |
+
|
| 213 |
+
Args:
|
| 214 |
+
stats_for_key: ``norm_stats[unnorm_key]`` from ``dataset_statistics.json``.
|
| 215 |
+
embodiment_tag: Embodiment tag from the DataConfig.
|
| 216 |
+
action_keys: Ordered list of full action keys.
|
| 217 |
+
state_keys: Ordered list of full state keys.
|
| 218 |
+
action_key_dims: Per-key dimension dict (``{full_key: dim_k}``).
|
| 219 |
+
Defaults to dim=1 for every key.
|
| 220 |
+
state_key_dims: Per-key dimension dict for state keys.
|
| 221 |
+
Defaults to dim=1 for every key.
|
| 222 |
+
rotation_types: ``full_key -> source rotation representation`` for keys
|
| 223 |
+
the training transforms rotate. Without it those transforms cannot
|
| 224 |
+
be rebuilt, because the dataset's ``modality.json`` is not shipped
|
| 225 |
+
with the checkpoint.
|
| 226 |
+
"""
|
| 227 |
+
if action_key_dims is None:
|
| 228 |
+
action_key_dims = {k: 1 for k in action_keys}
|
| 229 |
+
if state_key_dims is None:
|
| 230 |
+
state_key_dims = {k: 1 for k in state_keys}
|
| 231 |
+
rotation_types = rotation_types or {}
|
| 232 |
+
|
| 233 |
+
def _split_combined(
|
| 234 |
+
combined: Dict[str, Sequence[float]],
|
| 235 |
+
keys: Sequence[str],
|
| 236 |
+
key_dims: Dict[str, int],
|
| 237 |
+
):
|
| 238 |
+
"""Split combined arrays into per-subkey dicts using per-key dims.
|
| 239 |
+
|
| 240 |
+
``combined`` looks like ``{"min": [..D..], "max": [..D..], "mask": [..D..], ...}``.
|
| 241 |
+
``keys`` is the ordered list of full keys.
|
| 242 |
+
``key_dims`` maps each full key to its integer dimension.
|
| 243 |
+
Returns ``(stats_per_subkey, meta_per_subkey)``.
|
| 244 |
+
"""
|
| 245 |
+
stats_per_subkey: Dict[str, Dict[str, List[float]]] = {}
|
| 246 |
+
meta_per_subkey: Dict[str, StateActionMetadata] = {}
|
| 247 |
+
cursor = 0
|
| 248 |
+
for full_key in keys:
|
| 249 |
+
subkey = full_key.split(".", 1)[1]
|
| 250 |
+
dim_k = key_dims.get(full_key, 1)
|
| 251 |
+
per_key: Dict[str, List[float]] = {}
|
| 252 |
+
for stat_name, arr in combined.items():
|
| 253 |
+
if stat_name == "mask":
|
| 254 |
+
continue
|
| 255 |
+
end = cursor + dim_k
|
| 256 |
+
if end > len(arr):
|
| 257 |
+
# Saved combined array shorter than expected (truncated
|
| 258 |
+
# pad channels etc.). Skip this stat field silently.
|
| 259 |
+
continue
|
| 260 |
+
per_key[stat_name] = [float(v) for v in arr[cursor:end]]
|
| 261 |
+
stats_per_subkey[subkey] = per_key
|
| 262 |
+
meta_per_subkey[subkey] = StateActionMetadata(
|
| 263 |
+
absolute=True,
|
| 264 |
+
rotation_type=rotation_types.get(full_key),
|
| 265 |
+
shape=(dim_k,),
|
| 266 |
+
continuous=True,
|
| 267 |
+
)
|
| 268 |
+
cursor += dim_k
|
| 269 |
+
return stats_per_subkey, meta_per_subkey
|
| 270 |
+
|
| 271 |
+
action_combined = stats_for_key.get("action", {})
|
| 272 |
+
state_combined = stats_for_key.get("state", {})
|
| 273 |
+
|
| 274 |
+
action_stats, action_meta = _split_combined(action_combined, action_keys, action_key_dims)
|
| 275 |
+
state_stats, state_meta = _split_combined(state_combined, state_keys, state_key_dims)
|
| 276 |
+
|
| 277 |
+
# Pydantic accepts dict input with field validators
|
| 278 |
+
return DatasetMetadata.model_validate(
|
| 279 |
+
{
|
| 280 |
+
"statistics": {
|
| 281 |
+
"state": state_stats,
|
| 282 |
+
"action": action_stats,
|
| 283 |
+
},
|
| 284 |
+
"modalities": {
|
| 285 |
+
"video": {},
|
| 286 |
+
"state": state_meta,
|
| 287 |
+
"action": action_meta,
|
| 288 |
+
},
|
| 289 |
+
"embodiment_tag": embodiment_tag.value
|
| 290 |
+
if hasattr(embodiment_tag, "value")
|
| 291 |
+
else embodiment_tag,
|
| 292 |
+
}
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
class PolicyNormProcessor:
|
| 297 |
+
"""Server-side normalization helper backed by training-time transforms.
|
| 298 |
+
|
| 299 |
+
Construct once per checkpoint; call :meth:`unapply_actions` to convert
|
| 300 |
+
a normalized action chunk ``(T, D)`` back to env-space.
|
| 301 |
+
|
| 302 |
+
Args:
|
| 303 |
+
ckpt_path: Path to the ``*.pt`` checkpoint (the loader looks for
|
| 304 |
+
``config.yaml`` and ``dataset_statistics.json`` two dirs up).
|
| 305 |
+
unnorm_key: Which top-level key in ``dataset_statistics.json`` to use.
|
| 306 |
+
``None`` → auto-pick the only key.
|
| 307 |
+
"""
|
| 308 |
+
|
| 309 |
+
def __init__(self, ckpt_path: str, unnorm_key: Optional[str] = None) -> None:
|
| 310 |
+
self._ckpt_path = str(ckpt_path)
|
| 311 |
+
cfg, norm_stats = read_mode_config(self._ckpt_path)
|
| 312 |
+
self._model_cfg = cfg
|
| 313 |
+
self._norm_stats = norm_stats
|
| 314 |
+
|
| 315 |
+
# 3-early) Pick the requested unnorm_key (or auto-select) BEFORE
|
| 316 |
+
# resolving robot_type so we can use it as a hint for multi-robot mixtures.
|
| 317 |
+
if unnorm_key is None:
|
| 318 |
+
if len(norm_stats) == 1:
|
| 319 |
+
unnorm_key = next(iter(norm_stats.keys()))
|
| 320 |
+
# else: defer error to step 3 below after robot_type resolution attempt
|
| 321 |
+
elif unnorm_key not in norm_stats:
|
| 322 |
+
raise KeyError(
|
| 323 |
+
f"unnorm_key={unnorm_key!r} not in {list(norm_stats.keys())}"
|
| 324 |
+
)
|
| 325 |
+
self._unnorm_key = unnorm_key # may still be None for multi-key case
|
| 326 |
+
|
| 327 |
+
# 1) Resolve which DataConfig was used at training.
|
| 328 |
+
robot_type = _resolve_robot_type(cfg, unnorm_key=unnorm_key)
|
| 329 |
+
if robot_type not in ROBOT_TYPE_CONFIG_MAP:
|
| 330 |
+
raise KeyError(
|
| 331 |
+
f"robot_type={robot_type!r} not in ROBOT_TYPE_CONFIG_MAP "
|
| 332 |
+
f"(available: {sorted(ROBOT_TYPE_CONFIG_MAP.keys())}). "
|
| 333 |
+
"Make sure the example's data_registry/data_config.py is importable."
|
| 334 |
+
)
|
| 335 |
+
self._data_config = ROBOT_TYPE_CONFIG_MAP[robot_type]
|
| 336 |
+
self._action_keys: List[str] = list(self._data_config.action_keys)
|
| 337 |
+
self._state_keys: List[str] = list(getattr(self._data_config, "state_keys", []))
|
| 338 |
+
self._modality_key_set = set(self._action_keys) | set(self._state_keys)
|
| 339 |
+
|
| 340 |
+
# 2) Build training-time transform pipeline.
|
| 341 |
+
transform = self._data_config.transform()
|
| 342 |
+
if not isinstance(transform, ComposedModalityTransform):
|
| 343 |
+
transform = ComposedModalityTransform(transforms=[transform])
|
| 344 |
+
self._transform = transform
|
| 345 |
+
|
| 346 |
+
# 3) Pick the requested unnorm_key (finalize; error if still None here).
|
| 347 |
+
if self._unnorm_key is None:
|
| 348 |
+
raise ValueError(
|
| 349 |
+
f"Multiple unnorm_keys in dataset_statistics.json: "
|
| 350 |
+
f"{list(norm_stats.keys())}. Pass unnorm_key explicitly."
|
| 351 |
+
)
|
| 352 |
+
unnorm_key = self._unnorm_key
|
| 353 |
+
|
| 354 |
+
# 4) Resolve per-key dims (handles multi-d action/state keys).
|
| 355 |
+
stats_for_unnorm = norm_stats[unnorm_key]
|
| 356 |
+
self._action_key_dims: Dict[str, int] = _infer_key_dims(
|
| 357 |
+
self._data_config, stats_for_unnorm, self._action_keys, "action"
|
| 358 |
+
)
|
| 359 |
+
self._state_key_dims: Dict[str, int] = _infer_key_dims(
|
| 360 |
+
self._data_config, stats_for_unnorm, self._state_keys, "state"
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
# 5) Widths of the model tensor, which differ from the raw widths for
|
| 364 |
+
# every key the transforms rotate (xtrainer2: quaternion 4 -> 6D).
|
| 365 |
+
target_rotations = _target_rotations(self._transform)
|
| 366 |
+
self._action_model_dims = _model_space_dims(self._action_key_dims, target_rotations)
|
| 367 |
+
self._state_model_dims = _model_space_dims(self._state_key_dims, target_rotations)
|
| 368 |
+
|
| 369 |
+
# 6) Build & bind metadata.
|
| 370 |
+
ds_meta = _build_dataset_metadata(
|
| 371 |
+
stats_for_key=stats_for_unnorm,
|
| 372 |
+
embodiment_tag=self._data_config.embodiment_tag,
|
| 373 |
+
action_keys=self._action_keys,
|
| 374 |
+
state_keys=self._state_keys,
|
| 375 |
+
action_key_dims=self._action_key_dims,
|
| 376 |
+
state_key_dims=self._state_key_dims,
|
| 377 |
+
rotation_types=dict(getattr(self._data_config, "rotation_types", {})),
|
| 378 |
+
)
|
| 379 |
+
# Only the state/action transforms are bound. The video transforms would
|
| 380 |
+
# demand per-camera resolutions that the checkpoint does not carry, and
|
| 381 |
+
# the server never runs them: images are resized by the framework's own
|
| 382 |
+
# inference path.
|
| 383 |
+
self._state_action_transforms = [
|
| 384 |
+
sub_transform
|
| 385 |
+
for sub_transform in self._transform.transforms
|
| 386 |
+
if self._modality_key_set.intersection(getattr(sub_transform, "apply_to", []))
|
| 387 |
+
]
|
| 388 |
+
for sub_transform in self._state_action_transforms:
|
| 389 |
+
sub_transform.set_metadata(ds_meta)
|
| 390 |
+
sub_transform.eval() # mark transforms as eval-mode
|
| 391 |
+
|
| 392 |
+
logger.info(
|
| 393 |
+
"PolicyNormProcessor ready: robot_type=%s, unnorm_key=%s, "
|
| 394 |
+
"action_keys=%s (raw dims=%s, model dims=%s), state_keys=%s "
|
| 395 |
+
"(raw dims=%s, model dims=%s)",
|
| 396 |
+
robot_type,
|
| 397 |
+
unnorm_key,
|
| 398 |
+
self._action_keys,
|
| 399 |
+
[self._action_key_dims[k] for k in self._action_keys],
|
| 400 |
+
[self._action_model_dims[k] for k in self._action_keys],
|
| 401 |
+
self._state_keys,
|
| 402 |
+
[self._state_key_dims[k] for k in self._state_keys],
|
| 403 |
+
[self._state_model_dims[k] for k in self._state_keys],
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
# ------------------------------------------------------------------
|
| 407 |
+
# Public properties
|
| 408 |
+
# ------------------------------------------------------------------
|
| 409 |
+
@property
|
| 410 |
+
def action_keys(self) -> List[str]:
|
| 411 |
+
return list(self._action_keys)
|
| 412 |
+
|
| 413 |
+
@property
|
| 414 |
+
def state_keys(self) -> List[str]:
|
| 415 |
+
return list(self._state_keys)
|
| 416 |
+
|
| 417 |
+
@property
|
| 418 |
+
def unnorm_key(self) -> str:
|
| 419 |
+
return self._unnorm_key
|
| 420 |
+
|
| 421 |
+
@property
|
| 422 |
+
def available_unnorm_keys(self) -> List[str]:
|
| 423 |
+
return list(self._norm_stats.keys())
|
| 424 |
+
|
| 425 |
+
@property
|
| 426 |
+
def transform(self) -> ComposedModalityTransform:
|
| 427 |
+
return self._transform
|
| 428 |
+
|
| 429 |
+
@property
|
| 430 |
+
def action_dim(self) -> int:
|
| 431 |
+
"""Width of the model action vector (post-transform)."""
|
| 432 |
+
return sum(self._action_model_dims[key] for key in self._action_keys)
|
| 433 |
+
|
| 434 |
+
@property
|
| 435 |
+
def raw_action_dim(self) -> int:
|
| 436 |
+
"""Width of an action in robot/dataset coordinates."""
|
| 437 |
+
return sum(self._action_key_dims[key] for key in self._action_keys)
|
| 438 |
+
|
| 439 |
+
@property
|
| 440 |
+
def state_dim(self) -> int:
|
| 441 |
+
"""Width of the model state vector (post-transform)."""
|
| 442 |
+
return sum(self._state_model_dims[key] for key in self._state_keys)
|
| 443 |
+
|
| 444 |
+
@property
|
| 445 |
+
def raw_state_dim(self) -> int:
|
| 446 |
+
"""Width of a state as the robot reports it."""
|
| 447 |
+
return sum(self._state_key_dims[key] for key in self._state_keys)
|
| 448 |
+
|
| 449 |
+
# ------------------------------------------------------------------
|
| 450 |
+
# Forward path (robot observation → model state)
|
| 451 |
+
# ------------------------------------------------------------------
|
| 452 |
+
def apply_state(self, raw_state: np.ndarray) -> np.ndarray:
|
| 453 |
+
"""Normalize a raw state exactly as the training dataloader did.
|
| 454 |
+
|
| 455 |
+
Args:
|
| 456 |
+
raw_state: ``(raw_state_dim,)`` or ``(T, raw_state_dim)`` in dataset
|
| 457 |
+
coordinates -- i.e. after any robot-specific reordering, with
|
| 458 |
+
rotations still in their source representation.
|
| 459 |
+
|
| 460 |
+
Returns:
|
| 461 |
+
``(T, state_dim)`` normalized state, the layout the action head was
|
| 462 |
+
trained on.
|
| 463 |
+
"""
|
| 464 |
+
state = np.asarray(raw_state, dtype=np.float32)
|
| 465 |
+
if state.ndim == 1:
|
| 466 |
+
state = state[None, :]
|
| 467 |
+
if state.ndim != 2:
|
| 468 |
+
raise ValueError(f"Expected (raw_state_dim,) or (T, raw_state_dim); got {state.shape}")
|
| 469 |
+
if state.shape[-1] != self.raw_state_dim:
|
| 470 |
+
raise ValueError(
|
| 471 |
+
f"Expected {self.raw_state_dim} raw state dims "
|
| 472 |
+
f"(state_keys={self._state_keys}, dims="
|
| 473 |
+
f"{[self._state_key_dims[k] for k in self._state_keys]}); got {state.shape[-1]}"
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
data: Dict[str, Any] = {}
|
| 477 |
+
cursor = 0
|
| 478 |
+
for full_key in self._state_keys:
|
| 479 |
+
dim_k = self._state_key_dims[full_key]
|
| 480 |
+
data[full_key] = state[:, cursor : cursor + dim_k]
|
| 481 |
+
cursor += dim_k
|
| 482 |
+
|
| 483 |
+
for sub_transform in self._state_action_transforms:
|
| 484 |
+
data = sub_transform(data)
|
| 485 |
+
|
| 486 |
+
parts: List[np.ndarray] = []
|
| 487 |
+
for full_key in self._state_keys:
|
| 488 |
+
value = data[full_key]
|
| 489 |
+
if isinstance(value, torch.Tensor):
|
| 490 |
+
value = value.detach().cpu().numpy()
|
| 491 |
+
parts.append(np.asarray(value, dtype=np.float32))
|
| 492 |
+
return np.concatenate(parts, axis=-1)
|
| 493 |
+
|
| 494 |
+
# ------------------------------------------------------------------
|
| 495 |
+
# Inverse path (model output → env action)
|
| 496 |
+
# ------------------------------------------------------------------
|
| 497 |
+
def unapply_actions(self, normalized_actions: np.ndarray) -> np.ndarray:
|
| 498 |
+
"""Invert action normalization using the training-time pipeline.
|
| 499 |
+
|
| 500 |
+
Args:
|
| 501 |
+
normalized_actions: shape ``(T, D)`` where ``D == action_dim``.
|
| 502 |
+
|
| 503 |
+
Returns:
|
| 504 |
+
``(T, raw_action_dim)`` un-normalized actions in env coordinates.
|
| 505 |
+
"""
|
| 506 |
+
normalized_actions = np.asarray(normalized_actions)
|
| 507 |
+
assert normalized_actions.ndim == 2, (
|
| 508 |
+
f"Expected (T, D); got shape {normalized_actions.shape}"
|
| 509 |
+
)
|
| 510 |
+
|
| 511 |
+
# Split (T, D) into per-key {full_key: torch.Tensor[T, dim_k]}.
|
| 512 |
+
data: Dict[str, torch.Tensor] = {}
|
| 513 |
+
cursor = 0
|
| 514 |
+
for full_key in self._action_keys:
|
| 515 |
+
dim_k = self._action_model_dims.get(full_key, 1)
|
| 516 |
+
slice_ = normalized_actions[..., cursor : cursor + dim_k]
|
| 517 |
+
data[full_key] = torch.as_tensor(slice_, dtype=torch.float32)
|
| 518 |
+
cursor += dim_k
|
| 519 |
+
|
| 520 |
+
if cursor != normalized_actions.shape[-1]:
|
| 521 |
+
raise ValueError(
|
| 522 |
+
f"Sum of per-key model dims ({cursor}) != action_dim "
|
| 523 |
+
f"({normalized_actions.shape[-1]}). "
|
| 524 |
+
f"action_keys={self._action_keys}, "
|
| 525 |
+
f"action_model_dims={self._action_model_dims}"
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
out: Dict[str, Any] = data
|
| 529 |
+
for sub_transform in reversed(self._state_action_transforms):
|
| 530 |
+
out = sub_transform.unapply(out)
|
| 531 |
+
|
| 532 |
+
parts: List[np.ndarray] = []
|
| 533 |
+
for full_key in self._action_keys:
|
| 534 |
+
v = out[full_key]
|
| 535 |
+
if isinstance(v, torch.Tensor):
|
| 536 |
+
v = v.detach().cpu().numpy()
|
| 537 |
+
parts.append(np.asarray(v))
|
| 538 |
+
return np.concatenate(parts, axis=-1)
|
real_robot/source/deployment/model_server/policy_wrapper.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License.
|
| 3 |
+
"""Policy server wrapper.
|
| 4 |
+
|
| 5 |
+
Encapsulates a `baseframework` instance plus a :class:`PolicyNormProcessor`
|
| 6 |
+
that reuses the *training-time* :class:`ComposedModalityTransform` for action
|
| 7 |
+
un-normalization (no hand-rolled math). The websocket server returns
|
| 8 |
+
already-unnormalized actions.
|
| 9 |
+
|
| 10 |
+
Client-side responsibilities that REMAIN on the client:
|
| 11 |
+
- environment-specific adapters (image_history, gripper sticky, action
|
| 12 |
+
ensembling)
|
| 13 |
+
- chunk-cache scheduling (`step % chunk_size == 0` triggers a new infer)
|
| 14 |
+
|
| 15 |
+
Embodiments whose DataConfig declares a ``robot_command_layout`` (currently
|
| 16 |
+
xtrainer2) additionally get the raw-robot conversions here, so the client never
|
| 17 |
+
has to reproduce training-time normalization or the quaternion order:
|
| 18 |
+
|
| 19 |
+
- ``state`` arrives in robot coordinates (16-D, xyzw) and is reordered to the
|
| 20 |
+
training layout, then normalized with the training transforms (20-D)
|
| 21 |
+
- returned actions are un-normalized *and* reordered back to xyzw
|
| 22 |
+
|
| 23 |
+
Exposed API:
|
| 24 |
+
- ``metadata`` (dict, sent at handshake): ``action_chunk_size``,
|
| 25 |
+
``available_unnorm_keys``, ``action_keys``, ``state_keys``, plus the
|
| 26 |
+
embodiment's raw-robot contract.
|
| 27 |
+
- ``predict_action(examples, unnorm_key=None, **kwargs)`` returns
|
| 28 |
+
``{"actions": np.ndarray[B, T, action_dim]}``.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
import logging
|
| 34 |
+
from typing import Any, Dict, List, Optional
|
| 35 |
+
|
| 36 |
+
import numpy as np
|
| 37 |
+
import torch
|
| 38 |
+
|
| 39 |
+
from starVLA.model.framework.base_framework import baseframework
|
| 40 |
+
from starVLA.model.framework.share_tools import read_mode_config
|
| 41 |
+
|
| 42 |
+
from deployment.model_server.policy_norm_processor import PolicyNormProcessor
|
| 43 |
+
from deployment.model_server import xtrainer2_command
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class PolicyServerWrapper:
|
| 47 |
+
"""Wraps a `baseframework` for use as a websocket-server policy."""
|
| 48 |
+
|
| 49 |
+
def __init__(
|
| 50 |
+
self,
|
| 51 |
+
ckpt_path: str,
|
| 52 |
+
device: str = "cuda",
|
| 53 |
+
use_bf16: bool = False,
|
| 54 |
+
unnorm_key: Optional[str] = None,
|
| 55 |
+
normalized_action_clip: Optional[float] = None,
|
| 56 |
+
) -> None:
|
| 57 |
+
self._ckpt_path = str(ckpt_path)
|
| 58 |
+
self._normalized_action_clip = normalized_action_clip
|
| 59 |
+
|
| 60 |
+
logging.info("PolicyServerWrapper: loading framework from %s", self._ckpt_path)
|
| 61 |
+
framework = baseframework.from_pretrained(self._ckpt_path)
|
| 62 |
+
if use_bf16:
|
| 63 |
+
framework = framework.to(torch.bfloat16)
|
| 64 |
+
framework = framework.to(device).eval()
|
| 65 |
+
self._framework = framework
|
| 66 |
+
|
| 67 |
+
# Co-located metadata.
|
| 68 |
+
model_cfg, _ = read_mode_config(self._ckpt_path)
|
| 69 |
+
self._model_cfg = model_cfg
|
| 70 |
+
vla_data_cfg = model_cfg.get("datasets", {}).get("vla_data", {})
|
| 71 |
+
include_state = vla_data_cfg.get("include_state", False)
|
| 72 |
+
self._uses_state = include_state not in (False, None, 0, "False", "false", "0")
|
| 73 |
+
|
| 74 |
+
# action_chunk_size = future_action_window_size + 1 (matches old client).
|
| 75 |
+
action_model_cfg = model_cfg["framework"]["action_model"]
|
| 76 |
+
|
| 77 |
+
if "action_horizon" in action_model_cfg:
|
| 78 |
+
self._action_chunk_size = int(action_model_cfg["action_horizon"])
|
| 79 |
+
elif "future_action_window_size" in action_model_cfg:
|
| 80 |
+
self._action_chunk_size = int(action_model_cfg["future_action_window_size"]) + 1
|
| 81 |
+
else:
|
| 82 |
+
raise ValueError(
|
| 83 |
+
f"PolicyServerWrapper: no action_horizon or future_action_window_size found in model config for {self._ckpt_path}"
|
| 84 |
+
)
|
| 85 |
+
# Cache of PolicyNormProcessor instances per unnorm_key.
|
| 86 |
+
# For single-dataset ckpts unnorm_key is auto-selected; for multi-dataset
|
| 87 |
+
# ckpts clients must pass unnorm_key per request.
|
| 88 |
+
self._default_unnorm_key = unnorm_key
|
| 89 |
+
self._norm_processors: Dict[str, PolicyNormProcessor] = {}
|
| 90 |
+
|
| 91 |
+
# Peek at available keys without building a full processor.
|
| 92 |
+
_, _ns = read_mode_config(self._ckpt_path)
|
| 93 |
+
self._available_unnorm_keys: List[str] = list(_ns.keys())
|
| 94 |
+
|
| 95 |
+
# Eagerly build when unambiguous; defer for multi-key / no explicit key.
|
| 96 |
+
if unnorm_key is not None or len(self._available_unnorm_keys) == 1:
|
| 97 |
+
default_proc = self._get_processor(unnorm_key)
|
| 98 |
+
self._default_unnorm_key = default_proc.unnorm_key
|
| 99 |
+
logging.info(
|
| 100 |
+
"PolicyServerWrapper ready: action_chunk_size=%d, default_unnorm_key=%s, "
|
| 101 |
+
"available_unnorm_keys=%s, action_keys=%s, state_keys=%s",
|
| 102 |
+
self._action_chunk_size,
|
| 103 |
+
default_proc.unnorm_key,
|
| 104 |
+
default_proc.available_unnorm_keys,
|
| 105 |
+
default_proc.action_keys,
|
| 106 |
+
default_proc.state_keys,
|
| 107 |
+
)
|
| 108 |
+
else:
|
| 109 |
+
logging.info(
|
| 110 |
+
"PolicyServerWrapper ready (multi-key): action_chunk_size=%d, "
|
| 111 |
+
"available_unnorm_keys=%s — clients must pass unnorm_key per request.",
|
| 112 |
+
self._action_chunk_size,
|
| 113 |
+
self._available_unnorm_keys,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
def _get_processor(self, unnorm_key: Optional[str]) -> PolicyNormProcessor:
|
| 117 |
+
cache_key = unnorm_key if unnorm_key is not None else "__default__"
|
| 118 |
+
if cache_key not in self._norm_processors:
|
| 119 |
+
self._norm_processors[cache_key] = PolicyNormProcessor(
|
| 120 |
+
self._ckpt_path, unnorm_key=unnorm_key
|
| 121 |
+
)
|
| 122 |
+
return self._norm_processors[cache_key]
|
| 123 |
+
|
| 124 |
+
@staticmethod
|
| 125 |
+
def _uses_xtrainer2_layout(proc: PolicyNormProcessor) -> bool:
|
| 126 |
+
"""Whether this embodiment speaks the raw xtrainer2 robot layout."""
|
| 127 |
+
layout = getattr(proc._data_config, "robot_command_layout", None)
|
| 128 |
+
return layout == xtrainer2_command.COMMAND_LAYOUT
|
| 129 |
+
|
| 130 |
+
@property
|
| 131 |
+
def metadata(self) -> Dict[str, Any]:
|
| 132 |
+
"""Model-invariant metadata; sent to client at websocket handshake."""
|
| 133 |
+
base = {
|
| 134 |
+
"env": "starvla_policy_server",
|
| 135 |
+
"ckpt_path": self._ckpt_path,
|
| 136 |
+
"action_chunk_size": self._action_chunk_size,
|
| 137 |
+
"uses_state": self._uses_state,
|
| 138 |
+
"available_unnorm_keys": self._available_unnorm_keys,
|
| 139 |
+
"default_unnorm_key": self._default_unnorm_key,
|
| 140 |
+
"normalized_action_clip": self._normalized_action_clip,
|
| 141 |
+
"action_head_attention_mask": False,
|
| 142 |
+
"inference_rng": "continuous_server_stream",
|
| 143 |
+
# Every client sends images this way; the framework does its own
|
| 144 |
+
# resize, so only the layout and channel order are binding.
|
| 145 |
+
"image_format": "HWC_RGB_uint8",
|
| 146 |
+
"image_key": "image",
|
| 147 |
+
}
|
| 148 |
+
# Enrich with per-embodiment keys when a default processor already exists.
|
| 149 |
+
if self._default_unnorm_key is not None:
|
| 150 |
+
proc = self._get_processor(self._default_unnorm_key)
|
| 151 |
+
base["action_keys"] = proc.action_keys
|
| 152 |
+
base["state_keys"] = proc.state_keys
|
| 153 |
+
base["camera_order"] = list(getattr(proc._data_config, "video_keys", []))
|
| 154 |
+
base["model_action_dim"] = proc.action_dim
|
| 155 |
+
base["model_state_dim"] = proc.state_dim
|
| 156 |
+
base["server_normalizes_state"] = self._server_normalizes_state(proc)
|
| 157 |
+
if base["server_normalizes_state"]:
|
| 158 |
+
base["state_dim"] = proc.raw_state_dim
|
| 159 |
+
if self._uses_xtrainer2_layout(proc):
|
| 160 |
+
base["robot_command_layout"] = xtrainer2_command.COMMAND_LAYOUT
|
| 161 |
+
base["action_dim"] = proc.raw_action_dim
|
| 162 |
+
base["model_quaternion_format"] = xtrainer2_command.MODEL_QUATERNION_FORMAT
|
| 163 |
+
base["robot_quaternion_format"] = xtrainer2_command.SOURCE_QUATERNION_FORMAT
|
| 164 |
+
base["quaternion_starts"] = list(xtrainer2_command.QUATERNION_STARTS)
|
| 165 |
+
action_mode = str(
|
| 166 |
+
getattr(self, "_model_cfg", {})
|
| 167 |
+
.get("datasets", {})
|
| 168 |
+
.get("vla_data", {})
|
| 169 |
+
.get("action_mode", "abs")
|
| 170 |
+
)
|
| 171 |
+
base["action_mode"] = action_mode
|
| 172 |
+
base["server_composes_relative_eef"] = (
|
| 173 |
+
xtrainer2_command.is_relative_action_mode(action_mode)
|
| 174 |
+
)
|
| 175 |
+
return base
|
| 176 |
+
|
| 177 |
+
@staticmethod
|
| 178 |
+
def _server_normalizes_state(proc: PolicyNormProcessor) -> bool:
|
| 179 |
+
"""Whether ``state`` arrives raw and must be normalized server-side."""
|
| 180 |
+
return bool(getattr(proc._data_config, "server_normalizes_state", False))
|
| 181 |
+
|
| 182 |
+
@classmethod
|
| 183 |
+
def _prepare_examples(cls, examples: List[dict], proc: PolicyNormProcessor) -> List[dict]:
|
| 184 |
+
"""Turn raw-robot observations into the tensors the model was trained on.
|
| 185 |
+
|
| 186 |
+
Leaves examples untouched for embodiments that do not opt in, so clients
|
| 187 |
+
that already send normalized state keep working.
|
| 188 |
+
"""
|
| 189 |
+
if not cls._server_normalizes_state(proc):
|
| 190 |
+
return examples
|
| 191 |
+
|
| 192 |
+
to_model_layout = (
|
| 193 |
+
xtrainer2_command.robot_state_to_model
|
| 194 |
+
if cls._uses_xtrainer2_layout(proc)
|
| 195 |
+
else None
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
prepared: List[dict] = []
|
| 199 |
+
for example in examples:
|
| 200 |
+
if "state" not in example or example["state"] is None:
|
| 201 |
+
prepared.append(example)
|
| 202 |
+
continue
|
| 203 |
+
raw_state = np.asarray(example["state"], dtype=np.float32)
|
| 204 |
+
if raw_state.shape[-1] != proc.raw_state_dim:
|
| 205 |
+
raise ValueError(
|
| 206 |
+
f"state must be the robot's raw {proc.raw_state_dim}-D vector "
|
| 207 |
+
f"(state_keys={proc.state_keys}); got shape {raw_state.shape}. "
|
| 208 |
+
"The server applies the training-time normalization itself."
|
| 209 |
+
)
|
| 210 |
+
if to_model_layout is not None:
|
| 211 |
+
raw_state = to_model_layout(raw_state)
|
| 212 |
+
example = dict(example)
|
| 213 |
+
# Keep the training-layout (wxyz) pose so rel-mode actions can be
|
| 214 |
+
# composed back to absolute commands after un-normalization.
|
| 215 |
+
example["_raw_state_wxyz"] = raw_state
|
| 216 |
+
example["state"] = proc.apply_state(raw_state)
|
| 217 |
+
prepared.append(example)
|
| 218 |
+
return prepared
|
| 219 |
+
|
| 220 |
+
def predict_action(
|
| 221 |
+
self,
|
| 222 |
+
examples: List[dict],
|
| 223 |
+
unnorm_key: Optional[str] = None,
|
| 224 |
+
**kwargs,
|
| 225 |
+
) -> Dict[str, np.ndarray]:
|
| 226 |
+
"""Run the framework, then un-normalize via training-time transforms.
|
| 227 |
+
|
| 228 |
+
Args:
|
| 229 |
+
examples: list of dicts (each with ``image`` / ``lang`` / optional
|
| 230 |
+
``state``). For embodiments that declare
|
| 231 |
+
``server_normalizes_state``, ``state`` is the robot's raw vector
|
| 232 |
+
and is normalized here.
|
| 233 |
+
unnorm_key: dataset key for un-normalization stats. ``None`` -->
|
| 234 |
+
use the wrapper's default (auto-picked at startup).
|
| 235 |
+
**kwargs: forwarded to the framework's ``predict_action``
|
| 236 |
+
(``do_sample``, ``use_ddim``, ``num_ddim_steps``, ...).
|
| 237 |
+
|
| 238 |
+
Returns:
|
| 239 |
+
``{"actions": np.ndarray[B, T, D]}`` -- un-normalized.
|
| 240 |
+
"""
|
| 241 |
+
effective_key = unnorm_key if unnorm_key is not None else self._default_unnorm_key
|
| 242 |
+
if effective_key is None:
|
| 243 |
+
if len(self._available_unnorm_keys) == 1:
|
| 244 |
+
effective_key = self._available_unnorm_keys[0]
|
| 245 |
+
else:
|
| 246 |
+
raise ValueError(
|
| 247 |
+
f"predict_action: unnorm_key not specified and no default set. "
|
| 248 |
+
f"Pass one of {self._available_unnorm_keys}."
|
| 249 |
+
)
|
| 250 |
+
proc = self._get_processor(effective_key)
|
| 251 |
+
examples = self._prepare_examples(examples, proc)
|
| 252 |
+
|
| 253 |
+
out = self._framework.predict_action(examples=examples, **kwargs)
|
| 254 |
+
normalized = np.asarray(out["normalized_actions"]) # (B, T, D)
|
| 255 |
+
if self._normalized_action_clip is not None:
|
| 256 |
+
limit = float(self._normalized_action_clip)
|
| 257 |
+
normalized = np.clip(normalized, -limit, limit)
|
| 258 |
+
|
| 259 |
+
unnorm = np.stack(
|
| 260 |
+
[proc.unapply_actions(normalized[b]) for b in range(normalized.shape[0])],
|
| 261 |
+
axis=0,
|
| 262 |
+
)
|
| 263 |
+
if self._uses_xtrainer2_layout(proc):
|
| 264 |
+
if xtrainer2_command.is_relative_action_mode(
|
| 265 |
+
self._model_cfg.get("datasets", {}).get("vla_data", {}).get("action_mode")
|
| 266 |
+
):
|
| 267 |
+
composed = []
|
| 268 |
+
for batch_index in range(unnorm.shape[0]):
|
| 269 |
+
state_wxyz = examples[batch_index].get("_raw_state_wxyz")
|
| 270 |
+
if state_wxyz is None:
|
| 271 |
+
raise ValueError(
|
| 272 |
+
"action_mode=rel requires a raw robot state so relative "
|
| 273 |
+
"EE commands can be composed back to absolute poses"
|
| 274 |
+
)
|
| 275 |
+
composed.append(
|
| 276 |
+
xtrainer2_command.compose_relative_eef_wxyz(unnorm[batch_index], state_wxyz)
|
| 277 |
+
)
|
| 278 |
+
unnorm = np.stack(composed, axis=0)
|
| 279 |
+
unnorm = xtrainer2_command.model_actions_to_robot(unnorm)
|
| 280 |
+
return {"actions": unnorm}
|
real_robot/source/deployment/model_server/server_policy.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License, Version 1.0 (the "License");
|
| 3 |
+
|
| 4 |
+
import logging
|
| 5 |
+
import socket
|
| 6 |
+
import argparse
|
| 7 |
+
from deployment.model_server.tools.websocket_policy_server import WebsocketPolicyServer
|
| 8 |
+
from deployment.model_server.policy_wrapper import PolicyServerWrapper
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main(args) -> None:
|
| 13 |
+
# Example usage:
|
| 14 |
+
# policy = YourPolicyClass() # Replace with your actual policy class
|
| 15 |
+
# server = WebsocketPolicyServer(policy, host="localhost", port=10091)
|
| 16 |
+
# server.serve_forever()
|
| 17 |
+
|
| 18 |
+
policy = PolicyServerWrapper(
|
| 19 |
+
ckpt_path=args.ckpt_path,
|
| 20 |
+
device="cuda",
|
| 21 |
+
use_bf16=args.use_bf16,
|
| 22 |
+
unnorm_key=args.unnorm_key,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
hostname = socket.gethostname()
|
| 26 |
+
local_ip = socket.gethostbyname(hostname)
|
| 27 |
+
logging.info("Creating server (host: %s, ip: %s)", hostname, local_ip)
|
| 28 |
+
# The handshake metadata is the request contract; log it so a client
|
| 29 |
+
# mismatch (camera order, state width, quaternion order) is visible here.
|
| 30 |
+
for key, value in sorted(policy.metadata.items()):
|
| 31 |
+
logging.info(" metadata %-24s %s", key, value)
|
| 32 |
+
|
| 33 |
+
# start websocket server
|
| 34 |
+
server = WebsocketPolicyServer(
|
| 35 |
+
policy=policy,
|
| 36 |
+
host="0.0.0.0",
|
| 37 |
+
port=args.port,
|
| 38 |
+
idle_timeout=args.idle_timeout,
|
| 39 |
+
metadata={**policy.metadata, "policy_seed": args.seed},
|
| 40 |
+
)
|
| 41 |
+
logging.info("server running ...")
|
| 42 |
+
server.serve_forever()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def build_argparser():
|
| 46 |
+
parser = argparse.ArgumentParser()
|
| 47 |
+
parser.add_argument("--ckpt_path", type=str, default="Qwen/Qwen2.5-VL-3B-Instruct")
|
| 48 |
+
parser.add_argument("--port", type=int, default=10093)
|
| 49 |
+
parser.add_argument("--use_bf16", action="store_true")
|
| 50 |
+
parser.add_argument(
|
| 51 |
+
"--unnorm_key",
|
| 52 |
+
type=str,
|
| 53 |
+
default=None,
|
| 54 |
+
help="dataset_statistics.json key for un-normalization; required only for multi-dataset checkpoints",
|
| 55 |
+
)
|
| 56 |
+
parser.add_argument("--seed", type=int, default=0)
|
| 57 |
+
parser.add_argument("--idle_timeout" , type=int, default=1800, help="Idle timeout in seconds, -1 means never close")
|
| 58 |
+
return parser
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def start_debugpy_once():
|
| 62 |
+
"""start debugpy once"""
|
| 63 |
+
import debugpy
|
| 64 |
+
if getattr(start_debugpy_once, "_started", False):
|
| 65 |
+
return
|
| 66 |
+
debugpy.listen(("0.0.0.0", 10095))
|
| 67 |
+
print("Waiting for VSCode attach on 0.0.0.0:10095 ...")
|
| 68 |
+
debugpy.wait_for_client()
|
| 69 |
+
start_debugpy_once._started = True
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
logging.basicConfig(level=logging.INFO, force=True)
|
| 74 |
+
parser = build_argparser()
|
| 75 |
+
args = parser.parse_args()
|
| 76 |
+
if os.getenv("DEBUG", "").strip().lower() in {"1", "true", "yes"}:
|
| 77 |
+
print("DEBUGPY is enabled")
|
| 78 |
+
start_debugpy_once()
|
| 79 |
+
main(args)
|
real_robot/source/deployment/model_server/tools/__init__.py
ADDED
|
File without changes
|
real_robot/source/deployment/model_server/tools/image_tools.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from PIL import Image
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def convert_to_uint8(img: np.ndarray) -> np.ndarray:
|
| 6 |
+
"""Converts an image to uint8 if it is a float image.
|
| 7 |
+
|
| 8 |
+
This is important for reducing the size of the image when sending it over the network.
|
| 9 |
+
"""
|
| 10 |
+
if np.issubdtype(img.dtype, np.floating):
|
| 11 |
+
img = (255 * img).astype(np.uint8)
|
| 12 |
+
return img
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def resize_with_pad(images: np.ndarray, height: int, width: int, method=Image.BILINEAR) -> np.ndarray:
|
| 16 |
+
"""Replicates tf.image.resize_with_pad for multiple images using PIL. Resizes a batch of images to a target height.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
images: A batch of images in [..., height, width, channel] format.
|
| 20 |
+
height: The target height of the image.
|
| 21 |
+
width: The target width of the image.
|
| 22 |
+
method: The interpolation method to use. Default is bilinear.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
The resized images in [..., height, width, channel].
|
| 26 |
+
"""
|
| 27 |
+
# If the images are already the correct size, return them as is.
|
| 28 |
+
if images.shape[-3:-1] == (height, width):
|
| 29 |
+
return images
|
| 30 |
+
|
| 31 |
+
original_shape = images.shape
|
| 32 |
+
|
| 33 |
+
images = images.reshape(-1, *original_shape[-3:])
|
| 34 |
+
resized = np.stack([_resize_with_pad_pil(Image.fromarray(im), height, width, method=method) for im in images])
|
| 35 |
+
return resized.reshape(*original_shape[:-3], *resized.shape[-3:])
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _resize_with_pad_pil(image: Image.Image, height: int, width: int, method: int) -> Image.Image:
|
| 39 |
+
"""Replicates tf.image.resize_with_pad for one image using PIL. Resizes an image to a target height and
|
| 40 |
+
width without distortion by padding with zeros.
|
| 41 |
+
|
| 42 |
+
Unlike the jax version, note that PIL uses [width, height, channel] ordering instead of [batch, h, w, c].
|
| 43 |
+
"""
|
| 44 |
+
cur_width, cur_height = image.size
|
| 45 |
+
if cur_width == width and cur_height == height:
|
| 46 |
+
return image # No need to resize if the image is already the correct size.
|
| 47 |
+
|
| 48 |
+
ratio = max(cur_width / width, cur_height / height)
|
| 49 |
+
resized_height = int(cur_height / ratio)
|
| 50 |
+
resized_width = int(cur_width / ratio)
|
| 51 |
+
resized_image = image.resize((resized_width, resized_height), resample=method)
|
| 52 |
+
|
| 53 |
+
zero_image = Image.new(resized_image.mode, (width, height), 0)
|
| 54 |
+
pad_height = max(0, int((height - resized_height) / 2))
|
| 55 |
+
pad_width = max(0, int((width - resized_width) / 2))
|
| 56 |
+
zero_image.paste(resized_image, (pad_width, pad_height))
|
| 57 |
+
assert zero_image.size == (width, height)
|
| 58 |
+
return zero_image
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
from typing import Any
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def to_pil_preserve(images: Any, scale_float: bool = True):
|
| 65 |
+
"""
|
| 66 |
+
Convert (possibly nested) numpy image arrays back to PIL.Image WITHOUT changing spatial shape
|
| 67 |
+
or nesting structure.
|
| 68 |
+
|
| 69 |
+
Accepts:
|
| 70 |
+
- np.ndarray with shape (H, W, C), C in {1,3,4}, dtype uint8 or float
|
| 71 |
+
- PIL.Image.Image (returned as-is)
|
| 72 |
+
- Nested list / tuple structures containing the above
|
| 73 |
+
|
| 74 |
+
Guarantees:
|
| 75 |
+
- No resize / pad / crop performed
|
| 76 |
+
- Returns an object with the SAME nesting layout (list -> list, tuple -> tuple)
|
| 77 |
+
- Only dtype (float -> uint8) and channel-mode adaptation may happen
|
| 78 |
+
* float arrays assumed in [0,1] if scale_float=True (scaled *255 + clip)
|
| 79 |
+
Args:
|
| 80 |
+
images: input object / sequence
|
| 81 |
+
scale_float: whether to scale float images in [0,1] to uint8
|
| 82 |
+
Returns:
|
| 83 |
+
Mirrored structure with all leaf nodes as PIL.Image.Image
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def _convert(obj):
|
| 87 |
+
# Nested containers
|
| 88 |
+
if isinstance(obj, list):
|
| 89 |
+
return [_convert(x) for x in obj]
|
| 90 |
+
if isinstance(obj, tuple):
|
| 91 |
+
return tuple(_convert(x) for x in obj)
|
| 92 |
+
|
| 93 |
+
# PIL stays
|
| 94 |
+
if isinstance(obj, Image.Image):
|
| 95 |
+
return obj
|
| 96 |
+
|
| 97 |
+
# numpy -> PIL
|
| 98 |
+
if isinstance(obj, np.ndarray):
|
| 99 |
+
arr = obj
|
| 100 |
+
if arr.ndim != 3:
|
| 101 |
+
raise ValueError(f"Expected 3D array (H,W,C), got shape={arr.shape}")
|
| 102 |
+
if arr.shape[2] not in (1, 3, 4):
|
| 103 |
+
raise ValueError(f"Channel count must be 1/3/4, got {arr.shape[2]}")
|
| 104 |
+
if np.issubdtype(arr.dtype, np.floating):
|
| 105 |
+
if scale_float:
|
| 106 |
+
arr = np.clip(arr, 0.0, 1.0)
|
| 107 |
+
arr = (arr * 255.0 + 0.5).astype(np.uint8)
|
| 108 |
+
else:
|
| 109 |
+
raise TypeError("Float array provided but scale_float=False")
|
| 110 |
+
elif arr.dtype != np.uint8:
|
| 111 |
+
arr = arr.astype(np.uint8)
|
| 112 |
+
|
| 113 |
+
# Single channel -> 'L'
|
| 114 |
+
if arr.shape[2] == 1:
|
| 115 |
+
arr = arr[:, :, 0]
|
| 116 |
+
return Image.fromarray(arr, mode="L")
|
| 117 |
+
# 3 channels -> RGB, 4 -> RGBA
|
| 118 |
+
mode = "RGB" if arr.shape[2] == 3 else "RGBA"
|
| 119 |
+
return Image.fromarray(arr, mode=mode)
|
| 120 |
+
|
| 121 |
+
raise TypeError(f"Unsupported element type: {type(obj)}")
|
| 122 |
+
|
| 123 |
+
return _convert(images)
|
real_robot/source/deployment/model_server/tools/msgpack_numpy.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Adds NumPy array support to msgpack.
|
| 2 |
+
|
| 3 |
+
msgpack is good for (de)serializing data over a network for multiple reasons:
|
| 4 |
+
- msgpack is secure (as opposed to pickle/dill/etc which allow for arbitrary code execution)
|
| 5 |
+
- msgpack is widely used and has good cross-language support
|
| 6 |
+
- msgpack does not require a schema (as opposed to protobuf/flatbuffers/etc) which is convenient in dynamically typed
|
| 7 |
+
languages like Python and JavaScript
|
| 8 |
+
- msgpack is fast and efficient (as opposed to readable formats like JSON/YAML/etc); I found that msgpack was ~4x faster
|
| 9 |
+
than pickle for serializing large arrays using the below strategy
|
| 10 |
+
|
| 11 |
+
The code below is adapted from https://github.com/lebedov/msgpack-numpy.
|
| 12 |
+
Its original BSD license is retained in licenses/msgpack-numpy-LICENSE.
|
| 13 |
+
The reason not to use that library directly is
|
| 14 |
+
that it falls back to pickle for object arrays.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import functools
|
| 18 |
+
|
| 19 |
+
import msgpack
|
| 20 |
+
import numpy as np
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def pack_array(obj):
|
| 24 |
+
if (isinstance(obj, (np.ndarray, np.generic))) and obj.dtype.kind in ("V", "O", "c"):
|
| 25 |
+
raise ValueError(f"Unsupported dtype: {obj.dtype}")
|
| 26 |
+
|
| 27 |
+
if isinstance(obj, np.ndarray):
|
| 28 |
+
return {
|
| 29 |
+
b"__ndarray__": True,
|
| 30 |
+
b"data": obj.tobytes(),
|
| 31 |
+
b"dtype": obj.dtype.str,
|
| 32 |
+
b"shape": obj.shape,
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
if isinstance(obj, np.generic):
|
| 36 |
+
return {
|
| 37 |
+
b"__npgeneric__": True,
|
| 38 |
+
b"data": obj.item(),
|
| 39 |
+
b"dtype": obj.dtype.str,
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
return obj
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def unpack_array(obj):
|
| 46 |
+
if b"__ndarray__" in obj:
|
| 47 |
+
return np.ndarray(buffer=obj[b"data"], dtype=np.dtype(obj[b"dtype"]), shape=obj[b"shape"])
|
| 48 |
+
|
| 49 |
+
if b"__npgeneric__" in obj:
|
| 50 |
+
return np.dtype(obj[b"dtype"]).type(obj[b"data"])
|
| 51 |
+
|
| 52 |
+
return obj
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
Packer = functools.partial(msgpack.Packer, default=pack_array)
|
| 56 |
+
packb = functools.partial(msgpack.packb, default=pack_array)
|
| 57 |
+
|
| 58 |
+
Unpacker = functools.partial(msgpack.Unpacker, object_hook=unpack_array)
|
| 59 |
+
unpackb = functools.partial(msgpack.unpackb, object_hook=unpack_array)
|
real_robot/source/deployment/model_server/tools/websocket_policy_client.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License, Version 1.0 (the "License");
|
| 3 |
+
|
| 4 |
+
import logging, argparse
|
| 5 |
+
import time, os
|
| 6 |
+
from typing import Dict, Optional, Tuple
|
| 7 |
+
|
| 8 |
+
from typing_extensions import override
|
| 9 |
+
import websockets.sync.client
|
| 10 |
+
|
| 11 |
+
from . import msgpack_numpy
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class WebsocketClientPolicy:
|
| 15 |
+
"""Implements the Policy interface by communicating with a server over websocket.
|
| 16 |
+
|
| 17 |
+
See WebsocketPolicyServer for a corresponding server implementation.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, host: str = "127.0.0.1", port: Optional[int] = 10093, api_key: Optional[str] = None) -> None:
|
| 21 |
+
# 0.0.0.0 cannot be used as a connection target, here default 127.0.0.1
|
| 22 |
+
self._uri = f"ws://{host}"
|
| 23 |
+
if port is not None:
|
| 24 |
+
self._uri += f":{port}"
|
| 25 |
+
self._packer = msgpack_numpy.Packer()
|
| 26 |
+
self._api_key = api_key
|
| 27 |
+
self._ws, self._server_metadata = self._wait_for_server()
|
| 28 |
+
|
| 29 |
+
def get_server_metadata(self) -> Dict:
|
| 30 |
+
return self._server_metadata
|
| 31 |
+
|
| 32 |
+
def _wait_for_server(self, timeout: float = 300) -> Tuple[websockets.sync.client.ClientConnection, Dict]:
|
| 33 |
+
logging.info(f"Waiting for server at {self._uri}...")
|
| 34 |
+
start_time = time.time()
|
| 35 |
+
|
| 36 |
+
for k in ("HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy"):
|
| 37 |
+
os.environ.pop(k, None)
|
| 38 |
+
|
| 39 |
+
while True:
|
| 40 |
+
if time.time() - start_time > timeout:
|
| 41 |
+
raise TimeoutError(f"Failed to connect to server within {timeout} seconds")
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
headers = {"Authorization": f"Api-Key {self._api_key}"} if self._api_key else None
|
| 45 |
+
conn = websockets.sync.client.connect(
|
| 46 |
+
self._uri,
|
| 47 |
+
compression=None,
|
| 48 |
+
max_size=None,
|
| 49 |
+
additional_headers=headers,
|
| 50 |
+
open_timeout=150,
|
| 51 |
+
ping_interval=None,
|
| 52 |
+
ping_timeout=None,
|
| 53 |
+
)
|
| 54 |
+
metadata = msgpack_numpy.unpackb(conn.recv())
|
| 55 |
+
return conn, metadata
|
| 56 |
+
except ConnectionRefusedError:
|
| 57 |
+
logging.info(f"Still waiting for server {self._uri} ...")
|
| 58 |
+
time.sleep(2)
|
| 59 |
+
|
| 60 |
+
def close(self) -> None:
|
| 61 |
+
try:
|
| 62 |
+
self._ws.close()
|
| 63 |
+
except Exception:
|
| 64 |
+
pass
|
| 65 |
+
|
| 66 |
+
@override
|
| 67 |
+
def predict_action(self, query_info: Dict) -> Dict:
|
| 68 |
+
data = self._packer.pack(query_info)
|
| 69 |
+
self._ws.send(data)
|
| 70 |
+
response = self._ws.recv()
|
| 71 |
+
if isinstance(response, str):
|
| 72 |
+
raise RuntimeError(f"Error in inference server:\n{response}")
|
| 73 |
+
return msgpack_numpy.unpackb(response)
|
| 74 |
+
|
| 75 |
+
|
real_robot/source/deployment/model_server/tools/websocket_policy_server.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2025 starVLA community. All rights reserved.
|
| 2 |
+
# Licensed under the MIT License, Version 1.0 (the "License");
|
| 3 |
+
|
| 4 |
+
import asyncio
|
| 5 |
+
import logging
|
| 6 |
+
import traceback
|
| 7 |
+
import time
|
| 8 |
+
|
| 9 |
+
import websockets.asyncio.server
|
| 10 |
+
import websockets.frames
|
| 11 |
+
|
| 12 |
+
# from openpi_client import base_policy as _base_policy
|
| 13 |
+
from . import msgpack_numpy
|
| 14 |
+
|
| 15 |
+
class WebsocketPolicyServer:
|
| 16 |
+
"""Serves a policy using the websocket protocol. See websocket_client_policy.py for a client implementation.
|
| 17 |
+
|
| 18 |
+
Currently only implements the `load` and `infer` methods.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(
|
| 22 |
+
self,
|
| 23 |
+
policy,
|
| 24 |
+
host: str = "0.0.0.0",
|
| 25 |
+
port: int = 10093,
|
| 26 |
+
idle_timeout: int = -1, # Negative values disable the idle timeout.
|
| 27 |
+
metadata: dict | None = None,
|
| 28 |
+
|
| 29 |
+
) -> None:
|
| 30 |
+
self._policy = policy
|
| 31 |
+
self._host = host
|
| 32 |
+
self._port = port
|
| 33 |
+
self._metadata = metadata or {}
|
| 34 |
+
self._idle_timeout = idle_timeout
|
| 35 |
+
self._last_active = time.time()
|
| 36 |
+
logging.getLogger("websockets.server").setLevel(logging.INFO)
|
| 37 |
+
|
| 38 |
+
def serve_forever(self) -> None:
|
| 39 |
+
asyncio.run(self.run())
|
| 40 |
+
|
| 41 |
+
async def run(self):
|
| 42 |
+
async with websockets.asyncio.server.serve(
|
| 43 |
+
self._handler,
|
| 44 |
+
self._host,
|
| 45 |
+
self._port,
|
| 46 |
+
compression=None,
|
| 47 |
+
max_size=None,
|
| 48 |
+
) as server:
|
| 49 |
+
if self._idle_timeout > 0:
|
| 50 |
+
await self._idle_watchdog(server)
|
| 51 |
+
else:
|
| 52 |
+
await server.serve_forever()
|
| 53 |
+
|
| 54 |
+
async def _idle_watchdog(self, server):
|
| 55 |
+
"""Close the server after the configured idle period."""
|
| 56 |
+
while True:
|
| 57 |
+
await asyncio.sleep(5)
|
| 58 |
+
if time.time() - self._last_active > self._idle_timeout:
|
| 59 |
+
logging.info(f"Idle timeout ({self._idle_timeout}s) reached, shutting down server.")
|
| 60 |
+
server.close()
|
| 61 |
+
await server.wait_closed()
|
| 62 |
+
break
|
| 63 |
+
|
| 64 |
+
async def _handler(self, websocket: websockets.asyncio.server.ServerConnection):
|
| 65 |
+
logging.info(f"Connection from {websocket.remote_address} opened")
|
| 66 |
+
packer = msgpack_numpy.Packer()
|
| 67 |
+
|
| 68 |
+
await websocket.send(packer.pack(self._metadata))
|
| 69 |
+
|
| 70 |
+
while True:
|
| 71 |
+
try:
|
| 72 |
+
msg = msgpack_numpy.unpackb(await websocket.recv())
|
| 73 |
+
self._last_active = time.time()
|
| 74 |
+
ret = self._route_message(msg)
|
| 75 |
+
await websocket.send(packer.pack(ret))
|
| 76 |
+
except websockets.ConnectionClosed:
|
| 77 |
+
logging.info(f"Connection from {websocket.remote_address} closed")
|
| 78 |
+
break
|
| 79 |
+
except Exception:
|
| 80 |
+
await websocket.send(traceback.format_exc())
|
| 81 |
+
await websocket.close(
|
| 82 |
+
code=websockets.frames.CloseCode.INTERNAL_ERROR,
|
| 83 |
+
reason="Internal server error. Traceback included in previous frame.",
|
| 84 |
+
)
|
| 85 |
+
raise
|
| 86 |
+
|
| 87 |
+
# route logic: recognize request from client
|
| 88 |
+
def _route_message(self, msg: dict) -> dict:
|
| 89 |
+
"""
|
| 90 |
+
Route rules (fault-tolerant):
|
| 91 |
+
- Supports messages of form:
|
| 92 |
+
{"type": "ping|init|infer|reset", "request_id": "...", "payload": {...}}
|
| 93 |
+
or a flat dict (will be treated as payload).
|
| 94 |
+
- Does NOT raise inside this function: all exceptions are caught and encoded in response.
|
| 95 |
+
"""
|
| 96 |
+
if not isinstance(msg, dict):
|
| 97 |
+
return {
|
| 98 |
+
"status": "error",
|
| 99 |
+
"ok": False,
|
| 100 |
+
"type": "inference_result",
|
| 101 |
+
"request_id": "default",
|
| 102 |
+
"error": {"message": "Payload must be a dict", "payload_type": str(type(msg))},
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
req_id = msg.get("request_id", "default")
|
| 106 |
+
mtype = msg.get("type", "infer") # default = infer
|
| 107 |
+
# The envelope fields are addressed to this router, not to the policy.
|
| 108 |
+
payload = {key: value for key, value in msg.items() if key not in ("type", "request_id")}
|
| 109 |
+
|
| 110 |
+
# ping
|
| 111 |
+
if mtype == "ping":
|
| 112 |
+
return {"status": "ok", "ok": True, "type": "ping", "request_id": req_id}
|
| 113 |
+
|
| 114 |
+
# infer --> framework.predict_action
|
| 115 |
+
elif mtype == "infer" or mtype == "predict_action":
|
| 116 |
+
try:
|
| 117 |
+
ouput_dict = self._policy.predict_action(**payload)
|
| 118 |
+
except Exception as e:
|
| 119 |
+
logging.exception("Policy inference error (request_id=%s)", req_id)
|
| 120 |
+
logging.exception(e)
|
| 121 |
+
|
| 122 |
+
return {
|
| 123 |
+
"status": "error",
|
| 124 |
+
"ok": False,
|
| 125 |
+
"type": "inference_result",
|
| 126 |
+
"request_id": req_id,
|
| 127 |
+
"error": {
|
| 128 |
+
"message": str(e),
|
| 129 |
+
# "traceback": traceback.format_exc(),
|
| 130 |
+
},
|
| 131 |
+
}
|
| 132 |
+
data = ouput_dict
|
| 133 |
+
return {
|
| 134 |
+
"status": "ok",
|
| 135 |
+
"ok": True,
|
| 136 |
+
"type": "inference_result",
|
| 137 |
+
"request_id": req_id,
|
| 138 |
+
"data": data,
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
# unknow request type
|
| 142 |
+
else:
|
| 143 |
+
return {
|
| 144 |
+
"status": "error",
|
| 145 |
+
"ok": False,
|
| 146 |
+
"type": "unknown",
|
| 147 |
+
"request_id": req_id,
|
| 148 |
+
"error": {"message": f"Unsupported message type '{mtype}'"},
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
if __name__ == "__main__":
|
| 153 |
+
logging.basicConfig(level=logging.INFO, force=True)
|
| 154 |
+
# Example usage:
|
| 155 |
+
# policy = YourPolicyClass() # Replace with your actual policy class
|
| 156 |
+
# server = WebsocketPolicyServer(policy, host="localhost", port=10091)
|
| 157 |
+
# server.serve_forever()
|
| 158 |
+
raise NotImplementedError("This module is not intended to be run directly.")
|
| 159 |
+
#
|
| 160 |
+
# Instead, it should be imported and used in a server context.
|
real_robot/source/deployment/model_server/xtrainer2_command.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""xtrainer2 command layout used between StarVLA and the real robot.
|
| 2 |
+
|
| 3 |
+
The converted LeRobot dataset stores each arm as ``xyz + quaternion + gripper``.
|
| 4 |
+
StarVLA / PyTorch3D read that quaternion as ``w, x, y, z``. The robot HDF5
|
| 5 |
+
streams and the robot controller use ``x, y, z, w``.
|
| 6 |
+
|
| 7 |
+
The training transform already maps quaternion <-> rotation-6D. After
|
| 8 |
+
un-normalization the server therefore has 16-D **wxyz** commands. Sending
|
| 9 |
+
those 16 numbers to the robot without this reorder will execute the wrong
|
| 10 |
+
orientation.
|
| 11 |
+
|
| 12 |
+
Do not confuse these widths:
|
| 13 |
+
|
| 14 |
+
* 14 – joint angles in the source HDF5 (not used for this VLA)
|
| 15 |
+
* 16 – stored / robot EEF command (after this conversion, xyzw)
|
| 16 |
+
* 20 – model tensor after rotation-6D (horizon is a separate axis)
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import numpy as np
|
| 22 |
+
|
| 23 |
+
from starVLA.dataloader.relative_pose import compose_quaternion
|
| 24 |
+
|
| 25 |
+
QUATERNION_STARTS = (3, 11)
|
| 26 |
+
POSITION_SLICES = ((0, 3), (8, 11))
|
| 27 |
+
ACTION_DIM = 16
|
| 28 |
+
ROBOT_TYPE = "xtrainer2_bimanual_eef"
|
| 29 |
+
SOURCE_QUATERNION_FORMAT = "xyzw"
|
| 30 |
+
MODEL_QUATERNION_FORMAT = "wxyz"
|
| 31 |
+
|
| 32 |
+
#: Value of ``DataConfig.robot_command_layout`` that selects the conversions in
|
| 33 |
+
#: this module. The policy server reads that attribute instead of matching on a
|
| 34 |
+
#: class name.
|
| 35 |
+
COMMAND_LAYOUT = "xtrainer2_bimanual_eef_xyzw"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _as_float_array(values: np.ndarray) -> np.ndarray:
|
| 39 |
+
array = np.asarray(values, dtype=np.float32)
|
| 40 |
+
if array.shape[-1] != ACTION_DIM:
|
| 41 |
+
raise ValueError(f"expected last dimension {ACTION_DIM}, got {array.shape}")
|
| 42 |
+
return array
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def wxyz_to_xyzw_block(block: np.ndarray) -> np.ndarray:
|
| 46 |
+
"""``w, x, y, z`` -> ``x, y, z, w``."""
|
| 47 |
+
return np.concatenate([block[..., 1:4], block[..., :1]], axis=-1)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def xyzw_to_wxyz_block(block: np.ndarray) -> np.ndarray:
|
| 51 |
+
"""``x, y, z, w`` -> ``w, x, y, z``."""
|
| 52 |
+
return np.concatenate([block[..., 3:4], block[..., :3]], axis=-1)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def model_actions_to_robot(values: np.ndarray) -> np.ndarray:
|
| 56 |
+
"""Convert un-normalized StarVLA actions (wxyz) to robot commands (xyzw)."""
|
| 57 |
+
converted = np.array(_as_float_array(values), copy=True)
|
| 58 |
+
for start in QUATERNION_STARTS:
|
| 59 |
+
converted[..., start : start + 4] = wxyz_to_xyzw_block(converted[..., start : start + 4])
|
| 60 |
+
return converted
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def robot_state_to_model(values: np.ndarray) -> np.ndarray:
|
| 64 |
+
"""Convert robot observations (xyzw) to the layout StarVLA was trained on."""
|
| 65 |
+
converted = np.array(_as_float_array(values), copy=True)
|
| 66 |
+
for start in QUATERNION_STARTS:
|
| 67 |
+
converted[..., start : start + 4] = xyzw_to_wxyz_block(converted[..., start : start + 4])
|
| 68 |
+
return converted
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def compose_relative_eef_wxyz(relative_actions: np.ndarray, state_wxyz: np.ndarray) -> np.ndarray:
|
| 72 |
+
"""Turn rel-mode 16-D wxyz actions into absolute 16-D wxyz poses.
|
| 73 |
+
|
| 74 |
+
Positions add the current EE; rotations left-multiply the current
|
| 75 |
+
quaternion; grippers are already absolute and are left unchanged.
|
| 76 |
+
"""
|
| 77 |
+
relative = np.array(_as_float_array(relative_actions), copy=True)
|
| 78 |
+
state = np.asarray(state_wxyz, dtype=np.float32).reshape(-1, ACTION_DIM)[0]
|
| 79 |
+
absolute = relative
|
| 80 |
+
for start, end in POSITION_SLICES:
|
| 81 |
+
absolute[..., start:end] = relative[..., start:end] + state[start:end]
|
| 82 |
+
for start in QUATERNION_STARTS:
|
| 83 |
+
absolute[..., start : start + 4] = compose_quaternion(
|
| 84 |
+
state[start : start + 4],
|
| 85 |
+
relative[..., start : start + 4],
|
| 86 |
+
)
|
| 87 |
+
return absolute
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def is_relative_action_mode(action_mode: object | None) -> bool:
|
| 91 |
+
if action_mode is None:
|
| 92 |
+
return False
|
| 93 |
+
return str(action_mode).lower() in {"rel", "relative"}
|
real_robot/source/examples/Xtrainer2/INFERENCE.md
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Xtrainer2 policy inference
|
| 2 |
+
|
| 3 |
+
The server and client interface is described in [ROBOT.md](ROBOT.md).
|
| 4 |
+
Data layout and training configuration are described in [README.md](README.md).
|
real_robot/source/examples/Xtrainer2/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Xtrainer2 data adapter
|
| 2 |
+
|
| 3 |
+
This directory contains optional StarVLA configurations and conversion helpers
|
| 4 |
+
for Xtrainer2 LeRobot data. It does not distribute robot data, checkpoints, or
|
| 5 |
+
on-robot control software. The ReTime command in the repository root is the
|
| 6 |
+
recommended entrypoint for proposal generation and training.
|
| 7 |
+
|
| 8 |
+
The supplied configurations cover pack-phone with absolute end-effector actions
|
| 9 |
+
and pick-bottle with relative end-effector actions. For bimanual data, the source
|
| 10 |
+
action contains 16 values: position, quaternion, and gripper for each arm.
|
| 11 |
+
Rotation conversion expands the model action to 20 values. `pytorch3d` is
|
| 12 |
+
required for this conversion; install a build matching your Python, PyTorch,
|
| 13 |
+
and CUDA environment. The additional Python dependencies are listed in
|
| 14 |
+
`requirements-xtrainer2.txt`.
|
| 15 |
+
|
| 16 |
+
Set dataset, model, statistics, training budget, and process count for your own
|
| 17 |
+
environment. Keep the trainer gradient accumulation equal to the Accelerate
|
| 18 |
+
configuration. Use a full checkpoint directory with `--start resume` when
|
| 19 |
+
optimizer and scheduler continuation is required. A model file with the default
|
| 20 |
+
`--start weights` initializes a new optimizer and scheduler.
|
| 21 |
+
|
| 22 |
+
Policy serving uses the included `deployment/model_server` implementation. A
|
| 23 |
+
separate client must supply observations and consume actions using the same
|
| 24 |
+
schema as the selected data configuration. Client source and deployment
|
| 25 |
+
instructions are outside this artifact. See [ROBOT.md](ROBOT.md) for the data
|
| 26 |
+
and interface contract.
|
real_robot/source/examples/Xtrainer2/ROBOT.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Xtrainer2 policy interface
|
| 2 |
+
|
| 3 |
+
The optional policy server is in `deployment/model_server`. Configure it with a
|
| 4 |
+
model checkpoint and the matching training configuration and dataset statistics.
|
| 5 |
+
The client is a separate component and is not distributed in this artifact.
|
| 6 |
+
|
| 7 |
+
The client sends camera observations, proprioception, and a task instruction.
|
| 8 |
+
For the supplied bimanual configurations, its proprioception and actions use
|
| 9 |
+
16 absolute end-effector values: position, quaternion, and gripper for each arm.
|
| 10 |
+
The model internally uses 20 values after quaternion to rotation-6D conversion.
|
| 11 |
+
The client quaternion format is `xyzw`; dataset conversion and normalization
|
| 12 |
+
must follow the selected configuration. The pick-bottle configuration composes
|
| 13 |
+
relative actions on the server before returning absolute client actions.
|
| 14 |
+
|
| 15 |
+
Check the model architecture, action dimension, normalization statistics,
|
| 16 |
+
camera names, and action chunk size against the checkpoint before deployment.
|
| 17 |
+
Only use a client whose message schema matches the server's documented policy
|
| 18 |
+
interface. Robot hardware and control software are outside this review package.
|
real_robot/source/examples/Xtrainer2/train_files/build_pack_phone_support.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Write frozen fingerprint and provenance for xtrainer2_pack_phone."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _identity_files(dataset_root: Path) -> list[Path]:
|
| 13 |
+
files = list((dataset_root / "data").rglob("*.parquet"))
|
| 14 |
+
files.extend((dataset_root / "videos").rglob("*.mp4"))
|
| 15 |
+
meta_root = dataset_root / "meta"
|
| 16 |
+
if meta_root.exists():
|
| 17 |
+
for pattern in ("*.json", "*.jsonl", "*.parquet"):
|
| 18 |
+
files.extend(meta_root.rglob(pattern))
|
| 19 |
+
return sorted(set(files), key=lambda path: path.relative_to(dataset_root).as_posix())
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def compute_dataset_content_fingerprint(dataset_root: str | Path) -> dict[str, Any]:
|
| 23 |
+
root = Path(dataset_root)
|
| 24 |
+
entries = []
|
| 25 |
+
total_bytes = 0
|
| 26 |
+
for file_path in _identity_files(root):
|
| 27 |
+
relative_path = file_path.relative_to(root).as_posix()
|
| 28 |
+
file_size = file_path.stat().st_size
|
| 29 |
+
entries.append({"path": relative_path, "bytes": file_size})
|
| 30 |
+
total_bytes += file_size
|
| 31 |
+
return {
|
| 32 |
+
"file_count": len(entries),
|
| 33 |
+
"total_bytes": total_bytes,
|
| 34 |
+
"files": entries,
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def main() -> None:
|
| 39 |
+
parser = argparse.ArgumentParser()
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--data-root",
|
| 42 |
+
type=Path,
|
| 43 |
+
default=Path("/path/to/data/lerobot"),
|
| 44 |
+
)
|
| 45 |
+
parser.add_argument(
|
| 46 |
+
"--output-dir",
|
| 47 |
+
type=Path,
|
| 48 |
+
default=None,
|
| 49 |
+
)
|
| 50 |
+
args = parser.parse_args()
|
| 51 |
+
dataset_root = args.data_root / "xtrainer2_pack_phone"
|
| 52 |
+
output_dir = args.output_dir or (args.data_root / "xtrainer2_optr_support" / "pack_phone")
|
| 53 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 54 |
+
|
| 55 |
+
info = json.loads((dataset_root / "meta" / "info.json").read_text(encoding="utf-8"))
|
| 56 |
+
report_path = dataset_root / "conversion_report.json"
|
| 57 |
+
report = json.loads(report_path.read_text(encoding="utf-8")) if report_path.is_file() else {}
|
| 58 |
+
|
| 59 |
+
provenance = {
|
| 60 |
+
"dataset_name": "xtrainer2_pack_phone",
|
| 61 |
+
"codebase_version": info.get("codebase_version"),
|
| 62 |
+
"robot_type": info.get("robot_type"),
|
| 63 |
+
"fps": info.get("fps"),
|
| 64 |
+
"total_episodes": info.get("total_episodes"),
|
| 65 |
+
"total_frames": info.get("total_frames"),
|
| 66 |
+
"quaternion_format": info.get("quaternion_format"),
|
| 67 |
+
"source_quaternion_format": info.get("source_quaternion_format"),
|
| 68 |
+
"source_fingerprint": report.get("source_fingerprint"),
|
| 69 |
+
}
|
| 70 |
+
provenance_path = output_dir / "data_provenance.json"
|
| 71 |
+
provenance_path.write_text(json.dumps(provenance, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
| 72 |
+
|
| 73 |
+
fingerprint = {
|
| 74 |
+
"datasets": {
|
| 75 |
+
"xtrainer2_pack_phone": compute_dataset_content_fingerprint(dataset_root),
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
fingerprint_path = output_dir / "content_fingerprints.json"
|
| 79 |
+
fingerprint_path.write_text(json.dumps(fingerprint) + "\n", encoding="utf-8")
|
| 80 |
+
|
| 81 |
+
print(f"wrote {provenance_path}")
|
| 82 |
+
print(f"wrote {fingerprint_path}")
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
if __name__ == "__main__":
|
| 86 |
+
main()
|
real_robot/source/examples/Xtrainer2/train_files/build_pick_bottle_support.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Write frozen fingerprint and provenance for xtrainer2_pick_bottle."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _identity_files(dataset_root: Path) -> list[Path]:
|
| 13 |
+
files = list((dataset_root / "data").rglob("*.parquet"))
|
| 14 |
+
files.extend((dataset_root / "videos").rglob("*.mp4"))
|
| 15 |
+
meta_root = dataset_root / "meta"
|
| 16 |
+
if meta_root.exists():
|
| 17 |
+
for pattern in ("*.json", "*.jsonl", "*.parquet"):
|
| 18 |
+
files.extend(meta_root.rglob(pattern))
|
| 19 |
+
return sorted(set(files), key=lambda path: path.relative_to(dataset_root).as_posix())
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def compute_dataset_content_fingerprint(dataset_root: str | Path) -> dict[str, Any]:
|
| 23 |
+
root = Path(dataset_root)
|
| 24 |
+
entries = []
|
| 25 |
+
total_bytes = 0
|
| 26 |
+
for file_path in _identity_files(root):
|
| 27 |
+
relative_path = file_path.relative_to(root).as_posix()
|
| 28 |
+
file_size = file_path.stat().st_size
|
| 29 |
+
entries.append({"path": relative_path, "bytes": file_size})
|
| 30 |
+
total_bytes += file_size
|
| 31 |
+
return {
|
| 32 |
+
"file_count": len(entries),
|
| 33 |
+
"total_bytes": total_bytes,
|
| 34 |
+
"files": entries,
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def main() -> None:
|
| 39 |
+
parser = argparse.ArgumentParser()
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--data-root",
|
| 42 |
+
type=Path,
|
| 43 |
+
default=Path("/path/to/data/lerobot"),
|
| 44 |
+
)
|
| 45 |
+
parser.add_argument(
|
| 46 |
+
"--output-dir",
|
| 47 |
+
type=Path,
|
| 48 |
+
default=None,
|
| 49 |
+
)
|
| 50 |
+
args = parser.parse_args()
|
| 51 |
+
dataset_root = args.data_root / "xtrainer2_pick_bottle"
|
| 52 |
+
output_dir = args.output_dir or (args.data_root / "xtrainer2_optr_support" / "pick_bottle")
|
| 53 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 54 |
+
|
| 55 |
+
info = json.loads((dataset_root / "meta" / "info.json").read_text(encoding="utf-8"))
|
| 56 |
+
report_path = dataset_root / "conversion_report.json"
|
| 57 |
+
report = json.loads(report_path.read_text(encoding="utf-8")) if report_path.is_file() else {}
|
| 58 |
+
|
| 59 |
+
provenance = {
|
| 60 |
+
"dataset_name": "xtrainer2_pick_bottle",
|
| 61 |
+
"codebase_version": info.get("codebase_version"),
|
| 62 |
+
"robot_type": info.get("robot_type"),
|
| 63 |
+
"fps": info.get("fps"),
|
| 64 |
+
"total_episodes": info.get("total_episodes"),
|
| 65 |
+
"total_frames": info.get("total_frames"),
|
| 66 |
+
"total_tasks": info.get("total_tasks"),
|
| 67 |
+
"quaternion_format": info.get("quaternion_format"),
|
| 68 |
+
"source_quaternion_format": info.get("source_quaternion_format"),
|
| 69 |
+
"source_fingerprint": report.get("source_fingerprint"),
|
| 70 |
+
}
|
| 71 |
+
provenance_path = output_dir / "data_provenance.json"
|
| 72 |
+
provenance_path.write_text(json.dumps(provenance, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
| 73 |
+
|
| 74 |
+
fingerprint = {
|
| 75 |
+
"datasets": {
|
| 76 |
+
"xtrainer2_pick_bottle": compute_dataset_content_fingerprint(dataset_root),
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
fingerprint_path = output_dir / "content_fingerprints.json"
|
| 80 |
+
fingerprint_path.write_text(json.dumps(fingerprint) + "\n", encoding="utf-8")
|
| 81 |
+
|
| 82 |
+
print(f"wrote {provenance_path}")
|
| 83 |
+
print(f"wrote {fingerprint_path}")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
main()
|
real_robot/source/examples/Xtrainer2/train_files/data_registry/data_config.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""StarVLA data registry for xtrainer2 bimanual EEF datasets."""
|
| 2 |
+
|
| 3 |
+
from starVLA.dataloader.gr00t_lerobot.datasets import ModalityConfig
|
| 4 |
+
from starVLA.dataloader.gr00t_lerobot.embodiment_tags import EmbodimentTag
|
| 5 |
+
from starVLA.dataloader.gr00t_lerobot.transform.base import ComposedModalityTransform
|
| 6 |
+
from starVLA.dataloader.gr00t_lerobot.transform.state_action import (
|
| 7 |
+
StateActionToTensor,
|
| 8 |
+
StateActionTransform,
|
| 9 |
+
)
|
| 10 |
+
from starVLA.dataloader.gr00t_lerobot.transform.video import (
|
| 11 |
+
VideoColorJitter,
|
| 12 |
+
VideoCrop,
|
| 13 |
+
VideoResize,
|
| 14 |
+
VideoToNumpy,
|
| 15 |
+
VideoToTensor,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Xtrainer2BimanualEefDataConfig:
|
| 20 |
+
"""Three-camera bimanual EEF control at 10 Hz.
|
| 21 |
+
|
| 22 |
+
LeRobot stores 16 raw dimensions per state/action in wxyz. Training
|
| 23 |
+
converts quaternions to rotation-6D (20 model dims) and inverts them
|
| 24 |
+
back to 16-D wxyz. With ``action_mode: rel`` the dataloader expresses
|
| 25 |
+
xyz and orientation relative to the current EE; the websocket server
|
| 26 |
+
composes those back to absolute poses and reorders wxyz -> xyzw.
|
| 27 |
+
See ``examples/Xtrainer2/README.md``.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
embodiment_tag = EmbodimentTag.NEW_EMBODIMENT
|
| 31 |
+
video_keys = [
|
| 32 |
+
"video.cam_high",
|
| 33 |
+
"video.cam_left_wrist",
|
| 34 |
+
"video.cam_right_wrist",
|
| 35 |
+
]
|
| 36 |
+
state_keys = [
|
| 37 |
+
"state.left_position",
|
| 38 |
+
"state.left_rotation",
|
| 39 |
+
"state.left_gripper",
|
| 40 |
+
"state.right_position",
|
| 41 |
+
"state.right_rotation",
|
| 42 |
+
"state.right_gripper",
|
| 43 |
+
]
|
| 44 |
+
action_keys = [
|
| 45 |
+
"action.left_position",
|
| 46 |
+
"action.left_rotation",
|
| 47 |
+
"action.left_gripper",
|
| 48 |
+
"action.right_position",
|
| 49 |
+
"action.right_rotation",
|
| 50 |
+
"action.right_gripper",
|
| 51 |
+
]
|
| 52 |
+
language_keys = ["annotation.human.action.task_description"]
|
| 53 |
+
observation_indices = [0]
|
| 54 |
+
action_indices = list(range(16))
|
| 55 |
+
|
| 56 |
+
# Raw widths, matching ``meta/modality.json`` and the 16-D arrays in
|
| 57 |
+
# ``dataset_statistics.json``. 16 is not divisible by the 6 keys, so the
|
| 58 |
+
# policy server cannot infer the split and must be told.
|
| 59 |
+
state_key_dims = {
|
| 60 |
+
"state.left_position": 3,
|
| 61 |
+
"state.left_rotation": 4,
|
| 62 |
+
"state.left_gripper": 1,
|
| 63 |
+
"state.right_position": 3,
|
| 64 |
+
"state.right_rotation": 4,
|
| 65 |
+
"state.right_gripper": 1,
|
| 66 |
+
}
|
| 67 |
+
action_key_dims = {
|
| 68 |
+
"action.left_position": 3,
|
| 69 |
+
"action.left_rotation": 4,
|
| 70 |
+
"action.left_gripper": 1,
|
| 71 |
+
"action.right_position": 3,
|
| 72 |
+
"action.right_rotation": 4,
|
| 73 |
+
"action.right_gripper": 1,
|
| 74 |
+
}
|
| 75 |
+
# Source representation of every rotation key. The dataset carries this in
|
| 76 |
+
# ``meta/modality.json``; deployment rebuilds the transforms from the
|
| 77 |
+
# checkpoint alone, where that file is not available.
|
| 78 |
+
rotation_types = {
|
| 79 |
+
"state.left_rotation": "quaternion",
|
| 80 |
+
"state.right_rotation": "quaternion",
|
| 81 |
+
"action.left_rotation": "quaternion",
|
| 82 |
+
"action.right_rotation": "quaternion",
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
# Deployment contract (read by deployment/model_server). The robot sends
|
| 86 |
+
# and receives 16 raw dims with xyzw quaternions; the model works in 20
|
| 87 |
+
# normalized dims with wxyz. See deployment/model_server/xtrainer2_command.py.
|
| 88 |
+
robot_command_layout = "xtrainer2_bimanual_eef_xyzw"
|
| 89 |
+
server_normalizes_state = True
|
| 90 |
+
|
| 91 |
+
def modality_config(self):
|
| 92 |
+
return {
|
| 93 |
+
"video": ModalityConfig(
|
| 94 |
+
delta_indices=self.observation_indices,
|
| 95 |
+
modality_keys=self.video_keys,
|
| 96 |
+
),
|
| 97 |
+
"state": ModalityConfig(
|
| 98 |
+
delta_indices=self.observation_indices,
|
| 99 |
+
modality_keys=self.state_keys,
|
| 100 |
+
),
|
| 101 |
+
"action": ModalityConfig(
|
| 102 |
+
delta_indices=self.action_indices,
|
| 103 |
+
modality_keys=self.action_keys,
|
| 104 |
+
),
|
| 105 |
+
"language": ModalityConfig(
|
| 106 |
+
delta_indices=self.observation_indices,
|
| 107 |
+
modality_keys=self.language_keys,
|
| 108 |
+
),
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
def transform(self):
|
| 112 |
+
rotation_keys = {
|
| 113 |
+
"state.left_rotation": "rotation_6d",
|
| 114 |
+
"state.right_rotation": "rotation_6d",
|
| 115 |
+
}
|
| 116 |
+
action_rotation_keys = {
|
| 117 |
+
"action.left_rotation": "rotation_6d",
|
| 118 |
+
"action.right_rotation": "rotation_6d",
|
| 119 |
+
}
|
| 120 |
+
state_normalization = {
|
| 121 |
+
key: "min_max" if key.endswith("_rotation") else "q99"
|
| 122 |
+
for key in self.state_keys
|
| 123 |
+
}
|
| 124 |
+
action_normalization = {
|
| 125 |
+
key: "min_max" if key.endswith("_rotation") else "q99"
|
| 126 |
+
for key in self.action_keys
|
| 127 |
+
}
|
| 128 |
+
return ComposedModalityTransform(
|
| 129 |
+
transforms=[
|
| 130 |
+
VideoToTensor(apply_to=self.video_keys),
|
| 131 |
+
VideoCrop(apply_to=self.video_keys, scale=0.95),
|
| 132 |
+
VideoResize(
|
| 133 |
+
apply_to=self.video_keys,
|
| 134 |
+
height=224,
|
| 135 |
+
width=224,
|
| 136 |
+
interpolation="linear",
|
| 137 |
+
),
|
| 138 |
+
VideoColorJitter(
|
| 139 |
+
apply_to=self.video_keys,
|
| 140 |
+
brightness=0.3,
|
| 141 |
+
contrast=0.4,
|
| 142 |
+
saturation=0.5,
|
| 143 |
+
hue=0.08,
|
| 144 |
+
),
|
| 145 |
+
VideoToNumpy(apply_to=self.video_keys),
|
| 146 |
+
StateActionToTensor(apply_to=self.state_keys),
|
| 147 |
+
StateActionTransform(
|
| 148 |
+
apply_to=self.state_keys,
|
| 149 |
+
normalization_modes=state_normalization,
|
| 150 |
+
target_rotations=rotation_keys,
|
| 151 |
+
),
|
| 152 |
+
StateActionToTensor(apply_to=self.action_keys),
|
| 153 |
+
StateActionTransform(
|
| 154 |
+
apply_to=self.action_keys,
|
| 155 |
+
normalization_modes=action_normalization,
|
| 156 |
+
target_rotations=action_rotation_keys,
|
| 157 |
+
),
|
| 158 |
+
]
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
ROBOT_TYPE_CONFIG_MAP = {
|
| 163 |
+
"xtrainer2_bimanual_eef": Xtrainer2BimanualEefDataConfig(),
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
ROBOT_TYPE_TO_EMBODIMENT_TAG = {}
|
| 167 |
+
|
| 168 |
+
DATASET_NAMED_MIXTURES = {
|
| 169 |
+
"xtrainer2_pick_bottle": [
|
| 170 |
+
("xtrainer2_pick_bottle", 1.0, "xtrainer2_bimanual_eef"),
|
| 171 |
+
],
|
| 172 |
+
"xtrainer2_pack_phone": [
|
| 173 |
+
("xtrainer2_pack_phone", 1.0, "xtrainer2_bimanual_eef"),
|
| 174 |
+
],
|
| 175 |
+
"xtrainer2_pick_pack": [
|
| 176 |
+
("xtrainer2_pick_bottle", 1.0, "xtrainer2_bimanual_eef"),
|
| 177 |
+
("xtrainer2_pack_phone", 1.0, "xtrainer2_bimanual_eef"),
|
| 178 |
+
],
|
| 179 |
+
}
|
real_robot/source/examples/Xtrainer2/train_files/modality.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"state": {
|
| 3 |
+
"left_position": {"start": 0, "end": 3, "dtype": "float32", "absolute": true, "original_key": "observation.state"},
|
| 4 |
+
"left_rotation": {"start": 3, "end": 7, "dtype": "float32", "absolute": true, "rotation_type": "quaternion", "original_key": "observation.state"},
|
| 5 |
+
"left_gripper": {"start": 7, "end": 8, "dtype": "float32", "absolute": true, "original_key": "observation.state"},
|
| 6 |
+
"right_position": {"start": 8, "end": 11, "dtype": "float32", "absolute": true, "original_key": "observation.state"},
|
| 7 |
+
"right_rotation": {"start": 11, "end": 15, "dtype": "float32", "absolute": true, "rotation_type": "quaternion", "original_key": "observation.state"},
|
| 8 |
+
"right_gripper": {"start": 15, "end": 16, "dtype": "float32", "absolute": true, "original_key": "observation.state"}
|
| 9 |
+
},
|
| 10 |
+
"action": {
|
| 11 |
+
"left_position": {"start": 0, "end": 3, "dtype": "float32", "absolute": true, "original_key": "action"},
|
| 12 |
+
"left_rotation": {"start": 3, "end": 7, "dtype": "float32", "absolute": true, "rotation_type": "quaternion", "original_key": "action"},
|
| 13 |
+
"left_gripper": {"start": 7, "end": 8, "dtype": "float32", "absolute": true, "original_key": "action"},
|
| 14 |
+
"right_position": {"start": 8, "end": 11, "dtype": "float32", "absolute": true, "original_key": "action"},
|
| 15 |
+
"right_rotation": {"start": 11, "end": 15, "dtype": "float32", "absolute": true, "rotation_type": "quaternion", "original_key": "action"},
|
| 16 |
+
"right_gripper": {"start": 15, "end": 16, "dtype": "float32", "absolute": true, "original_key": "action"}
|
| 17 |
+
},
|
| 18 |
+
"video": {
|
| 19 |
+
"cam_high": {"original_key": "observation.images.cam_high"},
|
| 20 |
+
"cam_left_wrist": {"original_key": "observation.images.cam_left_wrist"},
|
| 21 |
+
"cam_right_wrist": {"original_key": "observation.images.cam_right_wrist"}
|
| 22 |
+
},
|
| 23 |
+
"annotation": {
|
| 24 |
+
"human.action.task_description": {"original_key": "task_index"}
|
| 25 |
+
}
|
| 26 |
+
}
|
real_robot/source/examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Start the StarVLA websocket policy server for xtrainer2.
|
| 3 |
+
# The robot PC talks to this process; do not clone RoboticsX into this repo.
|
| 4 |
+
# The on-robot client is a separate component.
|
| 5 |
+
set -euo pipefail
|
| 6 |
+
|
| 7 |
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
| 8 |
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
| 9 |
+
export PYTHONPATH="${REPO_ROOT}:${PYTHONPATH:-}"
|
| 10 |
+
|
| 11 |
+
if [[ -f "${REPO_ROOT}/.env.xtrainer2-server" ]]; then
|
| 12 |
+
# Local GPU-server paths (gitignored). Sets OPTR_PYTHON / FLORENCE_MODEL_PATH.
|
| 13 |
+
# shellcheck disable=SC1091
|
| 14 |
+
source "${REPO_ROOT}/.env.xtrainer2-server"
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
if [[ $# -lt 1 ]]; then
|
| 18 |
+
echo "Usage: bash examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh <ckpt.pt> [gpu_id] [port]" >&2
|
| 19 |
+
exit 1
|
| 20 |
+
fi
|
| 21 |
+
|
| 22 |
+
ckpt=$(readlink -f "$1")
|
| 23 |
+
gpu_id=${2:-0}
|
| 24 |
+
port=${3:-10093}
|
| 25 |
+
if [[ -z "${OPTR_PYTHON:-}" && -z "${STARVLA_PYTHON:-}" && -x "${REPO_ROOT}/.venv-xtrainer2/bin/python" ]]; then
|
| 26 |
+
python_bin="${REPO_ROOT}/.venv-xtrainer2/bin/python"
|
| 27 |
+
else
|
| 28 |
+
python_bin=${OPTR_PYTHON:-${STARVLA_PYTHON:-python}}
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
if [[ ! -f $ckpt ]]; then
|
| 32 |
+
echo "checkpoint not found: ${ckpt}" >&2
|
| 33 |
+
exit 1
|
| 34 |
+
fi
|
| 35 |
+
|
| 36 |
+
# `read_mode_config` resolves config.yaml and dataset_statistics.json from the
|
| 37 |
+
# checkpoint's grandparent, i.e. it expects <run>/checkpoints/<name>.pt. A
|
| 38 |
+
# DeepSpeed run instead writes <run>/checkpoints/steps_N_accelerate/pytorch_model.pt,
|
| 39 |
+
# which is one level deeper, so stage that layout out of the way of the run dir.
|
| 40 |
+
ckpt_dir=$(dirname "$ckpt")
|
| 41 |
+
if [[ -f "${ckpt_dir}/../config.yaml" ]]; then
|
| 42 |
+
run_dir=$(cd "${ckpt_dir}/.." && pwd)
|
| 43 |
+
serve_ckpt=$ckpt
|
| 44 |
+
stage_dir=""
|
| 45 |
+
elif [[ -f "${ckpt_dir}/../../config.yaml" ]]; then
|
| 46 |
+
run_dir=$(cd "${ckpt_dir}/../.." && pwd)
|
| 47 |
+
stage_dir="${TMPDIR:-/tmp}/starvla-serve/$(basename "$run_dir")/$(basename "$ckpt_dir")"
|
| 48 |
+
mkdir -p "${stage_dir}/checkpoints"
|
| 49 |
+
ln -sfn "${run_dir}/config.yaml" "${stage_dir}/config.yaml"
|
| 50 |
+
ln -sfn "${run_dir}/dataset_statistics.json" "${stage_dir}/dataset_statistics.json"
|
| 51 |
+
ln -sfn "$ckpt" "${stage_dir}/checkpoints/$(basename "$ckpt_dir")_pytorch_model.pt"
|
| 52 |
+
serve_ckpt="${stage_dir}/checkpoints/$(basename "$ckpt_dir")_pytorch_model.pt"
|
| 53 |
+
echo "[INFO] accelerate checkpoint layout; serving through ${stage_dir}"
|
| 54 |
+
else
|
| 55 |
+
echo "need config.yaml and dataset_statistics.json in the run dir above ${ckpt_dir}" >&2
|
| 56 |
+
exit 1
|
| 57 |
+
fi
|
| 58 |
+
|
| 59 |
+
# Training config.yaml records the cluster Florence path. Rewrite it locally
|
| 60 |
+
# so from_pretrained does not look for /path/to/models/... .
|
| 61 |
+
if [[ -n "${FLORENCE_MODEL_PATH:-}" ]]; then
|
| 62 |
+
if [[ ! -d $FLORENCE_MODEL_PATH ]]; then
|
| 63 |
+
echo "FLORENCE_MODEL_PATH is not a directory: ${FLORENCE_MODEL_PATH}" >&2
|
| 64 |
+
exit 1
|
| 65 |
+
fi
|
| 66 |
+
if [[ -z $stage_dir ]]; then
|
| 67 |
+
stage_dir="${TMPDIR:-/tmp}/starvla-serve/$(basename "$run_dir")/local-florence"
|
| 68 |
+
mkdir -p "${stage_dir}/checkpoints"
|
| 69 |
+
ln -sfn "$ckpt" "${stage_dir}/checkpoints/$(basename "$ckpt")"
|
| 70 |
+
ln -sfn "${run_dir}/dataset_statistics.json" "${stage_dir}/dataset_statistics.json"
|
| 71 |
+
serve_ckpt="${stage_dir}/checkpoints/$(basename "$ckpt")"
|
| 72 |
+
fi
|
| 73 |
+
FLORENCE_MODEL_PATH="$FLORENCE_MODEL_PATH" "$python_bin" - "${run_dir}/config.yaml" "${stage_dir}/config.yaml" <<'PY'
|
| 74 |
+
import os
|
| 75 |
+
import sys
|
| 76 |
+
|
| 77 |
+
from omegaconf import OmegaConf
|
| 78 |
+
|
| 79 |
+
src, dst = sys.argv[1], sys.argv[2]
|
| 80 |
+
path = os.environ["FLORENCE_MODEL_PATH"]
|
| 81 |
+
if "florence" not in path.lower():
|
| 82 |
+
raise SystemExit(f"FLORENCE_MODEL_PATH must contain 'florence': {path}")
|
| 83 |
+
cfg = OmegaConf.load(src)
|
| 84 |
+
cfg.framework.qwenvl.base_vlm = path
|
| 85 |
+
OmegaConf.save(cfg, dst)
|
| 86 |
+
print(f"[INFO] rewrote base_vlm -> {path}")
|
| 87 |
+
PY
|
| 88 |
+
fi
|
| 89 |
+
|
| 90 |
+
if [[ ! -f $run_dir/dataset_statistics.json ]]; then
|
| 91 |
+
echo "missing ${run_dir}/dataset_statistics.json" >&2
|
| 92 |
+
exit 1
|
| 93 |
+
fi
|
| 94 |
+
|
| 95 |
+
echo "[INFO] xtrainer2 policy server"
|
| 96 |
+
echo "[INFO] checkpoint: ${ckpt}"
|
| 97 |
+
echo "[INFO] run dir: ${run_dir}"
|
| 98 |
+
echo "[INFO] gpu: ${gpu_id} port: ${port}"
|
| 99 |
+
echo "[INFO] client contract: 3 images (cam_high, cam_left_wrist, cam_right_wrist)"
|
| 100 |
+
echo "[INFO] HWC RGB uint8, 16-D robot state and 16-D actions, quaternions xyzw"
|
| 101 |
+
echo "[INFO] instruction must be English: Pick up the phone, place it in the box, and close the lid."
|
| 102 |
+
|
| 103 |
+
# Local Florence weights; do not send Hub traffic through a lab HTTP proxy.
|
| 104 |
+
unset HF_ENDPOINT || true
|
| 105 |
+
idle_timeout="${OPTR_IDLE_TIMEOUT:--1}"
|
| 106 |
+
CUDA_VISIBLE_DEVICES=$gpu_id "$python_bin" \
|
| 107 |
+
"$REPO_ROOT/deployment/model_server/server_policy.py" \
|
| 108 |
+
--ckpt_path "$serve_ckpt" \
|
| 109 |
+
--port "$port" \
|
| 110 |
+
--use_bf16 \
|
| 111 |
+
--idle_timeout "$idle_timeout"
|
real_robot/source/examples/Xtrainer2/train_files/run_xtrainer2_train.sh
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
if [[ $# -ne 2 ]]; then
|
| 5 |
+
echo "usage: $0 <config.yaml> <run-id>" >&2
|
| 6 |
+
exit 64
|
| 7 |
+
fi
|
| 8 |
+
|
| 9 |
+
project="${OPTR_PROJECT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
|
| 10 |
+
python="${OPTR_PYTHON:-python}"
|
| 11 |
+
accelerate_config="${OPTR_ACCELERATE_CONFIG:-${project}/configs/deepspeed_florence_8gpu.yaml}"
|
| 12 |
+
num_processes="${OPTR_NUM_PROCESSES:-8}"
|
| 13 |
+
config=$1
|
| 14 |
+
run_id=$2
|
| 15 |
+
|
| 16 |
+
if [[ ! -f "$config" && -f "${project}/${config}" ]]; then
|
| 17 |
+
config="${project}/${config}"
|
| 18 |
+
fi
|
| 19 |
+
if [[ ! -f "$config" ]]; then
|
| 20 |
+
echo "error: training config not found: ${config}" >&2
|
| 21 |
+
exit 66
|
| 22 |
+
fi
|
| 23 |
+
|
| 24 |
+
# The ${oc.env:...} refs in the training yaml have no defaults; OmegaConf
|
| 25 |
+
# resolves them eagerly and would abort on all ranks. Collect names from the
|
| 26 |
+
# file so pack_phone and pick_bottle (different provenance vars) share this
|
| 27 |
+
# launcher.
|
| 28 |
+
mapfile -t required_vars < <("$python" - "$config" <<'PY'
|
| 29 |
+
import re
|
| 30 |
+
import sys
|
| 31 |
+
from pathlib import Path
|
| 32 |
+
|
| 33 |
+
text = Path(sys.argv[1]).read_text(encoding="utf-8")
|
| 34 |
+
names = sorted(set(re.findall(r"\$\{oc\.env:([A-Za-z_][A-Za-z0-9_]*)\}", text)))
|
| 35 |
+
if not names:
|
| 36 |
+
sys.exit("error: no ${oc.env:...} interpolations found in " + sys.argv[1])
|
| 37 |
+
print("\n".join(names))
|
| 38 |
+
PY
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
missing=()
|
| 42 |
+
for var in "${required_vars[@]}"; do
|
| 43 |
+
if [[ -z "${!var:-}" ]]; then
|
| 44 |
+
missing+=("$var")
|
| 45 |
+
fi
|
| 46 |
+
done
|
| 47 |
+
if (( ${#missing[@]} )); then
|
| 48 |
+
echo "error: required environment variables are unset: ${missing[*]}" >&2
|
| 49 |
+
echo "see examples/Xtrainer2/README.md for the export block" >&2
|
| 50 |
+
exit 78
|
| 51 |
+
fi
|
| 52 |
+
|
| 53 |
+
if [[ -n "${FLORENCE_MODEL_PATH:-}" ]]; then
|
| 54 |
+
if [[ "${FLORENCE_MODEL_PATH,,}" != *florence* ]]; then
|
| 55 |
+
echo "error: FLORENCE_MODEL_PATH must contain 'florence' or the VLM dispatch" >&2
|
| 56 |
+
echo " raises NotImplementedError: ${FLORENCE_MODEL_PATH}" >&2
|
| 57 |
+
exit 78
|
| 58 |
+
fi
|
| 59 |
+
if [[ ! -d "$FLORENCE_MODEL_PATH" ]]; then
|
| 60 |
+
echo "error: FLORENCE_MODEL_PATH is not a directory: ${FLORENCE_MODEL_PATH}" >&2
|
| 61 |
+
exit 66
|
| 62 |
+
fi
|
| 63 |
+
fi
|
| 64 |
+
|
| 65 |
+
for var in "${required_vars[@]}"; do
|
| 66 |
+
value="${!var}"
|
| 67 |
+
case "$var" in
|
| 68 |
+
FLORENCE_MODEL_PATH|OPTR_RUN_ROOT|XTRAINER2_DATA_ROOT) continue ;;
|
| 69 |
+
esac
|
| 70 |
+
if [[ "$var" == *_PATH || "$var" == *_DIR ]]; then
|
| 71 |
+
if [[ ! -e "$value" ]]; then
|
| 72 |
+
echo "error: support file missing: ${value} (${var})" >&2
|
| 73 |
+
echo " run the matching examples/Xtrainer2/train_files/build_*_support.py first" >&2
|
| 74 |
+
exit 66
|
| 75 |
+
fi
|
| 76 |
+
fi
|
| 77 |
+
done
|
| 78 |
+
|
| 79 |
+
if ! command -v "$python" >/dev/null 2>&1 && [[ ! -x "$python" ]]; then
|
| 80 |
+
echo "error: python interpreter not found: ${python}" >&2
|
| 81 |
+
echo " set OPTR_PYTHON to an interpreter with deepspeed and pytorch3d" >&2
|
| 82 |
+
exit 69
|
| 83 |
+
fi
|
| 84 |
+
|
| 85 |
+
if [[ ! -f "$accelerate_config" ]]; then
|
| 86 |
+
echo "error: accelerate config not found: ${accelerate_config}" >&2
|
| 87 |
+
exit 66
|
| 88 |
+
fi
|
| 89 |
+
|
| 90 |
+
cd "$project"
|
| 91 |
+
export PYTHONPATH="${project}:${PYTHONPATH:-}"
|
| 92 |
+
export WANDB_MODE="${WANDB_MODE:-offline}"
|
| 93 |
+
"$python" -m accelerate.commands.launch \
|
| 94 |
+
--config_file "$accelerate_config" \
|
| 95 |
+
--num_processes "$num_processes" \
|
| 96 |
+
starVLA/training/train_starvla.py \
|
| 97 |
+
--config_yaml "$config" \
|
| 98 |
+
--run_id "$run_id"
|
real_robot/source/licenses/Diffusion-Policy-LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2023 Columbia Artificial Intelligence and Robotics Lab
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
real_robot/source/licenses/Isaac-GR00T-LICENSE
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by the Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding any notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 179 |
+
|
| 180 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 181 |
+
you may not use this file except in compliance with the License.
|
| 182 |
+
You may obtain a copy of the License at
|
| 183 |
+
|
| 184 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 185 |
+
|
| 186 |
+
Unless required by applicable law or agreed to in writing, software
|
| 187 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 188 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 189 |
+
See the License for the specific language governing permissions and
|
| 190 |
+
limitations under the License.
|
real_robot/source/licenses/MODIFIED_FILES.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
starVLA/dataloader/lerobot_datasets.py
|
| 2 |
+
starVLA/dataloader/__init__.py
|
| 3 |
+
starVLA/training/train_starvla.py
|
| 4 |
+
starVLA/training/trainer_utils/overwatch.py
|
| 5 |
+
starVLA/training/trainer_utils/trainer_tools.py
|
| 6 |
+
starVLA/model/framework/base_framework.py
|
| 7 |
+
starVLA/model/framework/share_tools.py
|
| 8 |
+
starVLA/model/modules/action_model/GR00T_ActionHeader.py
|
| 9 |
+
starVLA/model/modules/action_model/__init__.py
|
| 10 |
+
starVLA/model/modules/vlm/Florence2.py
|
| 11 |
+
starVLA/model/modules/vlm/QWen3.py
|
| 12 |
+
starVLA/model/modules/vlm/__init__.py
|
| 13 |
+
starVLA/model/modules/action_model/flow_matching_head/cross_attention_dit.py
|
| 14 |
+
starVLA/model/framework/VLM4A/QwenGR00T.py
|
| 15 |
+
starVLA/dataloader/gr00t_lerobot/datasets.py
|
| 16 |
+
starVLA/dataloader/gr00t_lerobot/data_config.py
|
| 17 |
+
starVLA/dataloader/gr00t_lerobot/transform/state_action.py
|
real_robot/source/licenses/OpenVLA-LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 Moo Jin Kim, Karl Pertsch, Siddharth Karamcheti.
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
real_robot/source/licenses/X-VLA-LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
real_robot/source/licenses/msgpack-numpy-LICENSE
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!---
|
| 2 |
+
-*- mode:markdown -*-
|
| 3 |
+
vi:ft=markdown
|
| 4 |
+
-->
|
| 5 |
+
License
|
| 6 |
+
=======
|
| 7 |
+
|
| 8 |
+
Copyright (c) 2013-2022, Lev E. Givon.
|
| 9 |
+
All rights reserved.
|
| 10 |
+
|
| 11 |
+
Redistribution and use in source and binary forms, with or without
|
| 12 |
+
modification, are permitted provided that the following conditions are
|
| 13 |
+
met:
|
| 14 |
+
|
| 15 |
+
* Redistributions of source code must retain the above copyright
|
| 16 |
+
notice, this list of conditions and the following disclaimer.
|
| 17 |
+
* Redistributions in binary form must reproduce the above
|
| 18 |
+
copyright notice, this list of conditions and the following
|
| 19 |
+
disclaimer in the documentation and/or other materials provided
|
| 20 |
+
with the distribution.
|
| 21 |
+
* Neither the name of Lev E. Givon nor the names of any
|
| 22 |
+
contributors may be used to endorse or promote products derived
|
| 23 |
+
from this software without specific prior written permission.
|
| 24 |
+
|
| 25 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 26 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 27 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 28 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 29 |
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 30 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 31 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 32 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 33 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 34 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 35 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
real_robot/source/pyproject.toml
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "retime-starvla"
|
| 7 |
+
description = "ReTime temporal sampling plugin and compatible StarVLA backend."
|
| 8 |
+
version = "0.1.0"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.10"
|
| 11 |
+
keywords = ["vision-language-action models", "robot learning", "importance sampling"]
|
| 12 |
+
license = {file = "LICENSE"}
|
| 13 |
+
classifiers = [
|
| 14 |
+
"Development Status :: 3 - Alpha",
|
| 15 |
+
"Intended Audience :: Developers",
|
| 16 |
+
"Intended Audience :: Science/Research",
|
| 17 |
+
"Operating System :: POSIX :: Linux",
|
| 18 |
+
"Programming Language :: Python :: 3 :: Only",
|
| 19 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 20 |
+
]
|
| 21 |
+
dependencies = ["omegaconf>=2.3,<3", "numpy", "scipy", "torch"]
|
| 22 |
+
|
| 23 |
+
[project.scripts]
|
| 24 |
+
retime = "retime.cli:main"
|
| 25 |
+
retime-pretrain = "retime.pretrain:main"
|
| 26 |
+
|
| 27 |
+
[project.optional-dependencies]
|
| 28 |
+
dev = [
|
| 29 |
+
"black>=24.2.0",
|
| 30 |
+
"ruff>=0.2.2",
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
[tool.setuptools]
|
| 35 |
+
license-files = ["LICENSE", "NOTICE", "licenses/*", "third_party/frameskip/LICENSE"]
|
| 36 |
+
|
| 37 |
+
[tool.setuptools.packages.find]
|
| 38 |
+
where = ["."]
|
| 39 |
+
exclude = [
|
| 40 |
+
"tests",
|
| 41 |
+
"tests.*",
|
| 42 |
+
"cache",
|
| 43 |
+
"cache.*",
|
| 44 |
+
"playground",
|
| 45 |
+
"playground.*",
|
| 46 |
+
"results",
|
| 47 |
+
"results.*",
|
| 48 |
+
"checkpoints",
|
| 49 |
+
"checkpoints.*",
|
| 50 |
+
"scripts",
|
| 51 |
+
"scripts.*",
|
| 52 |
+
"eval",
|
| 53 |
+
"eval.*",
|
| 54 |
+
"assets",
|
| 55 |
+
"assets.*",
|
| 56 |
+
".vscode",
|
| 57 |
+
"*.egg-info",
|
| 58 |
+
"**__pycache__**",
|
| 59 |
+
"*.pyc",
|
| 60 |
+
"*.pyo",
|
| 61 |
+
"starVLA.egg-info",
|
| 62 |
+
"starVLA.egg-info.*",
|
| 63 |
+
"starVLA.config",
|
| 64 |
+
"starVLA.config.*",
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
[tool.black]
|
| 68 |
+
line-length = 121
|
| 69 |
+
target-version = ["py310"]
|
| 70 |
+
preview = true
|
| 71 |
+
|
| 72 |
+
[tool.ruff]
|
| 73 |
+
line-length = 121
|
| 74 |
+
target-version = "py310"
|
| 75 |
+
|
| 76 |
+
[tool.ruff.lint]
|
| 77 |
+
select = ["A", "B", "E", "F", "I", "RUF", "W"]
|
| 78 |
+
ignore = ["F722"]
|
| 79 |
+
|
| 80 |
+
[tool.ruff.lint.per-file-ignores]
|
| 81 |
+
"__init__.py" = ["E402", "F401"]
|
real_robot/source/requirements-qwen.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optional dependencies for the supplied Qwen-GR00T configurations.
|
| 2 |
+
# Install requirements.txt first. Qwen Bridge and Qwen RoboTwin are included in
|
| 3 |
+
# the GPU integration test documented in VALIDATION.md.
|
| 4 |
+
|
| 5 |
+
-r requirements.txt
|
| 6 |
+
bitsandbytes==0.50.0
|
| 7 |
+
packaging
|
| 8 |
+
psutil
|
| 9 |
+
ninja
|
real_robot/source/requirements-xtrainer2.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optional dependencies for Xtrainer2 data processing and policy serving.
|
| 2 |
+
# Install requirements.txt first. This extension was not included in the
|
| 3 |
+
# Florence Bridge GPU integration test documented in VALIDATION.md.
|
| 4 |
+
#
|
| 5 |
+
# Xtrainer2 action_dim=20 requires a PyTorch3D build compatible with the
|
| 6 |
+
# installed Python, PyTorch, and CUDA versions. Install it separately from
|
| 7 |
+
# its official distribution for your platform.
|
| 8 |
+
|
| 9 |
+
-r requirements.txt
|
| 10 |
+
pytest
|
real_robot/source/requirements.txt
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
transformers==4.57.0
|
| 2 |
+
accelerate==1.13.0
|
| 3 |
+
tiktoken
|
| 4 |
+
einops
|
| 5 |
+
transformers_stream_generator==0.0.4
|
| 6 |
+
scipy
|
| 7 |
+
torch==2.7.1
|
| 8 |
+
torchvision==0.22.1
|
| 9 |
+
setuptools==80.9.0
|
| 10 |
+
pillow
|
| 11 |
+
tensorboard
|
| 12 |
+
matplotlib
|
| 13 |
+
albumentations==1.4.18
|
| 14 |
+
decord==0.6.0
|
| 15 |
+
pydantic==2.10.6
|
| 16 |
+
pyarrow==14.0.1
|
| 17 |
+
fastparquet==2024.11.0
|
| 18 |
+
av==12.3.0
|
| 19 |
+
numpydantic==1.6.9
|
| 20 |
+
deepspeed==0.19.2
|
| 21 |
+
qwen-vl-utils
|
| 22 |
+
omegaconf
|
| 23 |
+
numpy==1.26.4
|
| 24 |
+
wandb
|
| 25 |
+
rich
|
| 26 |
+
diffusers==0.33.1
|
| 27 |
+
timm
|
| 28 |
+
tyro
|
| 29 |
+
websockets==15.0.1
|
| 30 |
+
msgpack==1.1.0
|
| 31 |
+
tdigest==0.5.2.2
|
| 32 |
+
|
| 33 |
+
# Qwen configurations also require requirements-qwen.txt and a compatible
|
| 34 |
+
# Flash Attention 2 installation; see README.md.
|
real_robot/source/retime/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ReTime: objective-preserving temporal sampling for StarVLA.
|
| 2 |
+
|
| 3 |
+
Importing the plugin or its CLI does not initialize a model or CUDA.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
__version__ = "0.1.0"
|
real_robot/source/retime/__main__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .cli import main
|
| 2 |
+
|
| 3 |
+
if __name__ == "__main__":
|
| 4 |
+
raise SystemExit(main())
|
real_robot/source/retime/checkpoint.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Load full policy weights before or after DeepSpeed partitions parameters."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def load_policy_state_dict(model, state_dict, *, strict=True):
|
| 5 |
+
"""Every distributed rank must call this with the same state dictionary.
|
| 6 |
+
|
| 7 |
+
ZeRO-3 initialization replaces parameter storage with empty shards. Gather
|
| 8 |
+
those parameters while copying weights, then broadcast and repartition on
|
| 9 |
+
context exit. Loading on every rank also initializes replicated buffers.
|
| 10 |
+
Ordinary models do not need DeepSpeed installed.
|
| 11 |
+
"""
|
| 12 |
+
partitioned = [parameter for parameter in model.parameters() if hasattr(parameter, "ds_id")]
|
| 13 |
+
if not partitioned:
|
| 14 |
+
return model.load_state_dict(state_dict, strict=strict)
|
| 15 |
+
|
| 16 |
+
from deepspeed import zero
|
| 17 |
+
|
| 18 |
+
with zero.GatheredParameters(partitioned, modifier_rank=0):
|
| 19 |
+
return model.load_state_dict(state_dict, strict=strict)
|
real_robot/source/retime/cli.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""One-command adapter to the accompanying StarVLA training backend.
|
| 2 |
+
|
| 3 |
+
The CLI prepares temporal proposals and dispatches StarVLA training.
|
| 4 |
+
The sampler and ordinary importance correction are unchanged.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import json
|
| 11 |
+
import math
|
| 12 |
+
import os
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import subprocess
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def parser() -> argparse.ArgumentParser:
|
| 19 |
+
result = argparse.ArgumentParser(description=__doc__)
|
| 20 |
+
result.add_argument("--config", type=Path, required=True)
|
| 21 |
+
result.add_argument("--base-policy", type=Path, required=True,
|
| 22 |
+
help="Complete Accelerate checkpoint directory, committed single-process model file, or model file with --start weights.")
|
| 23 |
+
result.add_argument("--start", choices=("resume", "weights"), default="weights",
|
| 24 |
+
help="resume restores the full state; weights explicitly starts a new optimizer/schedule.")
|
| 25 |
+
result.add_argument("--alpha", type=float, default=0.2)
|
| 26 |
+
result.add_argument("--K", "--k", dest="k", type=int, default=8)
|
| 27 |
+
result.add_argument("--mode", nargs="+", choices=("retime", "uniform", "shuffle"),
|
| 28 |
+
default=["retime"])
|
| 29 |
+
result.add_argument("--manifest", type=Path)
|
| 30 |
+
result.add_argument("--data-root", type=Path, help="Override the dataset root in the config")
|
| 31 |
+
result.add_argument("--data-mix", type=str, help="Override the named dataset mixture in the config")
|
| 32 |
+
result.add_argument("--vlm-path", type=Path, help="Override the Florence/Qwen backbone location")
|
| 33 |
+
result.add_argument("--statistics-path", type=Path, help="Override precomputed dataset statistics, if required")
|
| 34 |
+
result.add_argument(
|
| 35 |
+
"--omit-data-identity-files",
|
| 36 |
+
action="store_true",
|
| 37 |
+
help="Use the manifest's computed dataset identity without separate provenance/fingerprint files.",
|
| 38 |
+
)
|
| 39 |
+
result.add_argument("--epsilon", type=float, default=0.1)
|
| 40 |
+
result.add_argument("--shuffle-seed", type=int, default=12345)
|
| 41 |
+
result.add_argument("--accelerate-config", type=Path, required=True)
|
| 42 |
+
result.add_argument("--output", type=Path, required=True)
|
| 43 |
+
result.add_argument("--enable-wandb", action="store_true")
|
| 44 |
+
result.add_argument("--backend-root", type=Path, default=Path(__file__).resolve().parents[1],
|
| 45 |
+
help="ReTime-enabled StarVLA source tree shipped with this artifact.")
|
| 46 |
+
result.add_argument("--dry-run", action="store_true",
|
| 47 |
+
help="Validate local inputs and print commands; do not score, train, or write files.")
|
| 48 |
+
return result
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _file(path: Path, label: str) -> Path:
|
| 52 |
+
path = path.expanduser().resolve()
|
| 53 |
+
if not path.is_file():
|
| 54 |
+
raise ValueError(f"{label} is not a file: {path}")
|
| 55 |
+
return path
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _directory(path: Path, label: str) -> Path:
|
| 59 |
+
path = path.expanduser().resolve()
|
| 60 |
+
if not path.is_dir():
|
| 61 |
+
raise ValueError(f"{label} is not a directory: {path}")
|
| 62 |
+
return path
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _native_commit_path(model_path: Path) -> Path:
|
| 66 |
+
for suffix in ("_pytorch_model.pt", "_model.safetensors"):
|
| 67 |
+
if model_path.name.endswith(suffix):
|
| 68 |
+
return model_path.with_name(
|
| 69 |
+
model_path.name[: -len(suffix)] + "_checkpoint.json"
|
| 70 |
+
)
|
| 71 |
+
raise ValueError(
|
| 72 |
+
"A single-process resume checkpoint must end in "
|
| 73 |
+
"_pytorch_model.pt or _model.safetensors"
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _validate_resume_checkpoint(base: Path, world_size: int) -> tuple[Path, dict]:
|
| 78 |
+
if base.is_dir():
|
| 79 |
+
model = _file(base / "pytorch_model.pt", "base-policy model")
|
| 80 |
+
commit = json.loads(
|
| 81 |
+
_file(base / "checkpoint.json", "checkpoint commit").read_text(
|
| 82 |
+
encoding="utf-8"
|
| 83 |
+
)
|
| 84 |
+
)
|
| 85 |
+
if int(commit.get("world_size", -1)) != world_size:
|
| 86 |
+
raise ValueError("Base checkpoint world size differs from the launch contract")
|
| 87 |
+
entries = commit.get("files", [])
|
| 88 |
+
if not entries or any(
|
| 89 |
+
not (base / entry["name"]).is_file()
|
| 90 |
+
or (base / entry["name"]).stat().st_size != int(entry["bytes"])
|
| 91 |
+
for entry in entries
|
| 92 |
+
):
|
| 93 |
+
raise ValueError("Base Accelerate checkpoint is incomplete")
|
| 94 |
+
return model, commit
|
| 95 |
+
|
| 96 |
+
model = _file(base, "base-policy model")
|
| 97 |
+
if world_size != 1:
|
| 98 |
+
raise ValueError(
|
| 99 |
+
"A committed single-process checkpoint requires a one-process launch"
|
| 100 |
+
)
|
| 101 |
+
commit_path = _native_commit_path(model)
|
| 102 |
+
commit = json.loads(_file(commit_path, "checkpoint commit").read_text(encoding="utf-8"))
|
| 103 |
+
files = commit.get("files", {})
|
| 104 |
+
for label in ("model", "training_state"):
|
| 105 |
+
entry = files.get(label, {})
|
| 106 |
+
path = model.parent / str(entry.get("name", ""))
|
| 107 |
+
if not path.is_file() or path.stat().st_size != int(entry.get("bytes", -1)):
|
| 108 |
+
raise ValueError(f"Base single-process checkpoint has an invalid {label} file")
|
| 109 |
+
if files["model"]["name"] != model.name:
|
| 110 |
+
raise ValueError("Checkpoint commit does not describe --base-policy")
|
| 111 |
+
return model, commit
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def build_plan(args: argparse.Namespace) -> dict:
|
| 115 |
+
from omegaconf import OmegaConf
|
| 116 |
+
|
| 117 |
+
if not math.isfinite(args.alpha) or not 0 <= args.alpha < 1:
|
| 118 |
+
raise ValueError("alpha must satisfy 0 <= alpha < 1")
|
| 119 |
+
if args.k < 1 or not math.isfinite(args.epsilon) or args.epsilon <= 0:
|
| 120 |
+
raise ValueError("K and epsilon must be positive")
|
| 121 |
+
if len(set(args.mode)) != len(args.mode):
|
| 122 |
+
raise ValueError("Each mode can be requested only once")
|
| 123 |
+
root = args.backend_root.expanduser().resolve()
|
| 124 |
+
config = _file(args.config, "config")
|
| 125 |
+
launch_config = _file(args.accelerate_config, "accelerate config")
|
| 126 |
+
cfg = OmegaConf.load(config)
|
| 127 |
+
if not args.enable_wandb:
|
| 128 |
+
cfg.wandb_project = None
|
| 129 |
+
launch = OmegaConf.load(launch_config)
|
| 130 |
+
output = args.output.expanduser().resolve()
|
| 131 |
+
cfg.run_root_dir = str(output / "runs")
|
| 132 |
+
cfg.output_dir = str(output / "prepare")
|
| 133 |
+
if args.data_root:
|
| 134 |
+
cfg.datasets.vla_data.data_root_dir = str(_directory(args.data_root, "dataset root"))
|
| 135 |
+
if args.data_mix:
|
| 136 |
+
cfg.datasets.vla_data.data_mix = args.data_mix
|
| 137 |
+
if args.vlm_path:
|
| 138 |
+
cfg.framework.qwenvl.base_vlm = str(_directory(args.vlm_path, "VLM backbone"))
|
| 139 |
+
if args.statistics_path:
|
| 140 |
+
cfg.datasets.vla_data.precomputed_statistics_path = str(_file(args.statistics_path, "statistics"))
|
| 141 |
+
if args.omit_data_identity_files:
|
| 142 |
+
cfg.datasets.vla_data.dataset_provenance_path = None
|
| 143 |
+
cfg.datasets.vla_data.dataset_content_fingerprint_path = None
|
| 144 |
+
# Model-only initialization derives support from the current dataset.
|
| 145 |
+
if args.start == "weights":
|
| 146 |
+
cfg.trainer.is_resume = False
|
| 147 |
+
cfg.trainer.resume_accelerate_checkpoint = None
|
| 148 |
+
cfg.trainer.resume_checkpoint_integrity_manifest = None
|
| 149 |
+
if OmegaConf.select(cfg, "trainer.initial_completed_steps") is not None:
|
| 150 |
+
cfg.trainer.initial_completed_steps = 0
|
| 151 |
+
if args.manifest is None:
|
| 152 |
+
cfg.datasets.vla_data.valid_anchor_manifest_path = None
|
| 153 |
+
cfg.datasets.vla_data.dataset_provenance_path = None
|
| 154 |
+
cfg.datasets.vla_data.dataset_content_fingerprint_path = None
|
| 155 |
+
cfg.datasets.vla_data.temporal_proposal_path = None
|
| 156 |
+
if args.manifest is not None:
|
| 157 |
+
cfg.datasets.vla_data.valid_anchor_manifest_path = str(args.manifest.expanduser().resolve())
|
| 158 |
+
if OmegaConf.select(cfg, "framework.name") != "QwenGR00T":
|
| 159 |
+
raise ValueError("This adapter supports the supplied Florence/Qwen GR00T configs only")
|
| 160 |
+
if not OmegaConf.select(cfg, "datasets.vla_data.traceable_sampling", default=False):
|
| 161 |
+
raise ValueError("ReTime requires traceable_sampling and a frozen valid-anchor manifest")
|
| 162 |
+
world_size = int(launch.num_processes)
|
| 163 |
+
if int(OmegaConf.select(launch, "num_machines", default=1)) != 1:
|
| 164 |
+
raise ValueError("The one-command launcher currently supports one machine")
|
| 165 |
+
if world_size != int(cfg.datasets.vla_data.expected_world_size):
|
| 166 |
+
raise ValueError("Accelerate process count differs from the dataset training contract")
|
| 167 |
+
accumulation = int(cfg.trainer.gradient_accumulation_steps)
|
| 168 |
+
ds_accumulation = OmegaConf.select(launch, "deepspeed_config.gradient_accumulation_steps")
|
| 169 |
+
if ds_accumulation is not None and int(ds_accumulation) != accumulation:
|
| 170 |
+
raise ValueError("Accelerate and trainer gradient accumulation differ")
|
| 171 |
+
batch = int(cfg.datasets.vla_data.per_device_batch_size)
|
| 172 |
+
if min(world_size, accumulation, batch) < 1:
|
| 173 |
+
raise ValueError("World size, accumulation and batch size must be positive")
|
| 174 |
+
configured_manifest = OmegaConf.select(cfg, "datasets.vla_data.valid_anchor_manifest_path")
|
| 175 |
+
manifest = args.manifest or (Path(configured_manifest) if configured_manifest else None)
|
| 176 |
+
prepare_manifest = manifest is None
|
| 177 |
+
if prepare_manifest and args.start == "resume":
|
| 178 |
+
raise ValueError("Exact resume requires the parent run's manifest; use --manifest")
|
| 179 |
+
manifest = output / "proposal" / "manifest.json" if prepare_manifest else _file(manifest, "manifest")
|
| 180 |
+
if configured_manifest and Path(configured_manifest).expanduser().resolve() != manifest:
|
| 181 |
+
raise ValueError("--manifest differs from the config manifest")
|
| 182 |
+
base = args.base_policy.expanduser().resolve()
|
| 183 |
+
if args.start == "resume":
|
| 184 |
+
checkpoint, _ = _validate_resume_checkpoint(base, world_size)
|
| 185 |
+
else:
|
| 186 |
+
checkpoint = _file(base / "pytorch_model.pt" if base.is_dir() else base, "base-policy model")
|
| 187 |
+
cfg.trainer.pretrained_checkpoint = str(checkpoint) if args.start == "weights" else None
|
| 188 |
+
cfg.trainer.resume_accelerate_checkpoint = str(base) if args.start == "resume" else None
|
| 189 |
+
cfg.trainer.is_resume = args.start == "resume"
|
| 190 |
+
modes = list(args.mode)
|
| 191 |
+
for mode in modes:
|
| 192 |
+
if (output / "runs" / mode).exists():
|
| 193 |
+
raise ValueError(f"Training output already exists: {output / 'runs' / mode}; choose a fresh --output")
|
| 194 |
+
commands = []
|
| 195 |
+
cfg.datasets.vla_data.valid_anchor_manifest_path = str(manifest)
|
| 196 |
+
# Materialize all paths once so every arm and subprocess uses the same config.
|
| 197 |
+
config_text = OmegaConf.to_yaml(cfg, resolve=True)
|
| 198 |
+
config = output / "config.yaml"
|
| 199 |
+
if prepare_manifest:
|
| 200 |
+
commands.append([sys.executable, "-m", "retime.prepare", "--config", str(config), "--output", str(manifest)])
|
| 201 |
+
|
| 202 |
+
def script(name: str, *arguments: str) -> None:
|
| 203 |
+
module = {
|
| 204 |
+
"build_residual_temporal_proposal.py": "retime.commands.proposal",
|
| 205 |
+
"build_shuffle_temporal_proposal.py": "retime.commands.shuffle",
|
| 206 |
+
}[name]
|
| 207 |
+
commands.append([sys.executable, "-m", module, *map(str, arguments)])
|
| 208 |
+
|
| 209 |
+
residual = output / "proposal" / "retime.json"
|
| 210 |
+
shuffled = output / "proposal" / "shuffle.json"
|
| 211 |
+
if any(mode != "uniform" for mode in modes):
|
| 212 |
+
script("build_residual_temporal_proposal.py", "--config", config, "--checkpoint", checkpoint,
|
| 213 |
+
"--manifest", manifest, "--cache-dir", output / "cache",
|
| 214 |
+
"--output", residual, "--grid-size", args.k, "--alpha", args.alpha, "--epsilon", args.epsilon)
|
| 215 |
+
if "shuffle" in modes:
|
| 216 |
+
script("build_shuffle_temporal_proposal.py", "--manifest", manifest,
|
| 217 |
+
"--residual-proposal", residual, "--permutation-seed", args.shuffle_seed,
|
| 218 |
+
"--output", shuffled)
|
| 219 |
+
training = _file(root / "starVLA" / "training" / "train_starvla.py", "training backend")
|
| 220 |
+
for mode in modes:
|
| 221 |
+
proposal = {"uniform": None, "retime": str(residual), "shuffle": str(shuffled)}[mode]
|
| 222 |
+
overrides = {
|
| 223 |
+
"run_id": mode,
|
| 224 |
+
"run_root_dir": str(output / "runs"),
|
| 225 |
+
"output_dir": str(output / "runs" / mode),
|
| 226 |
+
"datasets.vla_data.valid_anchor_manifest_path": str(manifest),
|
| 227 |
+
"datasets.vla_data.temporal_proposal_path": proposal,
|
| 228 |
+
"trainer.is_resume": args.start == "resume",
|
| 229 |
+
"trainer.allow_model_only_resume": False,
|
| 230 |
+
"trainer.resume_accelerate_checkpoint": str(base) if args.start == "resume" else None,
|
| 231 |
+
"trainer.pretrained_checkpoint": str(checkpoint) if args.start == "weights" else None,
|
| 232 |
+
}
|
| 233 |
+
commands.append([
|
| 234 |
+
sys.executable, "-m", "accelerate.commands.launch", "--config_file", str(launch_config),
|
| 235 |
+
"--num_processes", str(world_size), str(training), "--config_yaml", str(config),
|
| 236 |
+
*[f"--{key}={json.dumps(value)}" for key, value in overrides.items()],
|
| 237 |
+
])
|
| 238 |
+
return {
|
| 239 |
+
"backend_root": str(root), "output": str(output), "base_policy": str(base),
|
| 240 |
+
"start": args.start, "alpha": args.alpha, "K": args.k, "modes": modes,
|
| 241 |
+
"global_batch_size": batch * world_size * accumulation,
|
| 242 |
+
"config_text": config_text,
|
| 243 |
+
"commands": commands,
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def main(argv: list[str] | None = None) -> int:
|
| 248 |
+
arguments = parser()
|
| 249 |
+
args = arguments.parse_args(argv)
|
| 250 |
+
try:
|
| 251 |
+
plan = build_plan(args)
|
| 252 |
+
except (ValueError, OSError, ImportError, KeyError) as error:
|
| 253 |
+
arguments.error(str(error))
|
| 254 |
+
print(json.dumps({key: value for key, value in plan.items() if key != "config_text"}, indent=2), flush=True)
|
| 255 |
+
if args.dry_run:
|
| 256 |
+
return 0
|
| 257 |
+
output = Path(plan["output"])
|
| 258 |
+
output.mkdir(parents=True, exist_ok=True)
|
| 259 |
+
(output / "proposal").mkdir(exist_ok=True)
|
| 260 |
+
(output / "config.yaml").write_text(plan["config_text"], encoding="utf-8")
|
| 261 |
+
(output / "plan.json").write_text(json.dumps(plan, indent=2) + "\n", encoding="utf-8")
|
| 262 |
+
env = os.environ.copy()
|
| 263 |
+
env["PYTHONPATH"] = os.pathsep.join(filter(None, [plan["backend_root"], env.get("PYTHONPATH")]))
|
| 264 |
+
env.setdefault("WANDB_MODE", "offline")
|
| 265 |
+
env["PYTHONUNBUFFERED"] = "1"
|
| 266 |
+
for command in plan["commands"]:
|
| 267 |
+
subprocess.run(command, cwd=plan["backend_root"], env=env, check=True)
|
| 268 |
+
return 0
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
if __name__ == "__main__":
|
| 272 |
+
raise SystemExit(main())
|
real_robot/source/retime/commands/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""ReTime proposal-generation commands."""
|