mmmmmcyaaa commited on
Commit
8101696
·
verified ·
1 Parent(s): fda0566

Add Xtrainer2 real robot source and Chinese handoff guide

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +1 -0
  2. real_robot/AGENTS.md +10 -0
  3. real_robot/README.md +72 -0
  4. real_robot/SOURCE_FILES.json +909 -0
  5. real_robot/source/LICENSE +24 -0
  6. real_robot/source/NOTICE +40 -0
  7. real_robot/source/README.md +222 -0
  8. real_robot/source/UPSTREAM.md +29 -0
  9. real_robot/source/configs/deepspeed_florence_8gpu.yaml +23 -0
  10. real_robot/source/configs/florence_xtrainer2_pack_phone_residual_20ep_to_60ep.yaml +101 -0
  11. real_robot/source/configs/florence_xtrainer2_pack_phone_residual_resume_smoke.yaml +104 -0
  12. real_robot/source/configs/florence_xtrainer2_pack_phone_smoke.yaml +117 -0
  13. real_robot/source/configs/florence_xtrainer2_pack_phone_uniform.yaml +116 -0
  14. real_robot/source/configs/florence_xtrainer2_pick_bottle_rel_aug_smoke.yaml +119 -0
  15. real_robot/source/configs/florence_xtrainer2_pick_bottle_rel_aug_uniform.yaml +123 -0
  16. real_robot/source/deployment/__init__.py +0 -0
  17. real_robot/source/deployment/model_server/__init__.py +0 -0
  18. real_robot/source/deployment/model_server/policy_norm_processor.py +538 -0
  19. real_robot/source/deployment/model_server/policy_wrapper.py +280 -0
  20. real_robot/source/deployment/model_server/server_policy.py +79 -0
  21. real_robot/source/deployment/model_server/tools/__init__.py +0 -0
  22. real_robot/source/deployment/model_server/tools/image_tools.py +123 -0
  23. real_robot/source/deployment/model_server/tools/msgpack_numpy.py +59 -0
  24. real_robot/source/deployment/model_server/tools/websocket_policy_client.py +75 -0
  25. real_robot/source/deployment/model_server/tools/websocket_policy_server.py +160 -0
  26. real_robot/source/deployment/model_server/xtrainer2_command.py +93 -0
  27. real_robot/source/examples/Xtrainer2/INFERENCE.md +4 -0
  28. real_robot/source/examples/Xtrainer2/README.md +26 -0
  29. real_robot/source/examples/Xtrainer2/ROBOT.md +18 -0
  30. real_robot/source/examples/Xtrainer2/train_files/build_pack_phone_support.py +86 -0
  31. real_robot/source/examples/Xtrainer2/train_files/build_pick_bottle_support.py +87 -0
  32. real_robot/source/examples/Xtrainer2/train_files/data_registry/data_config.py +179 -0
  33. real_robot/source/examples/Xtrainer2/train_files/modality.json +26 -0
  34. real_robot/source/examples/Xtrainer2/train_files/run_xtrainer2_policy_server.sh +111 -0
  35. real_robot/source/examples/Xtrainer2/train_files/run_xtrainer2_train.sh +98 -0
  36. real_robot/source/licenses/Diffusion-Policy-LICENSE +21 -0
  37. real_robot/source/licenses/Isaac-GR00T-LICENSE +190 -0
  38. real_robot/source/licenses/MODIFIED_FILES.txt +17 -0
  39. real_robot/source/licenses/OpenVLA-LICENSE +21 -0
  40. real_robot/source/licenses/X-VLA-LICENSE +201 -0
  41. real_robot/source/licenses/msgpack-numpy-LICENSE +35 -0
  42. real_robot/source/pyproject.toml +81 -0
  43. real_robot/source/requirements-qwen.txt +9 -0
  44. real_robot/source/requirements-xtrainer2.txt +10 -0
  45. real_robot/source/requirements.txt +34 -0
  46. real_robot/source/retime/__init__.py +6 -0
  47. real_robot/source/retime/__main__.py +4 -0
  48. real_robot/source/retime/checkpoint.py +19 -0
  49. real_robot/source/retime/cli.py +272 -0
  50. 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."""