5 Commits
202 changed files with 2784 additions and 319539 deletions
View File
@@ -1 +0,0 @@
{"reason":"idle timeout","timestamp":1776932392614}
@@ -1 +0,0 @@
31521
@@ -1,48 +0,0 @@
<h2>新任务场景草图:双臂空中穿环插入</h2>
<p class="subtitle">保持双 Diana + 同一张桌子;左侧放环状木块,右侧放长条木块,先各自抓起,再在空中完成穿孔插入。</p>
<div class="split">
<div class="mockup">
<div class="mockup-header">俯视布局(初始)</div>
<div class="mockup-body">
<div style="position:relative;height:340px;background:#f3f4f6;border-radius:16px;border:1px solid #d1d5db;overflow:hidden;">
<div style="position:absolute;left:12%;top:18%;width:22%;height:64%;border:2px dashed #60a5fa;border-radius:18px;background:rgba(96,165,250,.08);display:flex;align-items:flex-start;justify-content:center;padding-top:8px;font-weight:700;">左臂采样区(环)</div>
<div style="position:absolute;right:12%;top:18%;width:22%;height:64%;border:2px dashed #f59e0b;border-radius:18px;background:rgba(245,158,11,.08);display:flex;align-items:flex-start;justify-content:center;padding-top:8px;font-weight:700;">右臂采样区(长条)</div>
<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:120px;height:120px;border:10px solid #ef4444;background:white;box-sizing:border-box;display:flex;align-items:center;justify-content:center;font-weight:700;color:#ef4444;">环块</div>
<div style="position:absolute;left:72%;top:50%;transform:translate(-50%,-50%) rotate(0deg);width:110px;height:22px;background:#10b981;border:2px solid #065f46;border-radius:6px;display:flex;align-items:center;justify-content:center;color:white;font-size:12px;font-weight:700;">长条</div>
<div style="position:absolute;left:50%;top:12%;transform:translateX(-50%);padding:6px 10px;background:#111827;color:white;border-radius:999px;font-size:12px;">桌面中心 / 空中插入区上方</div>
</div>
</div>
</div>
<div class="mockup">
<div class="mockup-header">任务阶段</div>
<div class="mockup-body">
<div class="section">
<div class="label">Phase 1</div>
<h3>左右臂分别接近目标</h3>
<p>左臂只抓环状木块,右臂只抓长条木块。两物体初始姿态固定,只随机平面位置。</p>
</div>
<div class="section">
<div class="label">Phase 2</div>
<h3>同时抓起并离桌</h3>
<p>两个物体都需要离开桌面后,才允许触发最终成功奖励。</p>
</div>
<div class="section">
<div class="label">Phase 3</div>
<h3>空中对齐并插入</h3>
<p>在桌面上方的会合区完成对齐,让长条沿自身长轴穿过环孔;一旦穿孔成功即判成功。</p>
</div>
</div>
</div>
</div>
<div class="section">
<h3>奖励草案(总分 5</h3>
<div class="cards">
<div class="card"><div class="card-body"><h3>+1</h3><p>左臂碰到环块</p></div></div>
<div class="card"><div class="card-body"><h3>+1</h3><p>右臂碰到长条</p></div></div>
<div class="card"><div class="card-body"><h3>+1</h3><p>环块离桌</p></div></div>
<div class="card"><div class="card-body"><h3>+1</h3><p>长条离桌</p></div></div>
<div class="card"><div class="card-body"><h3>+1</h3><p>两者离桌时,长条穿过环孔</p></div></div>
</div>
</div>
@@ -1 +0,0 @@
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh"><p class="subtitle">Continuing in terminal...</p></div>
-39
View File
@@ -1,39 +0,0 @@
# Repository Guidelines
## Project Structure & Module Organization
- `roboimi/` is the main package: simulation code in `roboimi/envs/`, assets/helpers in `roboimi/assets/`, VLA models/config in `roboimi/vla/`, and runnable scripts in `roboimi/demos/` plus `roboimi/demos/vla_scripts/`.
- `tests/` holds `unittest` regressions for training, rollout, dataset loading, and asset resolution.
- `docs/superpowers/` stores design specs and plans.
- `docs/experiment-guide.md` store how beginning experiment
- `runs/`, `outputs/`, `swanlog/`, `.worktrees/`, and `checkpoints/` are generated experiment artifacts, not source modules.
## Build, Test, and Development Commands
- `conda env create -f environment.yml && conda activate roboimi` — create the standard environment.
- `pip install -e .` — editable install.
- `python -m unittest discover -s tests -p 'test_*.py'` — run all tests.
- `python -m unittest tests.test_train_vla_rollout_validation -v` — run one focused test module.
- `python roboimi/vla/scripts/calculate_stats.py` — regenerate normalization stats.
- `python roboimi/demos/vla_scripts/train_vla.py train.max_steps=1000` — quick training smoke test.
- `python roboimi/demos/vla_scripts/eval_vla.py eval.ckpt_path=checkpoints/vla_model_best.pt eval.num_episodes=5` — evaluate a checkpoint.
## Coding Style & Naming Conventions
- Use Python, 4-space indentation, and PEP 8style naming.
- Prefer `snake_case` for files, functions, variables, and Hydra keys; use `CamelCase` for classes.
- Match existing Hydra namespaces such as `train.*`, `eval.*`, and `agent.*`.
- Keep diffs focused and imports tidy. Experiment names usually encode host/GPU, e.g. `...-l20g3-...`, `...-5880g1-...`, or `...-5090-...`.
## Testing Guidelines
- Tests use `unittest` and `unittest.mock`.
- Name files `tests/test_*.py` and classes `*Test`.
- Add direct regression coverage for changes to training, rollout, config, or dataset code paths.
- No strict coverage target is defined, but changed behavior should be tested and affected suites should pass before review.
## Commit & Pull Request Guidelines
- Follow existing prefixes: `feat`, `fix`, `chore`, `docs`, `debug`, optionally with scope, e.g. `feat(eval): export rollout video timing`.
- Keep commit messages imperative and specific.
- PRs should state the goal, touched configs/scripts, exact test commands, and dataset/checkpoint assumptions. Attach visual artifacts only when outputs change.
## Experiment Host Reference
- **Local workstation (`droid-z790eagleax`)** — local shell; **1× RTX 5090 32 GB**; env: `/home/droid/.conda/envs/roboimi/bin/python`.
- **5880 node (`droid-System-Product-Name`)** — username: `droid`; IP: `100.73.14.65`; connect with `ssh droid@100.73.14.65`; **2× RTX 5880 Ada 48 GB**; env: `/home/droid/miniforge3/envs/roboimi/bin/python`; trusted tailnet host.
- **L20 node (`droid-G5500-V7`)** — username: `droid`; IP: `100.119.99.14`; connect with `ssh droid@100.119.99.14`; **8× NVIDIA L20 46 GB**; env: `/home/droid/miniforge3/envs/roboimi/bin/python`; store large experiment data under `/data`.
-126
View File
@@ -1,126 +0,0 @@
Mon Mar 30 13:35:31 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 13:35:31 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 13:39:46 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 13:39:46 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 14:30:24 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 14:30:24 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 14:41:26 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 14:41:26 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 15:21:19 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 15:21:19 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 20:58:52 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 20:59:05 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Mon Mar 30 22:05:37 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:00:10 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:00:10 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:03:41 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:03:41 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:05:09 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:05:09 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:06:00 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:06:00 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:09:24 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:09:24 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:14:36 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:14:36 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:20:44 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:23:07 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Tue Mar 31 16:29:49 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 11:55:22 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 11:55:22 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Thu Apr 23 12:43:50 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Sun May 24 21:50:49 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Sun May 24 21:51:10 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Sun May 24 21:53:08 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
Sun May 24 21:53:28 2026
WARNING: OpenGL error 0x502 in or before mjr_makeContext
-66
View File
@@ -1,66 +0,0 @@
"""Capture first and last (success) frames from front camera for both sim tasks."""
import os
import time
import numpy as np
import cv2
from roboimi.envs.double_pos_ctrl_env import make_sim_env
from roboimi.demos.diana_policy import TestPickAndTransferPolicy
from roboimi.demos.diana_air_insert_policy import TestAirInsertPolicy
from roboimi.utils.act_ex_utils import sample_transfer_pose, sample_air_insert_socket_peg_state
OUTPUT_DIR = "sim_frames"
IMG_SIZE = (256, 256)
os.makedirs(OUTPUT_DIR, exist_ok=True)
def capture_task(task_name, policy, task_state, episode_len):
env = make_sim_env(task_name, headless=True)
env.reset(task_state)
time.sleep(1)
env._update_camera_images_sync()
first_frame = cv2.resize(env.front, IMG_SIZE)
last_success_frame = None
max_reward = 0
for step in range(episode_len):
action = policy.predict(task_state, step)
env.step(action)
env._update_camera_images_sync()
if env.rew is not None and env.rew >= max_reward:
max_reward = env.rew
if env.rew == env.max_reward:
last_success_frame = cv2.resize(env.front, IMG_SIZE)
if last_success_frame is None:
last_success_frame = cv2.resize(env.front, IMG_SIZE)
print(f" [WARN] {task_name}: max_reward={max_reward}/{env.max_reward}, using last frame")
else:
print(f" {task_name}: success! max_reward={max_reward}")
return first_frame, last_success_frame
def main():
# Transfer task (episode_len=700, policy covers full range)
print("Running sim_transfer...")
task_state_transfer = sample_transfer_pose()
policy_transfer = TestPickAndTransferPolicy(inject_noise=False)
first_t, last_t = capture_task('sim_transfer', policy_transfer, task_state_transfer, 700)
cv2.imwrite(os.path.join(OUTPUT_DIR, "transfer_front_first.png"), first_t)
cv2.imwrite(os.path.join(OUTPUT_DIR, "transfer_front_last.png"), last_t)
# Air insert / peg-in-socket (policy EPISODE_END_T=600)
print("Running sim_air_insert_socket_peg...")
task_state_insert = sample_air_insert_socket_peg_state()
policy_insert = TestAirInsertPolicy(inject_noise=False)
first_i, last_i = capture_task('sim_air_insert_socket_peg', policy_insert, task_state_insert, 600)
cv2.imwrite(os.path.join(OUTPUT_DIR, "air_insert_front_first.png"), first_i)
cv2.imwrite(os.path.join(OUTPUT_DIR, "air_insert_front_last.png"), last_i)
print(f"\nDone! Images saved to {OUTPUT_DIR}/")
if __name__ == '__main__':
main()
-498
View File
@@ -1,498 +0,0 @@
# 实验操作指南
## 0. 适用范围与当前起点
这份文档面向 **当前 `main` 分支** 的通用训练 / 验证 / 评估流程,不绑定某一条历史实验分支。
当前默认 Hydra 起点:
- agent`resnet_transformer`
- data`simpe_robot_dataset`
- eval`eval`
当前仓库里常见可选 agent 配置包括:
- `resnet_transformer`
- `resnet_diffusion`
- `resnet_gr00t_dit`
- `resnet_imf_attnres`
- `resnet_imf_attnres_multitoken`
- `siglip2_imf_attnres`
- `lewm_imf_attnres`
说明:
- `experiment_suites/` 下的历史目录仍然可以作为参考,但**正文以当前 `main` 的通用流程为准**。
- 如果你只是想快速开始,优先从默认 `resnet_transformer` 配置出发,再按实验需求替换 agent。
---
## 1. 关键文件与入口
| 路径 | 作用 |
| --- | --- |
| `roboimi/demos/vla_scripts/train_vla.py` | 主训练入口;负责数据集、checkpoint、`val/loss`、held-out action MSE、训练期 rollout 验证、SwanLab |
| `roboimi/demos/vla_scripts/eval_vla.py` | 单次 rollout / 离线评估入口;支持 headless、summary、trajectory image、video artifact、多进程并行 rollout |
| `roboimi/vla/conf/config.yaml` | 全局 Hydra 训练配置 |
| `roboimi/vla/conf/data/simpe_robot_dataset.yaml` | 默认数据集配置;数据路径、相机名、图像 resize 都在这里 |
| `roboimi/vla/conf/eval/eval.yaml` | eval 默认配置;`eval.ckpt_path``eval.num_episodes`、artifact 开关、多进程 rollout 配置都在这里 |
| `roboimi/vla/conf/agent/*.yaml` | 可选 agent 配置集合 |
| `roboimi/vla/data/simpe_robot_dataset.py` | HDF5 懒加载数据集;支持 `episode_indices` 过滤与 `available_episode_indices` 元信息 |
| `roboimi/vla/scripts/calculate_stats.py` | 重算 `dataset_stats.pkl` |
| `experiment_suites/` | 历史实验目录;可参考,但不作为当前主流程的唯一事实来源 |
---
## 2. 三台机器与环境
| 机器 | GPU | Python | 常用数据集路径 | 备注 |
| --- | --- | --- | --- | --- |
| 本地 `droid-z790eagleax` | 1× RTX 5090 32GB | `/home/droid/.conda/envs/roboimi/bin/python` | `/home/droid/project/diana_sim/sim_transfer` | 适合 smoke、单条主跑、本地调参 |
| 5880 节点 `100.73.14.65` | 2× RTX 5880 Ada 48GB | `/home/droid/miniforge3/envs/roboimi/bin/python` | `/home/droid/sim_dataset/sim_transfer` | 适合 2 条并行主跑 |
| L20 节点 `100.119.99.14` | 8× NVIDIA L20 46GB | `/home/droid/miniforge3/envs/roboimi/bin/python` | `/data/simtransfer/current` | 适合大规模 grid;建议数据与 run 放 `/data` |
连接:
- 5880`ssh droid@100.73.14.65`
- L20`ssh droid@100.119.99.14`
说明:
- repo / worktree 路径可能会随当前 checkout 或同步目录变化,**以你当前机器上的实际 repo 路径为准**。
- 如果训练目录会被 Hydra 自动切走,建议显式设置 `hydra.run.dir=...` 以固定输出目录。
---
## 3. 训练流怎么走
`train_vla.py` 当前主流程如下:
1. 读取 Hydra 配置并打印完整 cfg
2. 根据配置确定数据集图像 resize:
- 默认用 `data.image_resize_shape`
- 如果 `agent.vision_backbone.dataset_image_resize_shape` 存在,则优先用 backbone 覆盖值
3. 通过 `build_train_val_datasets()` 构建 dataset / train dataset / val dataset
- 若设置 `train.val_episode_indices`:按显式 episode 切出 held-out val
- 否则按 `train.val_split` 做随机划分
4.`DataLoader` 建 train / val loader
5.`dataset_dir/dataset_stats.pkl` 读取归一化统计
6. 实例化当前指定的 agent`instantiate(cfg.agent, dataset_stats=...)`
7. 可选加载:
- `train.pretrained_ckpt`:微调起点
- `train.resume_ckpt`:断点续训(支持显式路径或 `auto`
8. 建 optimizer 与 scheduler
9. 训练循环里按 `log_freq` 记录 train loss / lr
10.`save_freq` 保存 `checkpoints/vla_model_step_*.pt`,并在有 val loader 时计算 `val/loss`
11. 若设置了显式 held-out val,并且 `train.action_mse_val_freq_epochs > 0`,则按 epoch 计算 held-out `action MSE`
12.`train.rollout_val_freq_epochs` 跑训练期 rollout 验证
13. 最后写:
- `checkpoints/vla_model_best.pt`
- `checkpoints/vla_model_final.pt`
当前 best model 选择逻辑:
- **第一次拿到 rollout reward 之前**:先用 `val_loss`(或 train loss 回退)挑 best
- **第一次 rollout 之后**:优先用 `rollout_avg_reward` 挑 best
输出目录:
- 当前代码实际输出写到 **Hydra runtime output dir**
- checkpoint 默认在:`<hydra_output_dir>/checkpoints/`
- 如果不显式指定 `hydra.run.dir`Hydra 会自动生成目录
---
## 4. 验证流怎么走
### 4.1 常规 `val/loss`
常规验证有两种来源:
1. **随机划分验证集**
-`train.val_split > 0`
- 训练脚本会在保存 checkpoint 时计算 `val/loss`
2. **显式 held-out episode 验证集**
-`train.val_episode_indices=[...]`
- 训练集 = 全部 episode - held-out episode
- 验证集 = held-out episode
- checkpoint 保存时同样会计算 `val/loss`
说明:
-`train.val_split=0.0` 且未设置 `train.val_episode_indices`,则不会有 `val/loss`
### 4.2 held-out action MSE
如果想对固定 held-out episode 做更稳定、可复现的动作预测误差验证,推荐使用:
- `train.val_split=0.0`
- `train.val_episode_indices=[100]`(或你指定的 episode 列表)
- `train.action_mse_val_freq_epochs=1`
当前 `main` 中这套逻辑会:
- 每隔 `action_mse_val_freq_epochs` 个 epoch
- 对 held-out val loader 调 `agent.predict_action_chunk(...)`
- 与 batch 中的 `action` 计算 masked MSE
- 如果存在 `action_is_pad`,会自动 mask 掉 padding 部分
日志 key
- 控制台 / `train_vla.log``held-out action MSE`
- SwanLab`val/action_mse`
重要约束:
- `train.action_mse_val_freq_epochs > 0` **必须搭配** `train.val_episode_indices`
- 如果只设了 `action_mse_val_freq_epochs` 而没有设 `val_episode_indices`,训练会直接报错
### 4.3 rollout 验证
训练内 rollout 验证由:
- `train_vla.py -> run_rollout_validation() -> eval_vla._run_eval()`
当前训练内 rollout 会强制:
- `headless=true`
- `verbose_action=false`
- `record_video=false`
- `save_trajectory_image=true`
- `trajectory_image_camera_name=front`
- `save_summary_json=true`
当前这套路径是**配置驱动**的:
- `train.rollout_device`:默认跟随 `train.device`
- `train.rollout_num_workers`:默认 `null`
- 当 rollout 设备是 CPU 时,自动退化为 `1`
- 当 rollout 设备是 CUDA 时,自动推断为 `min(train.rollout_num_episodes, 8)`
- `train.rollout_cuda_devices`:默认 `null`,等价于逻辑 GPU `[0]`
- `train.rollout_response_timeout_s`
- `train.rollout_server_startup_timeout_s`
所以现在:
- 训练在 CUDA 上时,训练期 rollout 默认也会走 GPU
- `rollout_num_workers > 1` 时,会走并行 rollout
- 可以是 **单 GPU 多 worker 共用一个 inference server**
- 也可以是 **多 GPU 多 server 分摊 worker**
训练内 rollout artifact 默认落到:
- `<hydra_output_dir>/rollout_artifacts/<checkpoint_stem>/`
常见文件:
- `rollout_summary.json`
- `rollout_front_ep01_trajectory.png` ...
日志重点看:
- `Epoch X rollout 平均奖励`
- `最佳模型已更新`
---
## 5. 数据集加载与 `val_episode_indices` 机制
### 5.1 数据集格式
`SimpleRobotDataset` 读取 `dataset_dir` 下的 `*.hdf5` / `episode_*.hdf5`,每个 episode 文件至少要有:
- `action`
- `observations/qpos`
- `observations/images/{cam_name}`
默认数据配置中的相机:
- `r_vis`
- `top`
- `front`
### 5.2 懒加载行为
`roboimi/vla/data/simpe_robot_dataset.py` 是按帧懒加载,不会一次性把整套 HDF5 全读进内存。
它会:
- 扫描目录下的 HDF5 文件
- 在 worker 内做 HDF5 文件句柄 LRU 缓存
- 根据文件名中的 `episode_XXX` 建立 `available_episode_indices`
### 5.3 `val_episode_indices` 怎么切
`build_train_val_datasets()` 的逻辑是:
1. 先 instantiate 一次完整 dataset
2. 读取 `dataset.available_episode_indices`
3. 检查 `train.val_episode_indices` 是否都存在
4.`episode_indices=` 再各 instantiate 一次:
- train dataset = 全部 episode - held-out episode
- val dataset = 只包含 held-out episode
因此:
- `train.val_episode_indices=[100]` 的意思是把 `episode_100.hdf5` 整个拿去做 held-out val
- 如果 episode 不存在,会直接报错
- 如果你把所有 episode 都塞进 `val_episode_indices`,也会直接报错,因为训练集会变空
### 5.4 图像 resize 与返回字段
dataset 侧 resize 默认来自:
- `data.image_resize_shape`
- 如果 backbone 额外覆盖,则优先 `agent.vision_backbone.dataset_image_resize_shape`
当前通用 batch 返回字段包括:
- `observation.state`
- `observation_is_pad`
- `observation.<cam>`
- `action`
- `action_is_pad`
- `task`
### 5.5 统计文件
训练和推理都默认依赖 `dataset_stats.pkl`。数据集更新后需要重算:
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/vla/scripts/calculate_stats.py \
--dataset_dir /home/droid/project/diana_sim/sim_transfer
```
远端只要把:
- Python 路径
- `--dataset_dir`
替换成对应机器上的实际路径即可。
---
## 6. SwanLab 行为
当前默认配置里:
- `train.use_swanlab=false`
如果要开启,通常显式设置:
- `train.use_swanlab=true`
- `train.swanlab_project=roboimi-vla`
- `train.swanlab_run_name=<run_name>`
`train_vla.py` 当前会记录:
- 初始化时上传 `train` / `data` / `agent` 三段 config
- 训练中:
- `train/loss`
- `train/lr`
- `train/best_loss`
- `train/step`
- checkpoint 验证时:
- `val/loss`
- held-out 数值验证时:
- `val/action_mse`
- rollout 验证时:
- `rollout/avg_reward`
- `rollout/epoch`
- 训练结束时:
- `final/checkpoint_path`
- `final/best_checkpoint_path`
训练期 rollout 生成的前视图轨迹 PNG 会 best-effort 上传到 SwanLab;上传失败只会 warning,不会让训练中断。
---
## 7. 并行 rollout 说明
### 7.1 这套能力现在在哪里
当前主仓库已经**内置**多进程并行 rollout 能力,入口就是:
- `roboimi/demos/vla_scripts/eval_vla.py`
控制参数:
- `eval.num_workers`
- `eval.cuda_devices`
语义:
- `eval.num_workers`:环境 worker 数,按 episode 切分
- `eval.cuda_devices`:推理 server 绑定到哪些逻辑 GPU
### 7.2 两种常见模式
1. **单机单卡,多 worker 共用同一张 GPU**
- 典型:5090 只有 1 卡,但想让 4 个 rollout worker 并行跑环境
- 形式:`eval.device=cuda eval.num_workers=4 'eval.cuda_devices=[0]'`
- 这时是 **1 个 CUDA inference server + 4 个 env worker**
2. **单机多卡,多 server 分摊 worker**
- 典型:5880 / L20 有多卡
- 形式:`eval.device=cuda eval.num_workers=8 'eval.cuda_devices=[0,1]'`
- worker 会按 round-robin 分到多个 server 上
### 7.3 操作约束
- 并行 rollout 依赖 **多进程 eval 路径**,不是 `train.num_workers`
- `train.num_workers` 是 DataLoader worker,和 rollout 并行不是一回事
- `eval.num_workers > 1` 时必须 `eval.headless=true`
- worker 数会自动 cap 到 `eval.num_episodes`
- 多 worker 时**不支持**同时导出:
- `eval.record_video=true`
- `eval.save_trajectory=true`
- `eval.save_trajectory_npz=true`
- `eval.save_trajectory_image=true` 可以开,适合并行 reward + 定性检查一起做
---
## 8. 常用命令模板
下面的模板都以 **当前 `main` 通用流程** 为准。远端机器只需要替换:
- Python 路径
- repo 路径
- `data.dataset_dir`
- `hydra.run.dir`
### 8.1 本地 smoke train
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda \
train.batch_size=8 \
train.max_steps=1000 \
train.num_workers=4 \
train.save_freq=200 \
hydra.run.dir=/home/droid/project/roboimi/runs/smoke-resnet-transformer
```
### 8.2 通用训练模板
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda \
train.batch_size=16 \
train.lr=0.0001 \
train.max_steps=100000 \
train.num_workers=4 \
train.save_freq=2000 \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.swanlab_run_name=<run_name> \
hydra.run.dir=/home/droid/project/roboimi/runs/<run_name>
```
### 8.3 带 held-out episode 验证的训练模板
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda \
train.batch_size=16 \
train.lr=0.0001 \
train.max_steps=100000 \
train.num_workers=4 \
train.val_split=0.0 \
'train.val_episode_indices=[100]' \
train.action_mse_val_freq_epochs=1 \
train.rollout_val_freq_epochs=5 \
train.rollout_num_episodes=10 \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.swanlab_run_name=<run_name> \
hydra.run.dir=/home/droid/project/roboimi/runs/<run_name>
```
### 8.4 断点续训模板
`train_vla.py` 支持通过 CLI override 使用 `train.resume_ckpt`
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda \
train.resume_ckpt=auto \
hydra.run.dir=/home/droid/project/roboimi/runs/<run_name>
```
也可以把 `train.resume_ckpt` 换成显式 checkpoint 路径。
### 8.5 单次离线评估
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/eval_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda eval.device=cuda \
eval.ckpt_path=/home/droid/project/roboimi/runs/<run_name>/checkpoints/vla_model_best.pt \
eval.num_episodes=10 \
eval.headless=true \
eval.verbose_action=false \
eval.save_summary_json=true \
eval.save_trajectory_image=true \
eval.trajectory_image_camera_name=front \
eval.artifact_dir=/tmp/roboimi_eval_front
```
### 8.6 并行离线评估
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/eval_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda eval.device=cuda \
eval.ckpt_path=/home/droid/project/roboimi/runs/<run_name>/checkpoints/vla_model_best.pt \
eval.num_episodes=10 \
eval.num_workers=4 \
'eval.cuda_devices=[0]' \
eval.headless=true \
eval.verbose_action=false \
eval.save_summary_json=true \
eval.save_trajectory_image=true \
eval.trajectory_image_camera_name=front \
eval.artifact_dir=/tmp/roboimi_parallel_eval
```
### 8.7 训练内启用并行 GPU rollout
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=resnet_transformer \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda \
train.batch_size=16 \
train.lr=0.0001 \
train.max_steps=100000 \
train.num_workers=4 \
train.rollout_val_freq_epochs=5 \
train.rollout_num_episodes=10 \
train.rollout_device=cuda \
train.rollout_num_workers=4 \
'train.rollout_cuda_devices=[0]' \
train.rollout_validate_on_checkpoint=false \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.swanlab_run_name=<run_name> \
hydra.run.dir=/home/droid/project/roboimi/runs/<run_name>
```
### 8.8 重算统计文件
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/vla/scripts/calculate_stats.py \
--dataset_dir /home/droid/project/diana_sim/sim_transfer
```
### 8.9 监控日志
```bash
tail -f runs/<run_name>/train_vla.log
```
如果你显式设置了 `hydra.run.dir`,优先直接 tail 对应绝对路径下的日志文件。
---
## 9. 操作建议
- **优先固定 `hydra.run.dir`**,避免输出目录分散在 Hydra 自动生成路径里
- 如果只需要常规验证,使用:
- `train.val_split > 0`
- 如果需要稳定、可复现的 held-out 指标,使用:
- `train.val_split=0.0`
- `train.val_episode_indices=[...]`
- `train.action_mse_val_freq_epochs=1`
- `train.num_workers` 是 DataLoader worker,不等于 rollout 并行度
- rollout 并行优先看:
- `train.rollout_num_workers`
- `train.rollout_cuda_devices`
- `eval.num_workers`
- `eval.cuda_devices`
- 数据集更新后记得重算 `dataset_stats.pkl`
- 若同时设置了:
- `train.pretrained_ckpt`
- `train.resume_ckpt`
当前逻辑会优先走 `resume_ckpt`
- 建议 run name 中带上关键信息:
- agent / horizon / batch size / lr / host / gpu / date
- `experiment_suites/` 下的历史目录适合作为参考资料,但不要默认把某一条旧 suite 当作当前主流程的唯一标准
+471
View File
@@ -0,0 +1,471 @@
# feat-lewm-imf-fusion 实验操作指南
适用 worktree`/home/droid/project/roboimi/.worktrees/feat-lewm-imf-fusion`
## 0. 先记住当前常用 recipe
当前这条分支最常用的训练/验证配方,直接参考:
`experiment_suites/2026-04-21-lewm-fromscratch-old9-epoch50-roll5-val-20260421-153037/`
核心约定:
- agent`lewm_resnet_query_imf_attnres`
- from scratch`train.pretrained_ckpt=null``agent.lewm_pretrained_ckpt=null`
- 训练:`batch_size=32``lr=1e-4``max_steps=109350``save_freq=10000`
- 数值验证:`train.val_split=0.0` + `train.val_episode_indices=[100]`
- held-out numeric validation`train.action_mse_val_freq_epochs=1`
- rollout validation`train.rollout_val_freq_epochs=5``train.rollout_num_episodes=10`
- SwanLab`train.use_swanlab=true`project=`roboimi-vla`
---
## 1. 分支结构与关键文件
| 路径 | 作用 |
| --- | --- |
| `roboimi/demos/vla_scripts/train_vla.py` | 主训练入口;负责数据集、checkpoint、数值验证、训练期 rollout 验证、SwanLab |
| `roboimi/demos/vla_scripts/eval_vla.py` | 单次 rollout / 离线验证入口;支持 headless、summary、trajectory image/video artifact |
| `roboimi/vla/conf/config.yaml` | 全局 Hydra 配置;训练默认值都在这里 |
| `roboimi/vla/conf/eval/eval.yaml` | eval 默认配置;`eval.ckpt_path``eval.num_episodes`、artifact 开关都在这里 |
| `roboimi/vla/conf/agent/lewm_resnet_query_imf_attnres.yaml` | 本分支最常用 agentLeWM query fusion + IMF AttnRes head |
| `roboimi/vla/conf/backbone/lewm_resnet_query_fusion.yaml` | LeWM 多视角 ResNet query fusion backbone 配置 |
| `roboimi/vla/agent_imf.py` | `IMFVLAAgent` 实现;one-step IMF 推理、LeWM loss、LeWM 预训练组件加载 |
| `roboimi/vla/data/simpe_robot_dataset.py` | HDF5 懒加载数据集;也负责 `episode_indices` 过滤 |
| `roboimi/vla/scripts/calculate_stats.py` | 重算 `dataset_stats.pkl` |
| `experiment_suites/2026-04-21-lewm-fromscratch-old9-epoch50-roll5-val-20260421-153037/` | 当前最常用 suitemanifest、notes、launch log、local 启动脚本都在这里 |
补充:
- 本分支常用 run name 形如 `lewmimf-q08-ph08-ex08-emb384-l12-fromscratch-epoch50-step109350-5090g0-20260421-153037`
- `q08/ph16/ex08` 这类后缀分别对应 `agent.lewm_query_offsets``agent.pred_horizon``agent.num_action_steps`
---
## 2. 三台机器与环境
| 机器 | GPU | repo / worktree | Python | 常用数据集路径 |
| --- | --- | --- | --- | --- |
| 本地 `droid-z790eagleax` | 1× RTX 5090 32GB | `/home/droid/project/roboimi/.worktrees/feat-lewm-imf-fusion` | `/home/droid/.conda/envs/roboimi/bin/python` | `/home/droid/project/diana_sim/sim_transfer` |
| 5880 节点 `100.73.14.65` | 2× RTX 5880 Ada 48GB | `/home/droid/roboimi_suite_20260416_lewm_imf_fusion` | `/home/droid/miniforge3/envs/roboimi/bin/python` | `/home/droid/sim_dataset/sim_transfer` |
| L20 节点 `100.119.99.14` | 8× NVIDIA L20 46GB | `/data/roboimi_suite_20260416_lewm_imf_fusion` | `/home/droid/miniforge3/envs/roboimi/bin/python` | `/data/simtransfer/current` |
连接:
- 5880`ssh droid@100.73.14.65`
- L20`ssh droid@100.119.99.14`
经验规则:
- 本地 5090:适合单条 smoke / 小规模主跑 / 本地调参
- 5880:适合 2 条并行主跑
- L20:适合大 grid;数据和 run 建议都放 `/data`
---
## 3. 训练流怎么走
`train_vla.py` 的实际流程:
1. 读取 Hydra 配置并打印完整 cfg
2. 通过 `build_train_val_datasets()` 构建 train/val dataset
3.`DataLoader` 建 train/val loader
4.`dataset_dir/dataset_stats.pkl` 读取归一化统计
5. instantiate `IMFVLAAgent`
6. 可选加载:
- `train.pretrained_ckpt`
- `train.resume_ckpt`
- `agent.lewm_pretrained_ckpt`
7. 训练循环里按 `log_freq` 打 train loss / lr
8.`save_freq` 保存 `checkpoints/vla_model_step_*.pt`
9. 每个 epoch 结束时,按配置跑:
- held-out action MSE
- rollout validation
10. 最后写:
- `checkpoints/vla_model_best.pt`
- `checkpoints/vla_model_final.pt`
当前 best model 选择逻辑:
- **第一次拿到 rollout reward 之前**:先用 `val_loss`(或 train loss 回退)挑 best
- **第一次 rollout 之后**:优先用 `rollout_avg_reward` 挑 best
输出目录一般通过 `hydra.run.dir=...` 固定;否则 Hydra 自己生成。
---
## 4. 验证流怎么走
### 4.1 held-out 数值验证
当前常用做法不是随机切 `val_split`,而是:
- `train.val_split=0.0`
- `train.val_episode_indices=[100]`
- `train.action_mse_val_freq_epochs=1`
这样每个 epoch 结束都会在 `episode_100.hdf5` 上跑一次 `compute_action_mse_validation()`,日志 key 是:
- 控制台 / `train_vla.log``held-out action MSE`
- SwanLab`val/action_mse`
### 4.2 rollout 验证
当前训练内 rollout 验证由 `train_vla.py -> run_rollout_validation() -> eval_vla._run_eval()` 触发。
当前这条分支的常用训练内 rollout 约束是:
- `train.rollout_val_freq_epochs=5`
- `train.rollout_num_episodes=10`
- `train.rollout_validate_on_checkpoint=false`
- 强制 headless
- 强制 `verbose_action=false`
- 强制 `record_video=false`
- 强制 `save_trajectory_image=true`
- 强制 `trajectory_image_camera_name=front`
- 强制 `save_summary_json=true`
当前已经修正为**配置驱动的 rollout device / worker 路径**
- `train.rollout_device`:默认跟随 `train.device`
- `train.rollout_num_workers`:默认 `null`
- 当 rollout 设备是 CPU 时,自动退化为 `1`
- 当 rollout 设备是 CUDA 时,自动推断为 `min(train.rollout_num_episodes, 8)`
- `train.rollout_cuda_devices`:默认 `null`,等价于当前可见逻辑 GPU `[0]`
- `train.rollout_response_timeout_s`
- `train.rollout_server_startup_timeout_s`
所以现在:
- 训练在 `cuda` 上时,**训练期 rollout 默认会走 GPU**
- 如果 `rollout_num_workers > 1`,就会自动走并行 rollout
- 可以是 **单 GPU 多 worker 共用一个 inference server**
- 也可以是 **多 GPU 多 server 分摊 worker**
训练内 rollout artifact 默认落到:
`<hydra.run.dir>/rollout_artifacts/<checkpoint_stem>/`
常见文件:
- `rollout_summary.json`
- `rollout_front_ep01_trajectory.png` ... `rollout_front_ep10_trajectory.png`
日志重点看:
- `Epoch X rollout 平均奖励`
- `最佳模型已更新`
---
## 5. 数据集加载与 `val_episode_indices` 机制
### 5.1 数据集格式
`SimpleRobotDataset` 读取 `dataset_dir` 下的 `episode_*.hdf5`,每个 episode 文件里至少要有:
- `action`
- `observations/qpos`
- `observations/images/{cam_name}`
当前常用相机:
- `r_vis`
- `top`
- `front`
### 5.2 懒加载行为
`roboimi/vla/data/simpe_robot_dataset.py` 是按帧懒加载,不会一次性把整套 HDF5 全读进内存。
它会:
- 扫描目录下的 HDF5 文件
- 用文件名里的 episode 编号(如 `episode_100.hdf5` -> `100`)建立 `available_episode_indices`
- 在 worker 内做 HDF5 文件句柄 LRU 缓存
### 5.3 `val_episode_indices` 怎么切
`build_train_val_datasets()` 的逻辑是:
1. 先 instantiate 一次完整 dataset
2. 读取 `dataset.available_episode_indices`
3. 检查 `train.val_episode_indices` 是否都存在
4.`episode_indices=` 再各 instantiate 一次:
- train dataset = 全部 episode - held-out episode
- val dataset = 只包含 held-out episode
因此:
- `train.val_episode_indices=[100]` 的意思是“把 `episode_100.hdf5` 整个拿去做 held-out val”
- 如果 episode 不存在,会直接报错
- 如果你把所有 episode 都塞进 `val_episode_indices`,也会直接报错,因为训练集会变空
### 5.4 图像 resize 与 LeWM 附加字段
dataset 侧 resize 默认来自:
- `data.image_resize_shape`
- 如果 backbone 额外覆盖,则优先 `agent.vision_backbone.dataset_image_resize_shape`
返回 batch 除了常规:
- `observation.state`
- `observation.<cam>`
- `action`
还会在 LeWM 打开时返回:
- `lewm.observation.state`
- `lewm.observation.<cam>`
- `lewm.future.state`
- `lewm.future.<cam>`
### 5.5 统计文件
训练和推理都默认依赖 `dataset_stats.pkl`。数据集更新后重算:
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/vla/scripts/calculate_stats.py \
--dataset_dir /home/droid/project/diana_sim/sim_transfer
```
远端只要把 `--dataset_dir` 换成对应主机路径即可。
---
## 6. SwanLab 行为
当前配置默认值里 `train.use_swanlab=false`,但本分支常用 recipe 基本都显式开:
- `train.use_swanlab=true`
- `train.swanlab_project=roboimi-vla`
- `train.swanlab_run_name=<run_name>`
`train_vla.py` 的 SwanLab 行为:
- 初始化时上传 `train` / `data` / `agent` 三段 config
- 训练中记录:
- `train/loss`
- `train/lr`
- `train/best_loss`
- `train/step`
- checkpoint 验证时记录:
- `val/loss`
- held-out 数值验证时记录:
- `val/action_mse`
- rollout 验证时记录:
- `rollout/avg_reward`
- `rollout/epoch`
- 训练结束时记录:
- `final/checkpoint_path`
- `final/best_checkpoint_path`
训练期 rollout 生成的前视图轨迹 PNG 会 best-effort 上传到 SwanLab;失败只会 warning,不会让训练中断。
---
## 7. 并行 rollout 说明
### 7.1 这套能力从哪里来
本分支的并行 rollout 方向不是 DataLoader 并行,而是 **`eval_vla.py` 的 multiprocess rollout path**。
参考来源:
`/home/droid/project/roboimi/.worktrees/multiprocess-rollout/roboimi/demos/vla_scripts/eval_vla.py`
那条路径的控制参数是:
- `eval.num_workers`
- `eval.cuda_devices`
语义是:
- `eval.num_workers`:环境 worker 数,按 episode 切分
- `eval.cuda_devices`:推理 server 绑定到哪些逻辑 GPU
### 7.2 两种常见模式
1. **单机单卡,多 worker 共用同一张 GPU**
- 典型:本地 5090 只有 1 卡,但想让 4 个 rollout worker 并行跑环境
- 形式:`eval.device=cuda eval.num_workers=4 'eval.cuda_devices=[0]'`
- 这时是 **1 个 CUDA inference server + 4 个 env worker**
2. **单机多卡,多 server 分摊 worker**
- 典型:5880 有 2 卡,L20 有多卡
- 形式:`eval.device=cuda eval.num_workers=8 'eval.cuda_devices=[0,1]'`
- worker 会按 round-robin 分到多个 server 上
### 7.3 操作上要注意什么
- 并行 rollout 依赖 **多进程 eval 路径**,不是 `train.num_workers`
- `train.num_workers` 是 DataLoader worker,和 rollout 并行不是一回事
- `eval.num_workers > 1` 时必须 `eval.headless=true`
- worker 数会自动 cap 到 `eval.num_episodes`
- multiprocess rollout 当前已经支持 **per-episode trajectory image PNG**;多 worker 时每个 worker 会在自己的 artifact 子目录下写图,summary 会带回对应路径
- 但多 worker 时仍然不要同时要求:
- `eval.record_video=true`
- `eval.save_trajectory=true`
- `eval.save_trajectory_npz=true`
- `eval.save_trajectory_image=true` 现在是可以开的,适合并行 reward + 定性检查一起做
### 7.4 并行 rollout 命令模板
**5090 单卡 4 worker**
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/eval_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda eval.device=cuda eval.headless=true eval.verbose_action=false \
eval.ckpt_path=/home/droid/project/roboimi/.worktrees/feat-lewm-imf-fusion/runs/<run_name>/checkpoints/vla_model_best.pt \
eval.num_episodes=10 eval.num_workers=4 'eval.cuda_devices=[0]' \
eval.save_summary_json=true eval.artifact_dir=/tmp/lewm_parallel_eval_5090
```
**5880 双卡 8 worker**
```bash
/home/droid/miniforge3/envs/roboimi/bin/python roboimi/demos/vla_scripts/eval_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/home/droid/sim_dataset/sim_transfer \
train.device=cuda eval.device=cuda eval.headless=true eval.verbose_action=false \
eval.ckpt_path=/home/droid/roboimi_suite_20260416_lewm_imf_fusion/runs/<run_name>/checkpoints/vla_model_best.pt \
eval.num_episodes=10 eval.num_workers=8 'eval.cuda_devices=[0,1]' \
eval.save_summary_json=true eval.artifact_dir=/tmp/lewm_parallel_eval_5880
```
---
## 8. 当前常用命令 / 脚本
### 8.1 本地 5090:直接用 suite 脚本
现成脚本:
`experiment_suites/2026-04-21-lewm-fromscratch-old9-epoch50-roll5-val-20260421-153037/launch_local_5090.sh`
运行:
```bash
bash experiment_suites/2026-04-21-lewm-fromscratch-old9-epoch50-roll5-val-20260421-153037/launch_local_5090.sh
```
### 8.2 本地 5090:手动启动同 recipe
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
'agent.lewm_query_offsets=[8]' \
agent.pred_horizon=8 \
agent.num_action_steps=8 \
train.device=cuda \
train.batch_size=32 \
train.lr=0.0001 \
train.max_steps=109350 \
train.num_workers=4 \
train.save_freq=10000 \
train.rollout_validate_on_checkpoint=false \
train.rollout_val_freq_epochs=5 \
train.rollout_num_episodes=10 \
train.val_split=0.0 \
'train.val_episode_indices=[100]' \
train.action_mse_val_freq_epochs=1 \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.swanlab_run_name=lewmimf-q08-ph08-ex08-emb384-l12-fromscratch-epoch50-step109350-5090g0-20260421-153037 \
train.pretrained_ckpt=null \
agent.lewm_pretrained_ckpt=null \
hydra.run.dir=/home/droid/project/roboimi/.worktrees/feat-lewm-imf-fusion/runs/lewmimf-q08-ph08-ex08-emb384-l12-fromscratch-epoch50-step109350-5090g0-20260421-153037
```
### 8.3 5880:常用命令模板
```bash
ssh droid@100.73.14.65
cd /home/droid/roboimi_suite_20260416_lewm_imf_fusion
/home/droid/miniforge3/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/home/droid/sim_dataset/sim_transfer \
'agent.lewm_query_offsets=[8]' \
agent.pred_horizon=16 \
agent.num_action_steps=8 \
train.device=cuda train.batch_size=32 train.lr=0.0001 train.max_steps=109350 \
train.num_workers=4 train.save_freq=10000 train.rollout_validate_on_checkpoint=false \
train.rollout_val_freq_epochs=5 train.rollout_num_episodes=10 train.val_split=0.0 \
'train.val_episode_indices=[100]' train.action_mse_val_freq_epochs=1 \
train.use_swanlab=true train.swanlab_project=roboimi-vla \
train.swanlab_run_name=lewmimf-q08-ph16-ex08-emb384-l12-fromscratch-epoch50-step109350-5880g0-20260421-153037 \
train.pretrained_ckpt=null agent.lewm_pretrained_ckpt=null \
hydra.run.dir=/home/droid/roboimi_suite_20260416_lewm_imf_fusion/runs/lewmimf-q08-ph16-ex08-emb384-l12-fromscratch-epoch50-step109350-5880g0-20260421-153037
```
### 8.4 L20:常用命令模板
```bash
ssh droid@100.119.99.14
cd /data/roboimi_suite_20260416_lewm_imf_fusion
/home/droid/miniforge3/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/data/simtransfer/current \
'agent.lewm_query_offsets=[16]' \
agent.pred_horizon=16 \
agent.num_action_steps=16 \
train.device=cuda train.batch_size=32 train.lr=0.0001 train.max_steps=109350 \
train.num_workers=4 train.save_freq=10000 train.rollout_validate_on_checkpoint=false \
train.rollout_val_freq_epochs=5 train.rollout_num_episodes=10 train.val_split=0.0 \
'train.val_episode_indices=[100]' train.action_mse_val_freq_epochs=1 \
train.use_swanlab=true train.swanlab_project=roboimi-vla \
train.swanlab_run_name=lewmimf-q16-ph16-ex16-emb384-l12-fromscratch-epoch50-step109350-l20g0-20260421-153037 \
train.pretrained_ckpt=null agent.lewm_pretrained_ckpt=null \
hydra.run.dir=/data/roboimi_suite_20260416_lewm_imf_fusion/runs/lewmimf-q16-ph16-ex16-emb384-l12-fromscratch-epoch50-step109350-l20g0-20260421-153037
```
### 8.5 单次离线验证(当前分支已支持并行)
**单 GPU / 4 worker**
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/eval_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.device=cuda eval.device=cuda \
eval.ckpt_path=/home/droid/project/roboimi/.worktrees/feat-lewm-imf-fusion/runs/<run_name>/checkpoints/vla_model_best.pt \
eval.num_episodes=10 eval.num_workers=4 'eval.cuda_devices=[0]' \
eval.headless=true eval.verbose_action=false \
eval.save_summary_json=true eval.save_trajectory_image=true \
eval.trajectory_image_camera_name=front \
eval.artifact_dir=/tmp/lewm_eval_front
```
**训练内启用并行 GPU rollout(推荐显式写清楚)**
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
agent=lewm_resnet_query_imf_attnres \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
'agent.lewm_query_offsets=[8]' \
agent.pred_horizon=8 \
agent.num_action_steps=8 \
train.device=cuda \
train.batch_size=32 \
train.lr=0.0001 \
train.max_steps=109350 \
train.num_workers=4 \
train.save_freq=10000 \
train.rollout_val_freq_epochs=5 \
train.rollout_num_episodes=10 \
train.rollout_device=cuda \
train.rollout_num_workers=4 \
'train.rollout_cuda_devices=[0]' \
train.rollout_validate_on_checkpoint=false \
train.val_split=0.0 \
'train.val_episode_indices=[100]' \
train.action_mse_val_freq_epochs=1 \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.swanlab_run_name=<run_name> \
hydra.run.dir=/home/droid/project/roboimi/.worktrees/feat-lewm-imf-fusion/runs/<run_name>
```
### 8.6 监控日志
```bash
tail -f runs/<run_name>/launch.stdout.log
tail -f runs/<run_name>/train_vla.log
```
远端就把 `runs/<run_name>` 换成 manifest 里的绝对路径。
---
## 9. 操作建议
- **优先以 suite 的 `manifest.json` / `notes.md` / `launch_logs/*.launch.log` 为准**,不要手写一套和历史 run 不一致的命令
- 要做当前常用验证,就显式加上:
- `train.val_split=0.0`
- `train.val_episode_indices=[100]`
- `train.action_mse_val_freq_epochs=1`
- `train.rollout_val_freq_epochs=5`
- `train.rollout_num_episodes=10`
- 本分支如果要对比不同 horizon / action-step,尽量只改:
- `agent.lewm_query_offsets`
- `agent.pred_horizon`
- `agent.num_action_steps`
- 想复现 2026-04-21 那轮 from-scratch 结果时,记得同时设:
- `train.pretrained_ckpt=null`
- `agent.lewm_pretrained_ckpt=null`
@@ -1,256 +0,0 @@
# Align ResNet Transformer Diffusion To External Repo Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Do not switch to inline execution. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 将当前仓库的 `resnet_transformer` 对齐到 external repo `/home/droid/project/diffusion_policy` 的原生 DDPM Transformer Diffusion(非 PMF、非 DiT、非 UNet)实现,并采用 external 中已存在的 full-attention / nocausal 变体(`causal_attn=false`),固定为三相机图像条件输入,同时保持 EE action 语义的推理执行路径正确。
**Architecture:** 保留当前仓库较轻量的训练脚本和数据集组织,但把 Transformer denoiser 本体对齐到 external repo 的 `TransformerForDiffusion` 实现与接口语义;视觉编码器保留当前仓库的 ResNet+SpatialSoftmax 路线,仅保证其输出维度与三相机条件输入兼容。评估路径统一按 EE action 调 `env.step(action)`,训练/推理配置显式固定三相机 `r_vis/top/front` 且图像永远作为条件输入。
**Tech Stack:** Python, PyTorch, diffusers DDPM/DDIM, Hydra/OmegaConf, unittest, h5py, OpenCV
---
### Task 0: 执行前提与分支约束
**Files:**
- Verify only
- [ ] **Step 1: Confirm execution stays on the feature branch**
Run: `git branch --show-current`
Expected: `feat-align-dp-transformer-ee`
If the current branch is not `feat-align-dp-transformer-ee`, create/switch to it before continuing:
Run: `git checkout -b feat-align-dp-transformer-ee || git checkout feat-align-dp-transformer-ee`
Expected: working branch becomes `feat-align-dp-transformer-ee`
- [ ] **Step 2: Confirm implementation is executed with subagents**
Use `superpowers:subagent-driven-development` for implementation tasks and reviews. Do not switch to inline/manual execution unless the human explicitly changes course.
- [ ] **Step 3: Confirm the current branch is the only target branch for this work**
Do not create or switch to another branch/worktree during implementation unless the human explicitly asks for it. All changes for this migration stay on `feat-align-dp-transformer-ee`.
### Task 1: 验证 EE action 推理执行语义已固定
**Files:**
- Verify: `roboimi/vla/eval_utils.py`
- Verify: `roboimi/demos/vla_scripts/eval_vla.py`
- Verify: `tests/test_eval_vla_execution.py`
- [ ] **Step 1: Verify the test still passes on the feature branch**
Run: `mamba run -n roboimi python -m unittest tests.test_eval_vla_execution -v`
Expected: PASS
- [ ] **Step 2: Verify the real eval script no longer executes joint-action stepping**
Run:
```bash
python - <<'PY'
from pathlib import Path
src = Path('roboimi/demos/vla_scripts/eval_vla.py').read_text()
assert 'execute_policy_action(env, action)' in src
assert 'env.step_jnt(action)' not in src
print('eval_vla execution path verified')
PY
```
Expected: PASS
### Task 2: 通过对拍测试把当前 Transformer head 对齐到 external repo 的 `TransformerForDiffusion`
**Files:**
- Modify: `roboimi/vla/models/heads/transformer1d.py`
- Modify: `roboimi/vla/conf/head/transformer1d.yaml`
- Test: `tests/test_transformer1d_external_alignment.py`
- [ ] **Step 1: Confirm the alignment test exists and captures the intended parity contract**
Write a test that imports external repo's `TransformerForDiffusion` from:
`/home/droid/project/diffusion_policy/diffusion_policy/model/diffusion/transformer_for_diffusion.py`
and verifies the local `Transformer1D` can load the external model's `state_dict` and produce numerically identical outputs for the same inputs when configured equivalently. Use the full-attention / nocausal configuration (`causal_attn=False`) as the target behavior.
Required assertions:
- same parameter key structure (or compatible `load_state_dict(strict=True)`)
- same output shape `(B, T, action_dim)`
- `torch.allclose(local_out, external_out, atol=1e-6, rtol=1e-5)`
- local model exposes `get_optim_groups(weight_decay=...)` like external repo
- use an explicit import path / loader that does not depend on installing the external repo as a package
- account for external `ModuleAttrMixin`-style optimizer grouping expectations, including `_dummy_variable` / no-decay bookkeeping if needed for strict state-dict parity
- set both models to `eval()` and fix the random seed inside the test so dropout does not create false mismatches
- [ ] **Step 2: If the test still fails on this branch, use that red state as the TDD starting point**
Run: `mamba run -n roboimi python -m unittest tests.test_transformer1d_external_alignment -v`
Expected: either FAIL before implementation on a fresh checkout, or PASS if Task 2 has already been completed on this branch.
- [ ] **Step 3a: Port API and state-dict compatibility first**
Match constructor args, parameter names, embeddings, masks, and strict `state_dict` layout with external `TransformerForDiffusion`.
- [ ] **Step 3b: Port `_init_weights` and optimizer grouping**
Match external `_init_weights`, `get_optim_groups`, and `configure_optimizers`.
- [ ] **Step 3c: Port forward semantics**
Match external `forward(sample, timestep, cond)` behavior under the full-attention / nocausal configuration.
- [ ] **Step 3d: Port the minimal external implementation**
Update `roboimi/vla/models/heads/transformer1d.py` so it matches external repo's native DDPM transformer implementation semantics:
- same constructor arguments and defaults where relevant
- same token accounting (`time_as_cond`, `obs_as_cond`, `T_cond`)
- same parameter naming/layout for embeddings, encoder, decoder, masks
- same `_init_weights`
- same `get_optim_groups` / `configure_optimizers`
- same `forward(sample, timestep, cond)` behavior
Do **not** port PMF / IMF / DiT branches.
- [ ] **Step 4: Run test to verify it passes**
Run: `mamba run -n roboimi python -m unittest tests.test_transformer1d_external_alignment -v`
Expected: PASS with strict state-dict load and matching outputs.
### Task 3: 将当前 Agent 与配置固定到“三相机图像作为条件”的 Transformer diffusion 路线
**Files:**
- Modify: `roboimi/vla/agent.py`
- Modify: `roboimi/vla/conf/agent/resnet_transformer.yaml`
- Modify: `roboimi/vla/conf/data/simpe_robot_dataset.yaml`
- Modify: `roboimi/vla/conf/eval/eval.yaml`
- Modify: `roboimi/vla/models/backbones/resnet_diffusion.py`
- Test: `tests/test_resnet_transformer_agent_wiring.py`
- [ ] **Step 1: Write the failing test**
Write a wiring test that instantiates the Transformer agent config and checks:
- `head_type == "transformer"`
- `cfg.data.camera_names == cfg.eval.camera_names == ["r_vis", "top", "front"]`
- `num_cams == 3`
- Transformer `cond_dim == single_cam_feat_dim * 3 + obs_dim`
- `predict_action(...)` accepts image conditions and returns `(B, pred_horizon, action_dim)`
- test setup must not download weights; override `pretrained_backbone_weights=null` or stub the backbone
- assert camera order used for conditioning is tied to the required three cameras, not a stray constant
- [ ] **Step 2: Run test to verify it fails**
Run: `mamba run -n roboimi python -m unittest tests.test_resnet_transformer_agent_wiring -v`
Expected: FAIL if configs/head wiring do not yet guarantee three-camera conditional setup.
- [ ] **Step 3: Implement minimal alignment**
Make the transformer path explicit and stable:
- keep image observations always as condition (`cond_dim > 0`, `obs_as_cond` semantics)
- keep exactly three cameras: `r_vis`, `top`, `front`
- keep current ResNet+SpatialSoftmax backbone unless a test proves incompatibility
- make the camera feature order deterministic and aligned to the required three-camera list, not generic key sorting
- ensure `agent.per_step_cond_dim` and config `head.cond_dim` stay consistent
- ensure eval config uses the same three camera names as training
- ensure transformer config follows the external full-attention variant (`causal_attn=false`) instead of the external default causal setting
- [ ] **Step 4: Run test to verify it passes**
Run: `mamba run -n roboimi python -m unittest tests.test_resnet_transformer_agent_wiring -v`
Expected: PASS
### Task 4: 让训练脚本在 Transformer 路线上尽量遵循 external repo 的 optimizer/head 使用方式
**Files:**
- Modify: `roboimi/demos/vla_scripts/train_vla.py`
- Test: `tests/test_train_vla_transformer_optimizer.py`
- [ ] **Step 1: Write the failing test**
Write a test that builds a transformer agent and verifies the training script prefers the head/model supplied optimizer grouping when available (via `get_optim_groups`) instead of blindly using one flat `AdamW(agent.parameters(), ...)`.
The test must also prove that every remaining trainable non-head parameter is included exactly once in the optimizer (no silent drops, no duplicates).
- [ ] **Step 2: Run test to verify it fails**
Run: `mamba run -n roboimi python -m unittest tests.test_train_vla_transformer_optimizer -v`
Expected: FAIL because current training script uses flat optimizer construction.
- [ ] **Step 3: Implement minimal optimizer alignment**
Update `train_vla.py` so that:
- for transformer head paths, if `agent.noise_pred_net` exposes `get_optim_groups`, build optimizer groups like external repo
- explicitly include the remaining trainable non-head parameters (for example the ResNet backbone's non-frozen projection / pooling layers) in the optimizer instead of accidentally dropping them
- keep the rest of the simple training loop intact (no EMA unless required later)
- do not over-port external workspace abstractions
- [ ] **Step 4: Run test to verify it passes**
Run: `mamba run -n roboimi python -m unittest tests.test_train_vla_transformer_optimizer -v`
Expected: PASS
### Task 5: 端到端实例化与最小推理验收
**Files:**
- Verify only
- [ ] **Step 1: Run all focused unit tests**
Run:
```bash
mamba run -n roboimi python -m unittest \
tests.test_eval_vla_execution \
tests.test_transformer1d_external_alignment \
tests.test_resnet_transformer_agent_wiring \
tests.test_train_vla_transformer_optimizer -v
```
Expected: all PASS
- [ ] **Step 2: Run syntax checks on changed training/eval/model files**
Run:
```bash
mamba run -n roboimi python -m py_compile \
roboimi/vla/eval_utils.py \
roboimi/vla/models/heads/transformer1d.py \
roboimi/vla/agent.py \
roboimi/demos/vla_scripts/train_vla.py \
roboimi/demos/vla_scripts/eval_vla.py
```
Expected: no syntax errors
- [ ] **Step 3: Run a local instantiation smoke test**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python - <<'PY'
import torch
from hydra import compose, initialize_config_dir
from hydra.utils import instantiate
from pathlib import Path
config_dir = str((Path.cwd() / "roboimi" / "vla" / "conf").resolve())
with initialize_config_dir(version_base=None, config_dir=config_dir):
cfg = compose(config_name="config", overrides=[
"agent=resnet_transformer",
"agent.vision_backbone.pretrained_backbone_weights=null",
])
agent = instantiate(cfg.agent, dataset_stats=None)
images = {
"r_vis": torch.rand(1, cfg.agent.obs_horizon, 3, 224, 224),
"top": torch.rand(1, cfg.agent.obs_horizon, 3, 224, 224),
"front": torch.rand(1, cfg.agent.obs_horizon, 3, 224, 224),
}
qpos = torch.rand(1, cfg.agent.obs_horizon, cfg.agent.obs_dim)
out = agent.predict_action(images, qpos)
assert out.shape == (1, cfg.agent.pred_horizon, cfg.agent.action_dim), out.shape
print("smoke_shape=", out.shape)
PY
```
that:
- instantiates `agent=resnet_transformer`
- constructs fake three-camera input (`r_vis`, `top`, `front`)
- calls `agent.predict_action(...)`
- verifies output shape is `(1, pred_horizon, 16)`
Expected: PASS
@@ -1,202 +0,0 @@
# VLA Experiment Sweep Execution Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Probe the largest safe shared batch size, prepare the 10 unique VLA sweep runs, and launch the serial GPU experiment queue with SwanLab logging and 20-epoch headless rollout validation.
**Architecture:** Use the existing `train_vla.py` / `eval_vla.py` path without changing model code unless launch-blocking issues appear. A small external launcher contract will manage per-run directories, logs, pids, and serial execution while Hydra places trainer-local `checkpoints/` under each run directory.
**Tech Stack:** zsh, mamba env `roboimi`, Hydra overrides, PyTorch CUDA, SwanLab, existing `train_vla.py`
---
### Task 1: Reconfirm schedule math and current runtime assumptions
**Files:**
- Modify: `docs/superpowers/specs/2026-03-30-vla-experiment-sweep-design.md` (only if execution reveals a spec mismatch)
- Test: none
- [ ] **Step 1: Print dataset size and epoch math for the baseline batch size**
Run:
```bash
python - <<'PY'
num_samples = 70000
for batch_size in (32, 48, 64, 80):
steps_per_epoch = num_samples // batch_size
print(batch_size, steps_per_epoch, 20 * steps_per_epoch, 200 * steps_per_epoch)
PY
```
Expected: integer epoch-step mappings for candidate batch sizes.
- [ ] **Step 2: Reconfirm no stale training process is already active**
Run:
```bash
ps -ef | grep -E 'train_vla.py|python .*roboimi/demos/vla_scripts/train_vla.py' | grep -v grep || true
```
Expected: no active training process unless intentionally started by this session.
- [ ] **Step 3: Record the execution assumptions**
Capture in session notes:
- dataset path
- camera list
- current branch
- no active conflicting training process
### Task 2: Probe the largest safe shared batch size
**Files:**
- Create: `runs/batch-probe-<timestamp>/` (runtime artifact)
- Test: probe command output only
- [ ] **Step 1: Write the candidate probe command for the largest model**
Use overrides:
- `data.dataset_dir=/home/droid/project/diana_sim/sim_transfer`
- `data.camera_names=[r_vis,top,front]`
- `agent.head.n_emb=384`
- `agent.head.n_layer=12`
- `agent.head.n_head=4`
- `agent.vision_backbone.pretrained_backbone_weights=IMAGENET1K_V1`
- `agent.vision_backbone.freeze_backbone=true`
- `agent.vision_backbone.use_separate_rgb_encoder_per_camera=true`
- `train.device=cuda`
- `train.val_split=0.0`
- `train.seed=42`
- `train.use_swanlab=false`
- `train.rollout_val_freq_epochs=0`
- `train.rollout_validate_on_checkpoint=false`
- `train.max_steps=4`
- [ ] **Step 2: Run the probe for `batch_size=32`**
Expected pass condition:
- no CUDA OOM
- no crash/abort
- no NaN/Inf loss
- 4 steps complete
- [ ] **Step 3: Repeat for `batch_size=48`, `64`, then optionally `80`**
Stop increasing once one candidate fails.
- [ ] **Step 4: Compute the shared LR and 200-epoch step budget**
Using:
```text
lr = 1e-4 * (batch_size / 16)
max_steps = 200 * floor(70000 / batch_size)
```
- [ ] **Step 5: Write down the chosen shared batch size and derived max_steps**
This becomes the execution contract for all runs.
### Task 3: Materialize the 10 unique run matrix
**Files:**
- Create: `runs/vla-sweep-<timestamp>/manifest.txt` (runtime artifact)
- Create: `runs/vla-sweep-<timestamp>/launch_queue.sh` (runtime artifact)
- Test: shell syntax / manifest inspection
- [ ] **Step 1: Enumerate the 2-point pretraining comparison**
Canonical runs:
1. `sim-transfer-baseline-pretrain-on-emb128-layer4`
2. `sim-transfer-pretrain-off`
- [ ] **Step 2: Enumerate the remaining 8 unique architecture-sweep runs**
Skip the duplicate `(pretrain-on, emb128, layer4)` because it reuses the baseline run.
- [ ] **Step 3: For each run, define exact Hydra overrides**
Every run must pin:
- dataset path
- three cameras
- `train.device=cuda`
- `train.val_split=0.0`
- `train.num_workers=12`
- `train.seed=42`
- `train.rollout_val_freq_epochs=20`
- `train.rollout_validate_on_checkpoint=false`
- `train.rollout_num_episodes=3`
- `train.use_swanlab=true`
- a unique `train.swanlab_run_name`
- shared batch size
- shared LR
- derived `max_steps`
- `agent.head.n_head=4`
- `agent.vision_backbone.freeze_backbone=true`
- `agent.vision_backbone.use_separate_rgb_encoder_per_camera=true`
- [ ] **Step 4: Write the queue launcher script**
Launcher responsibilities:
- create run directory
- export `LD_LIBRARY_PATH` CUDA/cuDNN fixup
- export `MPLCONFIGDIR=/tmp/mpl`
- set `hydra.job.chdir=true`
- set `hydra.run.dir=<run_dir>`
- tee logs to `<run_dir>/train.log`
- write `<run_dir>/train.pid`
- run jobs serially
- [ ] **Step 5: Verify the queue script syntax**
Run:
```bash
zsh -n runs/vla-sweep-<timestamp>/launch_queue.sh
```
Expected: exit 0.
### Task 4: Launch the first probe-selected production run and verify startup
**Files:**
- Create: `runs/vla-sweep-<timestamp>/<run-name>/` (runtime artifact)
- Test: live log inspection / process inspection
- [ ] **Step 1: Start the first run in the queue**
Use the generated launcher contract.
- [ ] **Step 2: Inspect the first 100-200 log lines**
Expected:
- config resolves correctly
- dataset loads
- agent initializes on CUDA
- training loop starts
- SwanLab initializes
- [ ] **Step 3: Confirm process identity and run directory**
Collect:
- PID
- run dir
- log path
- SwanLab run name
- [ ] **Step 4: If startup is clean, continue the remaining serial queue**
If startup fails, stop and debug before launching more runs.
### Task 5: Report the launched sweep contract back to the user
**Files:**
- Test: log evidence only
- [ ] **Step 1: Summarize the chosen batch size, LR, max_steps, and rollout cadence**
- [ ] **Step 2: Provide the active run directory / pid / first-run status**
- [ ] **Step 3: State the remaining queued runs by name**
@@ -1,454 +0,0 @@
# VLA Training + Headless Rollout + SwanLab Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 补齐当前 `Transformer1D` 训练依赖,在 `/home/droid/project/diana_sim/sim_transfer` 上启动训练,接入 SwanLab 标量日志,并提供训练期可选的 headless rollout validation 路径。
**Architecture:** 保持现有 `train_vla.py` / `eval_vla.py` 主体不变,只做最小必要改造:补依赖、补 stats 生成入口、在训练脚本里加轻量 SwanLab logger 和可选 checkpoint-time rollout wrapper、在环境侧把图像更新与 GUI 显示解耦。训练默认仍走当前 `resnet_transformer + Transformer1D` 路线,rollout validation 作为薄封装默认关闭。
**Tech Stack:** Python, mamba/conda, pip, PyTorch, Hydra, diffusers, torchvision, einops, SwanLab, MuJoCo, OpenCV, unittest
---
### Task 0: 执行前提与分支/环境确认
**Files:**
- Verify only
- [ ] **Step 1: 确认当前分支仍是目标分支**
Run: `git branch --show-current`
Expected: `feat-align-dp-transformer-ee`
- [ ] **Step 2: 记录当前 Python 解释器与环境名**
Run: `/home/droid/.conda/envs/roboimi/bin/python - <<'PY'
import sys
print(sys.executable)
PY`
Expected: 输出 `/home/droid/.conda/envs/roboimi/bin/python`
- [ ] **Step 3: 记录当前数据集目录存在性与 episode 数量**
Run: `/usr/bin/zsh -lc 'echo DATASET=/home/droid/project/diana_sim/sim_transfer; find /home/droid/project/diana_sim/sim_transfer -maxdepth 1 -name "episode_*.hdf5" | wc -l'`
Expected: 输出目录路径与 `100`
### Task 1: 补齐训练依赖并把 resolved versions 写回环境定义
**Files:**
- Modify: `environment.yml`
- Verify only: local `roboimi` env
- [ ] **Step 1: 写出缺失依赖的最小清单**
需要补齐:
- `diffusers`
- `torchvision`
- `einops`
- `swanlab`
- [ ] **Step 2: 先用 dry-run 解析候选版本,确认不会升级 Torch**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python -m pip install --dry-run \
diffusers torchvision einops swanlab
```
Expected: 输出候选版本;若显示会升级/替换 `torch`,则停止并改用显式兼容版本
- [ ] **Step 3: 安装与当前 Torch 兼容的缺失依赖到现有环境**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python -m pip install \
diffusers torchvision einops swanlab
```
Expected: 安装成功,且不替换当前 `torch==2.4.0`
- [ ] **Step 4: 运行 import 验证**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python - <<'PY'
mods=['torch','hydra','omegaconf','diffusers','torchvision','einops','cv2','h5py','swanlab','mujoco']
for m in mods:
__import__(m)
print('OK', m)
PY
```
Expected: 每个模块都输出 `OK <module>`
- [ ] **Step 5: 记录实际安装版本**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python - <<'PY'
import diffusers, torchvision, einops, swanlab
print('diffusers', getattr(diffusers,'__version__',''))
print('torchvision', getattr(torchvision,'__version__',''))
print('einops', getattr(einops,'__version__',''))
print('swanlab', getattr(swanlab,'__version__',''))
PY
```
Expected: 输出四个包的 resolved versions
- [ ] **Step 6: 将 resolved versions 写回 `environment.yml`**
把新增依赖补到 `environment.yml` 的现有依赖列表(若使用 `pip:` 段则更新该段)里,使用 Step 5 得到的**实际 resolved versions**,避免环境漂移,并避免重复 package 条目。
- [ ] **Step 7: 语法检查环境定义文件仅作结构确认**
Run: `python - <<'PY'
from pathlib import Path
text = Path('environment.yml').read_text()
assert 'diffusers' in text
assert 'torchvision' in text
assert 'einops' in text
assert 'swanlab' in text
print('environment.yml updated')
PY`
Expected: `environment.yml updated`
### Task 2: 让统计脚本支持外部数据目录并生成 dataset stats
**Files:**
- Modify: `roboimi/vla/scripts/calculate_stats.py`
- Test: `tests/test_calculate_stats_cli.py`
- [ ] **Step 1: 写 failing test,验证统计脚本可接受外部 `--dataset_dir` 并输出目标路径**
Test file should:
- 用临时目录创建最小 HDF5 episode
- 调用脚本入口/函数时传入外部目录
- 断言输出 `dataset_stats.pkl` 出现在该目录
- 断言 pickle 内包含 `action_mean/qpos_mean/...`
- [ ] **Step 2: 跑测试确认它先失败**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_calculate_stats_cli -v`
Expected: FAIL(当前脚本写死默认目录)
- [ ] **Step 3: 最小实现 `--dataset_dir` 支持**
要求:
- 保留现有统计逻辑
- 仅增加 CLI 参数解析
- 输出仍写入 `<dataset_dir>/dataset_stats.pkl`
- [ ] **Step 4: 重新跑测试确认转绿**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_calculate_stats_cli -v`
Expected: PASS
- [ ] **Step 5: 用真实数据集生成 stats**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python roboimi/vla/scripts/calculate_stats.py \
--dataset_dir /home/droid/project/diana_sim/sim_transfer
```
Expected: 生成 `/home/droid/project/diana_sim/sim_transfer/dataset_stats.pkl`
- [ ] **Step 6: 验证 stats 文件结构**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python - <<'PY'
import pickle
path='/home/droid/project/diana_sim/sim_transfer/dataset_stats.pkl'
with open(path,'rb') as f:
stats=pickle.load(f)
for k in ['action_mean','action_std','action_min','action_max','qpos_mean','qpos_std','qpos_min','qpos_max']:
assert k in stats, k
print('stats_ok')
PY
```
Expected: `stats_ok`
### Task 3: 增加 SwanLab 训练日志集成
**Files:**
- Modify: `roboimi/demos/vla_scripts/train_vla.py`
- Modify: `roboimi/vla/conf/config.yaml`
- Test: `tests/test_train_vla_swanlab_logging.py`
- [ ] **Step 1: 写 failing test,验证训练脚本在 `train.use_swanlab=true` 时会初始化 SwanLab 并记录标量**
Test should:
- stub `swanlab`
- 调用训练脚本中抽取出的非 Hydra helper(如 `_run_training(cfg)`)的最小路径(`max_steps=0` 或很小)
- 断言调用了:
- `swanlab.init(project='roboimi-vla', ...)`
- 至少一次 `swanlab.log({...})`
- 断言当 `use_swanlab=false` 时不会 import/初始化 SwanLab
- 断言当 `use_swanlab=true` 且 import `swanlab` 失败时会 fail fast
- 断言当 `use_swanlab=true` 且认证/登录状态不可用时会 fail fast
- 断言会记录最终/最佳 checkpoint 路径到 log/summary
- [ ] **Step 2: 跑测试确认它先失败**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_train_vla_swanlab_logging -v`
Expected: FAIL(当前无 SwanLab 集成)
- [ ] **Step 3: 在配置中增加最小 SwanLab 契约**
`roboimi/vla/conf/config.yaml` 添加:
- `train.use_swanlab: true`
- `train.swanlab_project: roboimi-vla`
- 可选 `train.swanlab_run_name: null`
- [ ] **Step 4: 从 Hydra 入口提取可测试的训练 helper**
要求:
- 新增类似 `_run_training(cfg)` 的普通函数
- `main()` 只做 Hydra 入口转发
- 测试只调用 helper,不直接调用 Hydra-decorated `main(cfg)`
- [ ] **Step 5: 在训练脚本中实现 SwanLab 初始化的 fail-fast 逻辑**
要求:
- `use_swanlab=true` 时:
- import 失败 -> 直接报错
- 本地未登录/认证失败 -> 直接报错
- 成功后执行 `swanlab.init(project=cfg.train.swanlab_project, ...)`
- [ ] **Step 6: 在训练脚本中实现轻量 scalar logger**
要求:
- 仅 scalar logging,不引入自定义 callback 框架
- 训练时记录 `train/loss`, `train/lr`, `train/best_loss`, `train/step`
- 验证时记录 `val/loss`
- 训练结束时记录:
- `train/final_checkpoint_path`
- `train/best_checkpoint_path`
- 若库支持则显式 `finish/close`
- [ ] **Step 7: 重新跑测试确认转绿**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_train_vla_swanlab_logging -v`
Expected: PASS
- [ ] **Step 8: 用提供的 API key 完成本地登录**
Run:
```bash
/usr/bin/zsh -lc 'SWANLAB_API_KEY="<user-provided>"; /home/droid/.conda/envs/roboimi/bin/swanlab login -k "$SWANLAB_API_KEY"'
```
Expected: 登录成功并保存本地凭证
### Task 4: 为评估/rollout 路径增加 headless 模式
**Files:**
- Modify: `roboimi/envs/double_base.py`
- Modify: `roboimi/envs/double_pos_ctrl_env.py`
- Modify: `roboimi/vla/conf/eval/eval.yaml`
- Modify: `roboimi/demos/vla_scripts/eval_vla.py`
- Test: `tests/test_eval_vla_headless.py`
- [ ] **Step 1: 写 failing test,验证 headless 路径不触发 GUI 调用**
Test should stub:
- `cv2.namedWindow`
- `cv2.imshow`
- `cv2.waitKey`
- viewer launch/render path
And assert:
- `eval.headless=true` 时不调用这些 GUI 接口
- 仍能获取图像观测并走到 policy action 执行前/后关键路径
- [ ] **Step 2: 跑测试确认它先失败**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_eval_vla_headless -v`
Expected: FAIL(当前 env/eval 默认会开 viewer 和 cv2 窗口)
- [ ] **Step 3: 统一配置开关为 `eval.headless`**
`roboimi/vla/conf/eval/eval.yaml` 添加:
- `headless: false`
不要再引入第二个同义开关。
- [ ] **Step 4: 在 env 工厂中接入 `headless`**
`make_sim_env(...)` 中:
- `eval.headless=true` -> `is_render=False`
- 保留图像观测更新
- 不创建 MuJoCo viewer
- [ ] **Step 5: 将相机更新与 GUI 显示解耦**
`double_base.py`
- 图像更新逻辑保留
- `cv2.namedWindow/imshow/waitKey` 仅在非 headless 下执行
- [ ] **Step 6: eval 脚本中在 headless 下跳过 `env.render()`**
只在 `eval.headless=false` 时调用 `env.render()`
- [ ] **Step 7: 重新跑测试确认转绿**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_eval_vla_headless -v`
Expected: PASS
### Task 5: 给训练脚本加可选 checkpoint-time rollout validation 薄封装
**Files:**
- Modify: `roboimi/demos/vla_scripts/train_vla.py`
- Possibly modify: `roboimi/demos/vla_scripts/eval_vla.py`(仅当需要提取可复用入口)
- Modify: `roboimi/vla/conf/config.yaml`
- Test: `tests/test_train_vla_rollout_validation.py`
- [ ] **Step 1: 写 failing test,验证 checkpoint 保存点可选调用 rollout validation,且会传 `eval.headless=true`**
Test should:
- stub rollout/eval helper
- 开启 `train.rollout_validate_on_checkpoint=true`
- 设置小 `save_freq`
- 断言训练脚本在 checkpoint 时调用验证 helper
- 断言调用参数带 `headless=true`
- [ ] **Step 2: 跑测试确认它先失败**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_train_vla_rollout_validation -v`
Expected: FAIL(当前无该 hook
- [ ] **Step 3: 增加最小配置键**
`config.yaml` 中添加:
- `train.rollout_validate_on_checkpoint: false`
- `train.rollout_num_episodes: 1`
- [ ] **Step 4: 提取一个最小 rollout validation helper 接口**
要求:
- helper 输入至少包括 `cfg`, `ckpt_path`, `num_episodes`, `headless`
- 默认训练侧调用时强制 `headless=True`
- 优先复用现有 eval 逻辑,不引入第二套 validator 类
- [ ] **Step 5: 在 checkpoint 保存路径中接入 rollout helper**
要求:
- 不重写第二套验证框架
- 优先复用现有 eval 逻辑/工具
- 默认关闭
- 仅在 checkpoint 时少量调用
- 强制 `eval.headless=true`
- [ ] **Step 6: 重新跑测试确认转绿**
Run: `/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_train_vla_rollout_validation -v`
Expected: PASS
### Task 6: 启动训练前的集成 smoke verification
**Files:**
- Verify only
- [ ] **Step 1: 跑所有新增/相关测试**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python -m unittest \
tests.test_calculate_stats_cli \
tests.test_train_vla_swanlab_logging \
tests.test_eval_vla_headless \
tests.test_train_vla_rollout_validation -v
```
Expected: 全部 PASS
- [ ] **Step 2: 对关键修改文件做语法检查**
Run:
```bash
/home/droid/.conda/envs/roboimi/bin/python -m py_compile \
roboimi/vla/scripts/calculate_stats.py \
roboimi/demos/vla_scripts/train_vla.py \
roboimi/demos/vla_scripts/eval_vla.py \
roboimi/envs/double_pos_ctrl_env.py \
roboimi/envs/double_base.py
```
Expected: 无语法错误
- [ ] **Step 3: 运行训练 smoke run**
Run:
```bash
SWANLAB_API_KEY='<user-provided>' \
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.max_steps=20 \
train.log_freq=1 \
train.save_freq=10 \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.rollout_validate_on_checkpoint=false
```
Expected:
- 训练启动成功
- 产生 `checkpoints/vla_model_step_10.pt``vla_model_final.pt`
- 本地日志中无 ImportError
- [ ] **Step 4: 运行一个最小 headless rollout-validation smoke run**
Run:
```bash
SWANLAB_API_KEY='<user-provided>' \
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.max_steps=10 \
train.log_freq=1 \
train.save_freq=5 \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.rollout_validate_on_checkpoint=true \
train.rollout_num_episodes=1 \
eval.headless=true
```
Expected:
- 到达 checkpoint-time rollout 调用
- 不弹 MuJoCo viewer
- 不执行 `cv2.namedWindow/imshow/waitKey`
- [ ] **Step 5: 验证 checkpoint 文件已写出**
Run: `/usr/bin/zsh -lc 'ls -lah checkpoints | sed -n "1,120p"'`
Expected: 存在 `vla_model_step_10.pt``vla_model_final.pt`
- [ ] **Step 6: 验证 SwanLab 已收到标量**
验证方式:
- 终端日志中确认 `swanlab.init` / run URL / run id
- 若工具支持,确认 dashboard 中 `roboimi-vla` 项目下出现本次 run
### Task 7: 启动正式训练
**Files:**
- Verify only
- [ ] **Step 1: 用真实配置启动正式训练**
Run:
```bash
SWANLAB_API_KEY='<user-provided>' \
/home/droid/.conda/envs/roboimi/bin/python roboimi/demos/vla_scripts/train_vla.py \
data.dataset_dir=/home/droid/project/diana_sim/sim_transfer \
train.use_swanlab=true \
train.swanlab_project=roboimi-vla \
train.rollout_validate_on_checkpoint=true \
eval.headless=true
```
Expected:
- 训练持续运行
- checkpoint 周期性写出
- SwanLab 周期性收到 train/val 标量
- 若 checkpoint-time rollout 打开,则不弹 GUI
- [ ] **Step 2: 记录启动信息并向用户汇报**
汇报内容至少包括:
- 使用的数据集路径
- 训练命令/关键 overrides
- checkpoint 输出目录
- SwanLab project 名称
- rollout validation 是否已启用以及是否 headless
@@ -1,311 +0,0 @@
# sim_air_insert_ring_bar Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add an independent dual-Diana MuJoCo task `sim_air_insert_ring_bar` with a square ring block, a square bar block, staged rewards, strict finite-geometry in-air insertion success detection, and a task-specific scripted policy.
**Architecture:** Reuse the current dual-Diana EE-control stack and environment factory, but add a task-specific scene XML, robot asset entrypoint, sampling helpers, and a new task-specific environment module. Keep `sim_transfer` untouched while introducing pure-Python geometry helpers and focused tests so reward/success behavior can be regression tested without requiring a full MuJoCo rollout in every test.
**Tech Stack:** Python, unittest, MuJoCo XML assets, existing dual-Diana environment classes, Hydra-compatible task naming/config patterns.
---
## File Structure / Responsibilities
- **Create:** `roboimi/assets/models/manipulators/DianaMed/ring_bar_objects.xml`
- Defines the rigid ring body and bar body, each with a free joint and stable box-based geoms.
- **Create:** `roboimi/assets/models/manipulators/DianaMed/bi_diana_ring_bar_ee.xml`
- Scene entrypoint that includes the shared world/table/robot assets plus the new object XML.
- **Modify:** `roboimi/assets/robots/diana_med.py`
- Add a task-specific robot asset class for the new scene XML without changing existing `BiDianaMed` behavior.
- **Modify:** `roboimi/utils/act_ex_utils.py`
- Add deterministic helpers to sample left/right planar placement regions for ring and bar objects.
- **Modify:** `roboimi/utils/constants.py`
- Register the new task name and default metadata.
- **Create:** `roboimi/envs/double_air_insert_env.py`
- New task-specific environment, finite-geometry success helpers, reset logic, reward logic, and task factory branch.
- **Modify:** `roboimi/envs/double_pos_ctrl_env.py`
- Route `make_sim_env()` to the new task-specific environment while keeping current `sim_transfer` logic unchanged.
- **Create:** `roboimi/demos/diana_air_insert_policy.py`
- Task-specific waypoint/open-loop scripted policy for grasp-lift-align-insert.
- **Modify:** `roboimi/demos/vla_scripts/eval_vla.py`
- Reset the new task with the correct sampled task state instead of assuming a single transfer box pose.
- **Create:** `tests/test_air_insert_env.py`
- Focused unit tests for sampling, reset helpers, reward progression, and strict success detection.
- **Modify:** `tests/test_eval_vla_headless.py`
- Add coverage that headless evaluation dispatches the correct reset sampler for the new task.
- **Modify:** `tests/test_robot_asset_paths.py`
- Verify the new robot asset class resolves its XML path correctly independent of cwd.
---
### Task 1: Add failing tests for task registration, samplers, and asset wiring
**Files:**
- Create: `tests/test_air_insert_env.py`
- Modify: `tests/test_eval_vla_headless.py`
- Modify: `tests/test_robot_asset_paths.py`
- Modify: `roboimi/utils/act_ex_utils.py` (later in implementation)
- Modify: `roboimi/utils/constants.py` (later in implementation)
- Modify: `roboimi/assets/robots/diana_med.py` (later in implementation)
- Modify: `roboimi/envs/double_pos_ctrl_env.py` (later in implementation)
- Create: `roboimi/envs/double_air_insert_env.py` (minimal stub in this task)
- [ ] **Step 1: Write failing tests for task config and sampling helpers**
Add tests in `tests/test_air_insert_env.py` covering:
- `SIM_TASK_CONFIGS['sim_air_insert_ring_bar']` exists
- `sample_air_insert_ring_bar_pose()` (or equivalent helper) returns ring/bar positions with fixed z and correct left/right planar ranges
- output structure is explicit and easy for reset/eval code to consume
- [ ] **Step 2: Write failing tests for environment factory dispatch and robot asset resolution**
Add tests covering:
- `make_sim_env('sim_air_insert_ring_bar', headless=True)` dispatches to the new environment with rendering disabled
- a new robot asset class resolves the new XML path independent of cwd, similar to the existing `BiDianaMed` test pattern
- [ ] **Step 3: Write failing tests for eval reset helper dispatch**
Extend `tests/test_eval_vla_headless.py` so headless eval can reset the new task using the new sampler instead of hard-coding `sample_transfer_pose()`.
- [ ] **Step 4: Run the targeted tests to verify they fail for the expected missing-feature reasons**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env tests.test_eval_vla_headless tests.test_robot_asset_paths -v`
Expected:
- FAIL because the new task config/helper/class/dispatch branch does not exist yet
- [ ] **Step 5: Implement the minimal production code to satisfy the new task registration and helper tests**
Implement only enough to make the new tests pass:
- add new task config entry
- add the new placement sampler
- add the new robot asset class
- create a minimal importable `double_air_insert_env.py` stub and class/function surface needed for factory dispatch tests
- add the factory dispatch branch / headless wiring
- update eval reset dispatch for the new task
- [ ] **Step 6: Re-run the targeted tests to verify they pass**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env tests.test_eval_vla_headless tests.test_robot_asset_paths -v`
Expected:
- PASS for the new registration/sampler/dispatch/asset tests
- [ ] **Step 7: Commit Task 1**
Run:
`git add tests/test_air_insert_env.py tests/test_eval_vla_headless.py tests/test_robot_asset_paths.py roboimi/utils/act_ex_utils.py roboimi/utils/constants.py roboimi/assets/robots/diana_med.py roboimi/envs/double_pos_ctrl_env.py roboimi/envs/double_air_insert_env.py roboimi/demos/vla_scripts/eval_vla.py && git commit -m "feat(env): register sim air insert ring bar task"`
---
### Task 2: Add the MuJoCo ring+bar scene assets and reset helpers
**Files:**
- Create: `roboimi/assets/models/manipulators/DianaMed/ring_bar_objects.xml`
- Create: `roboimi/assets/models/manipulators/DianaMed/bi_diana_ring_bar_ee.xml`
- Create or Modify: `roboimi/envs/double_air_insert_env.py`
- Modify: `tests/test_air_insert_env.py`
- [ ] **Step 1: Write failing tests for object reset helpers and scene-specific joint naming assumptions**
In `tests/test_air_insert_env.py`, add unit tests for helper functions that:
- write ring pose to `ring_block_joint` from the named task-state mapping
- write bar pose to `bar_block_joint` from the named task-state mapping
- read back `env_state` as a stable 14D vector `[ring_pos, ring_quat, bar_pos, bar_quat]`
Use fake `mj_data` objects so tests stay fast and deterministic.
- [ ] **Step 2: Run the focused test slice and verify it fails**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env -v`
Expected:
- FAIL because reset/state helper functions and joint conventions are not implemented yet
- [ ] **Step 3: Implement the scene XML files and reset/state helper code**
Implement:
- the object XML with one rigid ring body and one rigid bar body
- the task scene XML entrypoint using the shared world/table/robot includes
- reset helper(s) in `double_air_insert_env.py` that set qpos for both free joints with fixed quaternions
- task-state accessor(s) returning both object poses in a stable structure
- [ ] **Step 4: Re-run the focused test slice and verify it passes**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env -v`
Expected:
- PASS for reset/state helper tests
- [ ] **Step 5: Commit Task 2**
Run:
`git add roboimi/assets/models/manipulators/DianaMed/ring_bar_objects.xml roboimi/assets/models/manipulators/DianaMed/bi_diana_ring_bar_ee.xml roboimi/envs/double_air_insert_env.py tests/test_air_insert_env.py && git commit -m "feat(scene): add ring and bar insertion scene assets"`
---
### Task 3: Implement strict reward and finite-geometry success detection
**Files:**
- Modify: `roboimi/envs/double_air_insert_env.py`
- Modify: `tests/test_air_insert_env.py`
- [ ] **Step 1: Write failing tests for reward stages and strict success detection**
Add tests in `tests/test_air_insert_env.py` for:
- left contact stage reward
- right contact stage reward
- ring lifted off table stage
- bar lifted off table stage
- positive success case where a finite bar truly passes through the aperture
- negative case where the centerline would pass but the finite square body would clip
- negative case where the bar has not crossed the ring thickness direction enough
- negative case where one/both objects are still on the table
Structure the tests around pure helper functions and light fake contact/state objects so the geometry logic is directly regression tested.
- [ ] **Step 2: Run the focused tests and verify they fail for missing reward/success logic**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env -v`
Expected:
- FAIL because the staged reward and finite-geometry insertion logic are not implemented yet
- [ ] **Step 3: Implement minimal strict success helpers and reward logic**
Implement in `roboimi/envs/double_air_insert_env.py`:
- pure helper(s) for transforming bar geometry into ring-local coordinates
- finite-geometry insertion predicate (not centerline-only)
- table-contact / airborne checks
- staged reward function returning the highest achieved stage with `max_reward = 5`
- [ ] **Step 4: Re-run the focused tests to verify the logic passes**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env -v`
Expected:
- PASS for reward and success-detection regression tests
- [ ] **Step 5: Commit Task 3**
Run:
`git add roboimi/envs/double_air_insert_env.py tests/test_air_insert_env.py && git commit -m "feat(env): add strict air insertion reward and success logic"`
---
### Task 4: Add the scripted policy and integration smoke coverage
**Files:**
- Create: `roboimi/demos/diana_air_insert_policy.py`
- Modify: `roboimi/demos/diana_record_sim_episodes.py`
- Modify: `tests/test_air_insert_env.py`
- Optionally Modify: `roboimi/demos/vla_scripts/eval_vla.py` (only if integration gaps remain after Task 1)
- [ ] **Step 1: Write failing tests for scripted-policy action shape and basic generation**
Add tests covering:
- the new policy produces a 16D action
- trajectory generation accepts sampled named task state without error
- the first action is a valid open-gripper safe pose command
- a deterministic nominal smoke path (with canonical sampled state or fake env shim) reaches the intended terminal interface contract without shape/reward mismatches
Keep the tests unit-level; do not require a full MuJoCo rollout for every assertion.
- [ ] **Step 2: Write failing tests for the scripted rollout entrypoint and a real headless smoke path**
Add coverage for both:
- the standard scripted rollout entrypoint (`roboimi/demos/diana_record_sim_episodes.py`) can select the new task sampler/policy instead of remaining sim_transfer-only
- a deterministic integration/smoke test that instantiates `make_sim_env('sim_air_insert_ring_bar', headless=True)`, resets with sampled named task state, and steps a few actions or scripted-policy outputs using the real task XML and task-specific wiring
- [ ] **Step 3: Run the scripted-policy tests and verify they fail**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env -v`
Expected:
- FAIL because the new scripted policy does not exist yet
- [ ] **Step 4: Implement the waypoint-based scripted policy**
Implement a conservative open-loop policy with phases:
- safe wait pose
- above-target approach
- descend + grasp
- dual lift
- airborne meeting alignment
- bar push-through insertion
Use fixed orientations for version 1 and follow the existing repository style from `diana_policy.py`.
- [ ] **Step 5: Re-run the scripted-policy tests to verify they pass**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env -v`
Expected:
- PASS for scripted-policy tests
- [ ] **Step 6: Run the combined verification suite for this feature**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env tests.test_eval_vla_headless tests.test_eval_vla_rollout_artifacts tests.test_train_vla_rollout_validation tests.test_robot_asset_paths -v`
Expected:
- PASS with 0 failures
- [ ] **Step 6b: Run the mandatory real headless smoke check**
Run a focused smoke command that instantiates the real task, resets with sampled state, and steps a few actions using the new scripted policy or a deterministic action sequence.
Example command (adjust module/test helper if needed):
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env.AirInsertEnvSmokeTest -v`
Expected:
- PASS, proving the real XML/assets/env wiring instantiate and step correctly in headless mode
- [ ] **Step 7: Commit Task 4**
Run:
`git add roboimi/demos/diana_air_insert_policy.py tests/test_air_insert_env.py tests/test_eval_vla_headless.py tests/test_robot_asset_paths.py roboimi/demos/vla_scripts/eval_vla.py && git commit -m "feat(policy): add scripted air insertion policy"`
---
### Task 5: Final verification and implementation review
**Files:**
- Review all files touched above
- [ ] **Step 1: Run fresh end-to-end verification before claiming completion**
Run:
`/home/droid/.conda/envs/roboimi/bin/python -m unittest tests.test_air_insert_env tests.test_eval_vla_headless tests.test_robot_asset_paths -v`
Expected:
- PASS with 0 failures
- [ ] **Step 2: Inspect git status and recent commits**
Run:
`git status --short && git log --oneline --decorate -n 8`
Expected:
- only intended feature files modified / committed
- [ ] **Step 3: Request final code review for the completed feature**
Use the requesting-code-review skill against the full diff from the feature branch starting point to current HEAD.
- [ ] **Step 4: Address any review findings and re-run verification if code changes**
If fixes are made, repeat the unittest command from Step 1.
- [ ] **Step 5: Hand off using finishing-a-development-branch**
After verification and review, use the finishing-a-development-branch skill to decide merge / PR / cleanup.
@@ -1,43 +0,0 @@
# Held-out Episode Validation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add optional held-out episode validation to main training flow, including explicit val episode selection and periodic action MSE evaluation without merging LEWM-only model features.
**Architecture:** Extend the generic dataset with optional episode filtering metadata, extend train config with explicit held-out validation knobs, and wire train_vla to choose between random split and explicit episode split. Reuse agent.predict_action_chunk for action MSE so the metric stays model-agnostic.
**Tech Stack:** Python, Hydra/OmegaConf, PyTorch, unittest
---
### Task 1: Add failing tests for dataset episode filtering and config semantics
**Files:**
- Modify: `tests/test_simple_robot_dataset_image_loading.py`
- Modify: `tests/test_train_vla_rollout_validation.py`
- [ ] Add dataset tests for `episode_indices` and `available_episode_indices`.
- [ ] Add training tests for explicit held-out episode splitting and fail-fast config validation.
- [ ] Run focused tests and verify they fail for the expected missing behavior.
### Task 2: Implement minimal dataset and training support
**Files:**
- Modify: `roboimi/vla/data/simpe_robot_dataset.py`
- Modify: `roboimi/vla/conf/config.yaml`
- Modify: `roboimi/demos/vla_scripts/train_vla.py`
- [ ] Add config keys `train.val_episode_indices` and `train.action_mse_val_freq_epochs`.
- [ ] Add optional dataset filtering by episode index plus `available_episode_indices` metadata.
- [ ] Add explicit train/val dataset builder and held-out action MSE computation.
- [ ] Log `val/action_mse` only when explicit held-out episode validation is configured.
### Task 3: Verify focused coverage
**Files:**
- Test: `tests/test_simple_robot_dataset_image_loading.py`
- Test: `tests/test_train_vla_rollout_validation.py`
- Test: `tests/test_train_vla_swanlab_logging.py`
- [ ] Run focused unittest targets for dataset filtering, held-out MSE, and SwanLab logging.
- [ ] Fix any regressions with minimal code changes.
@@ -1,242 +0,0 @@
# VLA Experiment Sweep Design
## Goal
Run a matched-configuration training sweep on `/home/droid/project/diana_sim/sim_transfer` for the aligned Transformer diffusion policy, comparing:
1. ResNet ImageNet pretraining vs no pretraining under matched settings.
2. Transformer width/depth choices across a 3×3 grid.
The sweep should use the full dataset, headless rollout validation every 20 epochs, SwanLab logging, and a total budget of about 200 epochs per run.
## Current Context
- Dataset: `100` episodes, `70000` frame-level samples.
- Cameras used as image conditions: `r_vis`, `top`, `front`.
- Current policy family: ResNet visual encoder + Transformer DDPM head.
- Current training path already supports:
- full-dataset training (`val_split=0.0`)
- checkpoint-time and epoch-time rollout hooks
- headless eval path
- SwanLab logging
- best-checkpoint selection by rollout reward once rollout metrics exist
- Current trainer behavior that matters for execution:
- step checkpoints are written to `checkpoints/` relative to the Hydra job working directory
- epoch-time rollout validation is controlled by `train.rollout_val_freq_epochs`
- checkpoint-time rollout validation is separately controlled by `train.rollout_validate_on_checkpoint`
- per-run log files, pid files, and run directories must be created by the external launcher, not by the trainer itself
## Derived Schedule
For a chosen global batch size `B`, define:
- `steps_per_epoch = floor(70000 / B)` because training currently uses `drop_last=True` when the train set is larger than the batch.
- `rollout_freq_steps = 20 * steps_per_epoch`
- `max_steps = 200 * steps_per_epoch`
Example at `B = 32`:
- `steps_per_epoch = 2187`
- `rollout_freq_steps = 43740`
- `max_steps = 437400`
This schedule must be recomputed after the batch-size probe chooses the final shared batch size.
## Experiment Strategy
### Recommended approach
Use one **shared batch size across all runs**, chosen by probing the largest model that will appear in the sweep. This preserves fairness while still using as much GPU memory as safely available.
### Why not per-run max batch size
Allowing each model to use a different batch size would add another changing variable, making the 11-run comparison harder to interpret.
### Unique executions vs logical comparisons
There are `11` logical comparison points in the user request (`2` for the pretraining comparison and `9` for the emb/layer grid), but only `10` **unique** training configurations because:
- the pretrained baseline in Section A (`pretrained_backbone_weights=IMAGENET1K_V1`, `n_emb=128`, `n_layer=4`) is also one cell of the Section B sweep.
This design will therefore run **10 unique training jobs**, and the pretrained-on baseline will be reused as the `(128, 4)` architecture-sweep point. This avoids wasting one full 200-epoch run on an identical configuration.
The reused baseline has one canonical identity throughout execution:
- run name stem: `sim-transfer-baseline-pretrain-on-emb128-layer4`
- this same run is counted both as:
- the pretrained-on arm of the 2-point pretraining comparison
- the `(n_emb=128, n_layer=4)` point of the 9-point architecture sweep
## Batch-Size Probe
Before launching the full sweep:
1. Probe the largest planned model:
- `n_emb=384`
- `n_layer=12`
- ResNet pretrained enabled
2. Try candidate batch sizes in ascending order:
- `32`
- `48`
- `64`
- optionally `80` if `64` is clearly safe
3. For each candidate, run a short GPU smoke training with:
- real dataset
- three cameras
- no SwanLab
- no rollout validation
- `max_steps=4`
4. A candidate **passes** if all 4 steps finish without:
- CUDA OOM
- process crash / abort
- NaN / Inf loss
5. Choose the **highest passing** candidate and use it for all sweep runs.
Learning rate should scale linearly using the current training rule:
- config baseline: `lr=1e-4` at `batch_size=16`
- equivalent rule: `lr = 1e-4 * (batch_size / 16)`
- equivalently: `lr = 2e-4 * (batch_size / 32)`
## Experiment Matrix
Total logical comparison points: `11`
Total unique training executions: `10`
### A. ResNet pretraining comparison (2 runs)
Fixed Transformer settings:
- `n_emb=128`
- `n_layer=4`
- `n_head=4`
- `freeze_backbone=true`
- `use_separate_rgb_encoder_per_camera=true`
Interpretation note:
- This comparison keeps the current frozen-backbone recipe unchanged.
- Therefore, the `pretrained_backbone_weights=null` run measures **random frozen visual features vs ImageNet frozen visual features**.
- It does **not** test end-to-end training of the ResNet from scratch; that would be a separate experiment.
Runs:
1. `pretrained_backbone_weights=IMAGENET1K_V1`
2. `pretrained_backbone_weights=null`
### B. Transformer architecture sweep (9 runs)
Fixed visual backbone setting:
- `pretrained_backbone_weights=IMAGENET1K_V1`
- `freeze_backbone=true`
- `use_separate_rgb_encoder_per_camera=true`
- `n_head=4`
Rationale:
- The 9-point emb/layer sweep fixes `pretrained_backbone_weights=IMAGENET1K_V1`.
- The separate 2-run comparison in Section A isolates the effect of ResNet pretraining at the baseline architecture.
Grid:
- `n_emb ∈ {128, 256, 384}`
- `n_layer ∈ {4, 8, 12}`
## Shared Training Configuration
All full runs should use:
- dataset: `/home/droid/project/diana_sim/sim_transfer`
- cameras: `[r_vis, top, front]`
- device: `cuda`
- `val_split=0.0`
- `num_workers=12`
- `seed=42`
- headless rollout validation
- `rollout_val_freq_epochs=20`
- `rollout_validate_on_checkpoint=false`
- `rollout_num_episodes=3`
- `use_swanlab=true`
- shared batch size from probe
- shared learning-rate rule from probe
- `max_steps` recomputed to represent about `200` epochs
Reproducibility note:
- `train.seed=42` will be fixed and recorded for all runs.
- Under the current `train_vla.py`, this does **not** make the runs bitwise deterministic when `val_split=0.0`, because model initialization and shuffled training order are not fully seeded by the trainer today.
- The sweep is therefore treated as a **matched-config comparison**, not a strict deterministic benchmark.
## Rollout / Validation Policy
- No held-out val split will be used.
- Loss remains a fallback best-model metric until the first rollout reward exists.
- After rollout metrics begin, the best checkpoint should be chosen by **higher average rollout reward**.
- Rollout should run with:
- no GUI
- CPU eval device
- current evaluation helper path
## Execution Model
Runs should be launched **serially**, not concurrently, because:
- the user currently has one GPU available
- concurrent jobs would reduce throughput and complicate memory management
- serial execution preserves clean logs and easier resume semantics
Each run gets:
- a dedicated run directory under `runs/`
- its own log file
- its own pid / launcher metadata if backgrounded
- a unique SwanLab run name
- `hydra.job.chdir=true`
- `hydra.run.dir=<run_dir>` so the trainer-local `checkpoints/` land inside the run directory
## Naming Convention
Suggested run name pattern:
- pretraining comparison:
- `sim-transfer-baseline-pretrain-on-emb128-layer4-bs{B}-lr{LR}-e200`
- `sim-transfer-pretrain-off-bs{B}-lr{LR}-e200`
- architecture sweep:
- `(128,4)` reuses `sim-transfer-baseline-pretrain-on-emb128-layer4-bs{B}-lr{LR}-e200`
- `sim-transfer-emb{E}-layer{L}-bs{B}-lr{LR}-e200`
## Success Criteria
The sweep is successful when:
1. A shared safe batch size has been found.
2. All 10 unique runs have launchable commands and isolated run directories.
3. Each run trains for about 200 epochs worth of steps.
4. Rollout validation occurs every 20 epochs.
5. SwanLab records train metrics and rollout rewards.
6. Final comparison can be made using best rollout average reward and training traces across:
- 2-point pretraining comparison
- 9-point emb/layer sweep
## Non-Goals
- No change to dataset format.
- No change to model architecture beyond the requested pretraining toggle and Transformer width/depth sweep.
- No attempt to equalize wall-clock time across models.
- No multi-GPU scheduling.
## Risks and Mitigations
### Risk: larger models may OOM
Mitigation: probe the largest model first and choose one shared safe batch size.
### Risk: long runs may be interrupted
Mitigation: use per-run directories, periodic checkpoints, and resumable launch commands.
### Risk: rollout validation increases total training time
Mitigation: keep rollout headless, CPU-based, and limited to 3 episodes every 20 epochs.
@@ -1,316 +0,0 @@
# sim_air_insert_ring_bar Design
## Summary
Add a new independent MuJoCo simulation task named `sim_air_insert_ring_bar` that keeps the existing dual-Diana tabletop setup but replaces the single transfer box with two randomized objects:
- a square ring block grasped by the left arm
- a square bar block grasped by the right arm
The task is to pick both objects off the table and complete an in-air insertion where the bar truly passes through the ring aperture. The existing `sim_transfer` task must remain unchanged.
## Goals
- Reuse the current dual-Diana EE-control simulation stack
- Keep the same table/base robot arrangement as the existing transfer task
- Add an independent task entrypoint and scene definition
- Randomize planar placement of both objects within left/right task-specific regions
- Implement reward staging for contact, lift, and successful in-air insertion
- Add a scripted policy that performs pick, lift, align, and in-air insertion
- Preserve compatibility with existing environment creation, evaluation, and rollout patterns
## Non-Goals
- No random yaw in the first version
- No visual servoing or closed-loop insertion controller
- No general multi-task environment framework refactor
- No guarantee that the VLA training stack is immediately tuned for this new task
- No replacement or behavior change for `sim_transfer`
## Task Name
Use a new task name:
- `sim_air_insert_ring_bar`
This task should be exposed alongside `sim_transfer`, not as a replacement.
## Scene Geometry
### Shared Base Scene
Keep the dual Diana robot, the table, and the existing camera layout conceptually unchanged.
### Ring Block
Represent the square ring as a rigid free body composed from simple MuJoCo box geoms rather than an external mesh.
Dimensions:
- outer side length: 68 mm
- inner aperture side length: 32 mm
- thickness: 18 mm
- ring wall width: 18 mm
The ring should behave as a single object body with a single free joint.
### Bar Block
Represent the bar as a rigid free body with a single box geom.
Dimensions:
- length: 90 mm
- cross-section: 18 mm x 18 mm
The bar should also be a single free-joint body.
## Initial Placement / Reset
The first version uses position-only randomization with fixed orientation. Reset sampling stays **caller-driven**, matching the existing `sim_transfer` usage pattern in rollout/eval code: a helper samples task state, then callers pass that state into `env.reset(...)`.
Use an explicit sampled task-state structure with named fields:
- `ring_pos`: 3D position
- `ring_quat`: fixed 4D quaternion for version 1
- `bar_pos`: 3D position
- `bar_quat`: fixed 4D quaternion for version 1
Behavior:
- ring block: randomized only in a left-side planar sampling region
- bar block: randomized only in a right-side planar sampling region
- both objects start flat on the table
- both objects use fixed orientation at reset
- no random yaw, tilt, or flip in this version
The sampling regions should be chosen conservatively so that:
- the left arm can comfortably reach and grasp the ring
- the right arm can comfortably reach and grasp the bar
- scripted open-loop pick trajectories remain feasible
## Control / Action Interface
Reuse the current 16D EE-space action convention already used by the dual-Diana position-control environment:
- left arm EE pose: 7D (`xyz + quat`)
- right arm EE pose: 7D (`xyz + quat`)
- left gripper command: 1D
- right gripper command: 1D
The new task should continue using EE targets transformed through the existing IK-based control path.
## Environment Structure
Implement this as a new task-specific environment path while reusing the existing dual-Diana simulation base where possible.
Expected responsibilities:
- scene instantiation for the ring+bar setup
- task reset for randomized object placement
- environment-state accessors for both objects
- reward computation
- in-air insertion success detection
The environment factory must dispatch by task name and leave the `sim_transfer` branch unchanged.
## Observation / Environment State
The task should retain the current observation structure style used by the dual-Diana environment:
- `qpos`
- multi-camera images
For task state access, the environment should expose a stable `env_state` vector with this exact order:
- `ring_pos[0:3]`
- `ring_quat[3:7]`
- `bar_pos[7:10]`
- `bar_quat[10:14]`
This 14D state should be sufficient for scripted-policy debugging and future rollout analysis, while reset itself remains caller-driven via the named task-state helper structure above.
## Reward Design
Use staged rewards in the same spirit as the current task, returning the highest achieved stage rather than accumulating one-time sparse bonuses per event.
Maximum reward:
- `max_reward = 5`
Reward stages:
1. left gripper touches the ring block
2. right gripper touches the bar block
3. ring block is lifted off the table
4. bar block is lifted off the table
5. while both objects are off the table, the bar truly passes through the ring aperture
Notes:
- contact rewards are intended as grasp-progress stages
- lift rewards require the object to be off the table, not merely touched
- final success reward only applies when both objects are airborne
## Success Detection
Success must **not** be based on a centerline-only check.
A centerline-only test is insufficient because:
- the bar has thickness, so a centerline can pass through while the body cannot
- a square bar with imperfect orientation can have its centerline inside the aperture while its corners still collide with the ring
### Required Success Semantics
A successful insertion requires all of the following:
1. the ring is off the table
2. the bar is off the table
3. the bar has actually crossed through the ring thickness direction
4. the bars finite square cross-section fits through the square aperture during that crossing
### Recommended Detection Approach
Use a task-level geometric check in Python rather than relying on contact alone.
Implementation intent:
- transform the bar geometry into the rings local frame
- reason about the bar as a finite oriented box (not a line)
- verify that the bar has crossed the ring thickness direction
- verify that the portion of the bar passing the aperture fits within the inner square opening, accounting for the bars cross-section and orientation
This geometric check is the primary success test.
### Role of Contacts
Contacts may still be used for:
- grasp-stage rewards
- debugging / diagnostics
But contact alone should **not** be the sole criterion for insertion success, since:
- a true clean insertion may have limited aperture-wall contact
- persistent contact can also happen while the bar is jammed and not actually inserted
## Scripted Policy
Add a new task-specific scripted policy for `sim_air_insert_ring_bar`.
### Policy Intent
The first version prioritizes a conservative, reliable open-loop demonstration rather than an optimized trajectory.
### Action Phases
The scripted policy should follow these phases:
1. move both arms to safe initial / waiting poses with grippers open
2. move left arm above the ring and right arm above the bar
3. descend and grasp the assigned objects
4. lift both objects clear of the table
5. move both objects to an airborne meeting region above the table
6. hold the ring stably while aligning the bar with the aperture
7. push the bar along the intended insertion direction until the geometric success condition is met
### Grasp Assignment
- left arm: ring only
- right arm: bar only
### Motion Style
Keep the current repository style:
- waypoint-based trajectory definition
- open-loop interpolation between waypoints
- fixed grasp orientation in the first version
No adaptive replanning is required for the first version.
## Files / Integration Scope
The implementation is expected to add task-specific files rather than broadly refactoring the codebase.
Likely additions / changes:
- a new MuJoCo scene XML for the ring+bar task
- one or more XML fragments defining the two new objects
- a new task-specific dual-Diana environment file
- robot asset wiring for the new scene XML
- reset sampling helpers for the new task
- task registration in constants / environment factory paths
- a new scripted policy file
- focused tests for task creation, reset, rewards, success detection, and scripted policy shape/smoke behavior
## Testing Requirements
At minimum, add regression coverage for:
### Environment Creation
- the new task can be created via the task factory
- the existing `sim_transfer` task remains unchanged
### Reset / Sampling
- ring reset positions are inside the left sampling region
- bar reset positions are inside the right sampling region
- reset orientation is fixed as intended
### Environment State
- environment-state access returns both object poses in the expected structure
### Success Detection
Must include both positive and negative cases.
Positive case:
- a configuration where the finite bar truly passes through the ring aperture is detected as success
Negative cases:
- centerline-inside but finite body would clip the aperture
- not enough depth / not actually crossing the ring thickness direction
- one or both objects still on the table
### Reward Logic
- left contact stage
- right contact stage
- ring lift stage
- bar lift stage
- final success stage with `max_reward = 5`
### Scripted Policy
At minimum:
- policy emits valid 16D actions
- trajectory generation does not error
- rollout smoke path can step through the new environment
## Risks / Constraints
- MuJoCo contact naming must remain stable enough for stage rewards
- geometric insertion checks must be strict enough to avoid false positives but not so brittle that numerically valid insertions are missed
- scripted open-loop insertion may require conservative alignment and lift heights to keep the first version reliable
## Acceptance Criteria
The feature is complete when all of the following are true:
- `sim_air_insert_ring_bar` is creatable as an independent task
- the scene contains the dual Diana, table, ring block, and bar block
- reset randomizes ring and bar positions in left/right planar regions with fixed orientation
- the environment exposes task state for both objects
- staged rewards progress to `max_reward = 5`
- final success is based on finite-geometry insertion semantics, not a centerline-only shortcut
- a new scripted policy can execute the intended pick-lift-align-insert behavior in the new environment
- a canonical nominal smoke path (unit-level or deterministic integration-level) exists for the new scripted-policy interface so success is not judged purely by interpretation
- existing `sim_transfer` behavior is preserved
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

@@ -1,216 +0,0 @@
{
"akita_black_bowl_1": {
"min": [
-0.10497,
-0.014979,
0.898407
],
"max": [
-0.075125,
0.013868,
0.899216
],
"range": [
0.029845,
0.028847,
0.000809
],
"std": [
0.009227,
0.008342,
0.000113
]
},
"plate_1": {
"min": [
0.035635,
-0.034313,
0.902506
],
"max": [
0.065008,
-0.008729,
0.902506
],
"range": [
0.029373,
0.025583,
1e-06
],
"std": [
0.009018,
0.007293,
0.0
]
},
"wine_bottle_1": {
"min": [
-0.214447,
-0.064733,
0.898875
],
"max": [
-0.186022,
-0.035164,
0.898875
],
"range": [
0.028425,
0.029569,
0.0
],
"std": [
0.007991,
0.009218,
0.0
]
},
"cream_cheese_1": {
"min": [
-0.069231,
0.111593,
0.908921
],
"max": [
-0.030195,
0.146634,
0.908921
],
"range": [
0.039036,
0.035042,
0.0
],
"std": [
0.011587,
0.009411,
0.0
]
},
"wooden_cabinet_1": {
"min": [
0.020628,
-0.249667,
0.905
],
"max": [
0.039668,
-0.23086,
0.905
],
"range": [
0.01904,
0.018807,
0.0
],
"std": [
0.005624,
0.005887,
0.0
]
},
"flat_stove_1": {
"min": [
-0.41998,
0.200256,
0.905
],
"max": [
-0.400671,
0.219361,
0.905
],
"range": [
0.019309,
0.019105,
0.0
],
"std": [
0.00542,
0.005313,
0.0
]
},
"wine_rack_1": {
"min": [
-0.269738,
-0.269776,
0.92
],
"max": [
-0.250473,
-0.250372,
0.92
],
"range": [
0.019265,
0.019404,
0.0
],
"std": [
0.005695,
0.005698,
0.0
]
},
"eef_pos": {
"min": [
-0.218347,
-0.022207,
1.155793
],
"max": [
-0.191731,
0.016482,
1.188032
],
"range": [
0.026616,
0.038689,
0.03224
],
"std": [
0.005112,
0.009859,
0.007417
]
},
"joint_pos": {
"min": [
-0.032644,
-0.185945,
-0.035387,
-2.486189,
-0.01145,
2.185933,
0.745322
],
"max": [
0.021994,
-0.132522,
0.029516,
-2.375572,
0.008342,
2.274589,
0.815841
],
"range": [
0.054637,
0.053423,
0.064903,
0.110617,
0.019791,
0.088656,
0.070519
],
"std": [
0.013218,
0.012722,
0.015234,
0.021384,
0.004568,
0.020009,
0.017693
]
}
}
@@ -1,552 +0,0 @@
[
{
"init_id": 0,
"akita_black_bowl_1": {
"pos": [
-0.097626,
-0.008583,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.062074,
-0.009382,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.204233,
-0.061651,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.046026,
0.134473,
0.908921
],
"quat": [
0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 1,
"akita_black_bowl_1": {
"pos": [
-0.096428,
0.013749,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.058727,
-0.034313,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.203176,
-0.053714,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.040864,
0.121312,
0.908921
],
"quat": [
-0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 2,
"akita_black_bowl_1": {
"pos": [
-0.103282,
0.002253,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.052456,
-0.012705,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.193374,
-0.036331,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.066908,
0.118864,
0.908921
],
"quat": [
-0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 3,
"akita_black_bowl_1": {
"pos": [
-0.090777,
-0.002232,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.040521,
-0.022222,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.20432,
-0.056655,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.031539,
0.123188,
0.908921
],
"quat": [
0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 4,
"akita_black_bowl_1": {
"pos": [
-0.104225,
0.013797,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.03824,
-0.010652,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.19591,
-0.039959,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.037586,
0.133424,
0.908921
],
"quat": [
0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 5,
"akita_black_bowl_1": {
"pos": [
-0.08334,
0.002911,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.064465,
-0.028594,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.198764,
-0.053643,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.063907,
0.120699,
0.908921
],
"quat": [
0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 6,
"akita_black_bowl_1": {
"pos": [
-0.076496,
-0.00786,
0.898414
],
"quat": [
0.706935,
-0.000206,
-0.00017,
0.707279
]
},
"plate_1": {
"pos": [
0.036954,
-0.031686,
0.902506
],
"quat": [
0.707659,
-3.5e-05,
1e-06,
0.706554
]
},
"wine_bottle_1": {
"pos": [
-0.19987,
-0.056163,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.053796,
0.120126,
0.908921
],
"quat": [
-0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 7,
"akita_black_bowl_1": {
"pos": [
-0.075125,
-0.003472,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.046896,
-0.014595,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.1875,
-0.0641,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.060927,
0.116114,
0.908921
],
"quat": [
-0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 8,
"akita_black_bowl_1": {
"pos": [
-0.086798,
0.010183,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.057509,
-0.011733,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.20364,
-0.035474,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.040355,
0.128839,
0.908921
],
"quat": [
0.0,
0.0,
0.0,
1.0
]
}
},
{
"init_id": 9,
"akita_black_bowl_1": {
"pos": [
-0.081038,
-0.000113,
0.898407
],
"quat": [
0.707107,
-1.4e-05,
2e-06,
0.707107
]
},
"plate_1": {
"pos": [
0.049027,
-0.013357,
0.902506
],
"quat": [
0.707107,
-1.3e-05,
6e-06,
0.707107
]
},
"wine_bottle_1": {
"pos": [
-0.203822,
-0.057315,
0.898875
],
"quat": [
0.0,
-6e-06,
0.0,
1.0
]
},
"cream_cheese_1": {
"pos": [
-0.06023,
0.136331,
0.908921
],
"quat": [
0.0,
0.0,
0.0,
1.0
]
}
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,43 +0,0 @@
{
"checkpoint": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/checkpoints/vla_model_step_83124.pt",
"num_rollouts": 5,
"episodes": [
{
"episode_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01",
"avg_reward": 0.0,
"avg_max_reward": 0.0,
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01/rollout_front.mp4",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01/rollout_summary.json"
},
{
"episode_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02",
"avg_reward": 0.0,
"avg_max_reward": 0.0,
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02/rollout_front.mp4",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02/rollout_summary.json"
},
{
"episode_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03",
"avg_reward": 0.0,
"avg_max_reward": 0.0,
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03/rollout_front.mp4",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03/rollout_summary.json"
},
{
"episode_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04",
"avg_reward": 2.0,
"avg_max_reward": 2.0,
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04/rollout_front.mp4",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04/rollout_summary.json"
},
{
"episode_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05",
"avg_reward": 1408.0,
"avg_max_reward": 4.0,
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05/rollout_front.mp4",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05/rollout_summary.json"
}
],
"mean_avg_reward": 282.0,
"best_avg_reward": 1408.0
}
@@ -1,216 +0,0 @@
{
"num_episodes": 1,
"episode_rewards": [
0.0
],
"episode_max_rewards": [
0.0
],
"avg_reward": 0.0,
"avg_max_reward": 0.0,
"episodes": [
{
"inference_fps": 416.328579450197,
"control_fps": 16.62784691072735,
"avg_obs_read_time_ms": 2.911593945131504,
"avg_preprocess_time_ms": 6.928545483055392,
"avg_inference_time_ms": 2.401948963774235,
"avg_env_step_time_ms": 47.72096303665811,
"avg_total_time_ms": 60.14007738758145,
"num_inferences": 88,
"num_model_forwards": 88,
"num_steps": 700,
"episode_reward": 0.0,
"episode_max_reward": 0.0,
"artifact_paths": {
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01/rollout_front.mp4",
"trajectory": null,
"timing": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01/rollout_summary.json"
},
"timing_breakdown_ms": {
"obs_read": {
"mean": 2.911593945131504,
"std": 0.2611419950299626,
"min": 2.712091023568064,
"max": 7.493758981581777
},
"preprocess": {
"mean": 6.928545483055392,
"std": 0.5543077871256182,
"min": 6.473146029748023,
"max": 19.883478991687298
},
"inference": {
"mean": 2.401948963774235,
"std": 12.794224178610442,
"min": 0.49940100871026516,
"max": 327.08223100053146
},
"env_step": {
"mean": 47.72096303665811,
"std": 2.1171372236113726,
"min": 39.931224018801004,
"max": 76.46449701860547
},
"loop_total": {
"mean": 60.14007738758145,
"std": 13.188809002297118,
"min": 56.274613016285,
"max": 389.8623649729416
}
},
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.911593945131504,
"std": 0.2611419950299626,
"min": 2.712091023568064,
"max": 7.493758981581777
},
"preprocess": {
"mean": 6.928545483055392,
"std": 0.5543077871256182,
"min": 6.473146029748023,
"max": 19.883478991687298
},
"inference": {
"mean": 2.401948963774235,
"std": 12.794224178610442,
"min": 0.49940100871026516,
"max": 327.08223100053146
},
"env_step": {
"mean": 47.72096303665811,
"std": 2.1171372236113726,
"min": 39.931224018801004,
"max": 76.46449701860547
},
"loop_total": {
"mean": 60.14007738758145,
"std": 13.188809002297118,
"min": 56.274613016285,
"max": 389.8623649729416
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.8758058099562978,
"std": 0.0751506551331651,
"min": 2.712091023568064,
"max": 3.0596989672631025
},
"preprocess": {
"mean": 7.1382775310088284,
"std": 1.3865518440871207,
"min": 6.516602006740868,
"max": 19.883478991687298
},
"inference": {
"mean": 15.048258656778755,
"std": 33.453789958947034,
"min": 11.253663979005069,
"max": 327.08223100053146
},
"env_step": {
"mean": 47.788851250185296,
"std": 1.684109686362168,
"min": 39.931224018801004,
"max": 56.31602002540603
},
"loop_total": {
"mean": 73.03679807624907,
"std": 34.000319709963435,
"min": 67.7422959706746,
"max": 389.8623649729416
}
}
}
}
],
"artifact_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01",
"artifacts": {
"output_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01/rollout_summary.json",
"timing_json": null,
"trajectory_npz": null,
"video_mp4": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep01/rollout_front.mp4",
"video_camera_name": "front"
},
"avg_inference_fps": 416.328579450197,
"avg_control_fps": 16.62784691072735,
"avg_obs_read_time_ms": 2.911593945131504,
"avg_preprocess_time_ms": 6.928545483055392,
"avg_inference_time_ms": 2.401948963774235,
"avg_env_step_time_ms": 47.72096303665811,
"avg_total_time_ms": 60.14007738758145,
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.911593945131504,
"std": 0.2611419950299626,
"min": 2.712091023568064,
"max": 7.493758981581777
},
"preprocess": {
"mean": 6.928545483055392,
"std": 0.5543077871256182,
"min": 6.473146029748023,
"max": 19.883478991687298
},
"inference": {
"mean": 2.401948963774235,
"std": 12.794224178610442,
"min": 0.49940100871026516,
"max": 327.08223100053146
},
"env_step": {
"mean": 47.72096303665811,
"std": 2.1171372236113726,
"min": 39.931224018801004,
"max": 76.46449701860547
},
"loop_total": {
"mean": 60.14007738758145,
"std": 13.188809002297118,
"min": 56.274613016285,
"max": 389.8623649729416
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.8758058099562978,
"std": 0.0751506551331651,
"min": 2.712091023568064,
"max": 3.0596989672631025
},
"preprocess": {
"mean": 7.1382775310088284,
"std": 1.3865518440871207,
"min": 6.516602006740868,
"max": 19.883478991687298
},
"inference": {
"mean": 15.048258656778755,
"std": 33.453789958947034,
"min": 11.253663979005069,
"max": 327.08223100053146
},
"env_step": {
"mean": 47.788851250185296,
"std": 1.684109686362168,
"min": 39.931224018801004,
"max": 56.31602002540603
},
"loop_total": {
"mean": 73.03679807624907,
"std": 34.000319709963435,
"min": 67.7422959706746,
"max": 389.8623649729416
}
}
}
}
@@ -1,216 +0,0 @@
{
"num_episodes": 1,
"episode_rewards": [
0.0
],
"episode_max_rewards": [
0.0
],
"avg_reward": 0.0,
"avg_max_reward": 0.0,
"episodes": [
{
"inference_fps": 423.2530657749397,
"control_fps": 16.369630991018912,
"avg_obs_read_time_ms": 2.922768201263222,
"avg_preprocess_time_ms": 7.034949533491662,
"avg_inference_time_ms": 2.362652703221621,
"avg_env_step_time_ms": 48.58740326665741,
"avg_total_time_ms": 61.088731966447085,
"num_inferences": 88,
"num_model_forwards": 88,
"num_steps": 700,
"episode_reward": 0.0,
"episode_max_reward": 0.0,
"artifact_paths": {
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02/rollout_front.mp4",
"trajectory": null,
"timing": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02/rollout_summary.json"
},
"timing_breakdown_ms": {
"obs_read": {
"mean": 2.922768201263222,
"std": 0.20864628356732534,
"min": 2.7384720160625875,
"max": 4.775333974976093
},
"preprocess": {
"mean": 7.034949533491662,
"std": 0.6020329090344461,
"min": 6.419215991627425,
"max": 10.473316011484712
},
"inference": {
"mean": 2.362652703221621,
"std": 12.69272670328858,
"min": 0.4969470319338143,
"max": 324.9893220490776
},
"env_step": {
"mean": 48.58740326665741,
"std": 3.577976984950618,
"min": 41.04071902111173,
"max": 67.934600985609
},
"loop_total": {
"mean": 61.088731966447085,
"std": 13.227582693702194,
"min": 56.584795005619526,
"max": 378.0319819925353
}
},
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.922768201263222,
"std": 0.20864628356732534,
"min": 2.7384720160625875,
"max": 4.775333974976093
},
"preprocess": {
"mean": 7.034949533491662,
"std": 0.6020329090344461,
"min": 6.419215991627425,
"max": 10.473316011484712
},
"inference": {
"mean": 2.362652703221621,
"std": 12.69272670328858,
"min": 0.4969470319338143,
"max": 324.9893220490776
},
"env_step": {
"mean": 48.58740326665741,
"std": 3.577976984950618,
"min": 41.04071902111173,
"max": 67.934600985609
},
"loop_total": {
"mean": 61.088731966447085,
"std": 13.227582693702194,
"min": 56.584795005619526,
"max": 378.0319819925353
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.89267532604175,
"std": 0.11125532360169263,
"min": 2.7624600334092975,
"max": 3.5272870445623994
},
"preprocess": {
"mean": 7.164920194720088,
"std": 0.6480595307828021,
"min": 6.461958982981741,
"max": 10.244207980576903
},
"inference": {
"mean": 14.722178172881037,
"std": 33.26832928567316,
"min": 10.894106002524495,
"max": 324.9893220490776
},
"env_step": {
"mean": 48.64498521717244,
"std": 3.7269116621985248,
"min": 41.04071902111173,
"max": 67.934600985609
},
"loop_total": {
"mean": 73.61584814432057,
"std": 32.98871154259634,
"min": 67.3744379892014,
"max": 378.0319819925353
}
}
}
}
],
"artifact_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02",
"artifacts": {
"output_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02/rollout_summary.json",
"timing_json": null,
"trajectory_npz": null,
"video_mp4": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep02/rollout_front.mp4",
"video_camera_name": "front"
},
"avg_inference_fps": 423.2530657749397,
"avg_control_fps": 16.369630991018912,
"avg_obs_read_time_ms": 2.922768201263222,
"avg_preprocess_time_ms": 7.034949533491662,
"avg_inference_time_ms": 2.362652703221621,
"avg_env_step_time_ms": 48.58740326665741,
"avg_total_time_ms": 61.088731966447085,
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.922768201263222,
"std": 0.20864628356732534,
"min": 2.7384720160625875,
"max": 4.775333974976093
},
"preprocess": {
"mean": 7.034949533491662,
"std": 0.6020329090344461,
"min": 6.419215991627425,
"max": 10.473316011484712
},
"inference": {
"mean": 2.362652703221621,
"std": 12.69272670328858,
"min": 0.4969470319338143,
"max": 324.9893220490776
},
"env_step": {
"mean": 48.58740326665741,
"std": 3.577976984950618,
"min": 41.04071902111173,
"max": 67.934600985609
},
"loop_total": {
"mean": 61.088731966447085,
"std": 13.227582693702194,
"min": 56.584795005619526,
"max": 378.0319819925353
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.89267532604175,
"std": 0.11125532360169263,
"min": 2.7624600334092975,
"max": 3.5272870445623994
},
"preprocess": {
"mean": 7.164920194720088,
"std": 0.6480595307828021,
"min": 6.461958982981741,
"max": 10.244207980576903
},
"inference": {
"mean": 14.722178172881037,
"std": 33.26832928567316,
"min": 10.894106002524495,
"max": 324.9893220490776
},
"env_step": {
"mean": 48.64498521717244,
"std": 3.7269116621985248,
"min": 41.04071902111173,
"max": 67.934600985609
},
"loop_total": {
"mean": 73.61584814432057,
"std": 32.98871154259634,
"min": 67.3744379892014,
"max": 378.0319819925353
}
}
}
}
@@ -1,216 +0,0 @@
{
"num_episodes": 1,
"episode_rewards": [
0.0
],
"episode_max_rewards": [
0.0
],
"avg_reward": 0.0,
"avg_max_reward": 0.0,
"episodes": [
{
"inference_fps": 428.38141787855113,
"control_fps": 16.74224842048907,
"avg_obs_read_time_ms": 2.900395128443571,
"avg_preprocess_time_ms": 6.847853548159557,
"avg_inference_time_ms": 2.3343682948533178,
"avg_env_step_time_ms": 47.4696740059049,
"avg_total_time_ms": 59.7291340377082,
"num_inferences": 88,
"num_model_forwards": 88,
"num_steps": 700,
"episode_reward": 0.0,
"episode_max_reward": 0.0,
"artifact_paths": {
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03/rollout_front.mp4",
"trajectory": null,
"timing": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03/rollout_summary.json"
},
"timing_breakdown_ms": {
"obs_read": {
"mean": 2.900395128443571,
"std": 0.2916878594282876,
"min": 2.68459296785295,
"max": 7.19653902342543
},
"preprocess": {
"mean": 6.847853548159557,
"std": 0.21243417443892795,
"min": 6.388033041730523,
"max": 9.000881982501596
},
"inference": {
"mean": 2.3343682948533178,
"std": 11.800465870815552,
"min": 0.49768202006816864,
"max": 300.0851860269904
},
"env_step": {
"mean": 47.4696740059049,
"std": 1.7681566119287178,
"min": 41.32056800881401,
"max": 82.69464899785817
},
"loop_total": {
"mean": 59.7291340377082,
"std": 11.80948193858811,
"min": 56.34060001466423,
"max": 353.5165900248103
}
},
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.900395128443571,
"std": 0.2916878594282876,
"min": 2.68459296785295,
"max": 7.19653902342543
},
"preprocess": {
"mean": 6.847853548159557,
"std": 0.21243417443892795,
"min": 6.388033041730523,
"max": 9.000881982501596
},
"inference": {
"mean": 2.3343682948533178,
"std": 11.800465870815552,
"min": 0.49768202006816864,
"max": 300.0851860269904
},
"env_step": {
"mean": 47.4696740059049,
"std": 1.7681566119287178,
"min": 41.32056800881401,
"max": 82.69464899785817
},
"loop_total": {
"mean": 59.7291340377082,
"std": 11.80948193858811,
"min": 56.34060001466423,
"max": 353.5165900248103
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.850530921105846,
"std": 0.06315892633423309,
"min": 2.729182015173137,
"max": 2.99588602501899
},
"preprocess": {
"mean": 6.963662055733783,
"std": 0.3317973295122339,
"min": 6.484561017714441,
"max": 9.000881982501596
},
"inference": {
"mean": 14.542899646171877,
"std": 30.613461495155526,
"min": 11.056077957618982,
"max": 300.0851860269904
},
"env_step": {
"mean": 47.46852555862543,
"std": 0.9486706652557946,
"min": 41.32056800881401,
"max": 49.64756895788014
},
"loop_total": {
"mean": 72.01395506920313,
"std": 30.190598270939223,
"min": 67.28241901146248,
"max": 353.5165900248103
}
}
}
}
],
"artifact_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03",
"artifacts": {
"output_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03/rollout_summary.json",
"timing_json": null,
"trajectory_npz": null,
"video_mp4": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep03/rollout_front.mp4",
"video_camera_name": "front"
},
"avg_inference_fps": 428.38141787855113,
"avg_control_fps": 16.74224842048907,
"avg_obs_read_time_ms": 2.900395128443571,
"avg_preprocess_time_ms": 6.847853548159557,
"avg_inference_time_ms": 2.3343682948533178,
"avg_env_step_time_ms": 47.4696740059049,
"avg_total_time_ms": 59.7291340377082,
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.900395128443571,
"std": 0.2916878594282876,
"min": 2.68459296785295,
"max": 7.19653902342543
},
"preprocess": {
"mean": 6.847853548159557,
"std": 0.21243417443892795,
"min": 6.388033041730523,
"max": 9.000881982501596
},
"inference": {
"mean": 2.3343682948533178,
"std": 11.800465870815552,
"min": 0.49768202006816864,
"max": 300.0851860269904
},
"env_step": {
"mean": 47.4696740059049,
"std": 1.7681566119287178,
"min": 41.32056800881401,
"max": 82.69464899785817
},
"loop_total": {
"mean": 59.7291340377082,
"std": 11.80948193858811,
"min": 56.34060001466423,
"max": 353.5165900248103
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.850530921105846,
"std": 0.06315892633423309,
"min": 2.729182015173137,
"max": 2.99588602501899
},
"preprocess": {
"mean": 6.963662055733783,
"std": 0.3317973295122339,
"min": 6.484561017714441,
"max": 9.000881982501596
},
"inference": {
"mean": 14.542899646171877,
"std": 30.613461495155526,
"min": 11.056077957618982,
"max": 300.0851860269904
},
"env_step": {
"mean": 47.46852555862543,
"std": 0.9486706652557946,
"min": 41.32056800881401,
"max": 49.64756895788014
},
"loop_total": {
"mean": 72.01395506920313,
"std": 30.190598270939223,
"min": 67.28241901146248,
"max": 353.5165900248103
}
}
}
}
@@ -1,216 +0,0 @@
{
"num_episodes": 1,
"episode_rewards": [
2.0
],
"episode_max_rewards": [
2.0
],
"avg_reward": 2.0,
"avg_max_reward": 2.0,
"episodes": [
{
"inference_fps": 425.1027328138825,
"control_fps": 17.116880794537646,
"avg_obs_read_time_ms": 2.8568991100681678,
"avg_preprocess_time_ms": 6.912527064227366,
"avg_inference_time_ms": 2.3523725509377464,
"avg_env_step_time_ms": 46.12416496566896,
"avg_total_time_ms": 58.42185921626099,
"num_inferences": 88,
"num_model_forwards": 88,
"num_steps": 700,
"episode_reward": 2.0,
"episode_max_reward": 2.0,
"artifact_paths": {
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04/rollout_front.mp4",
"trajectory": null,
"timing": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04/rollout_summary.json"
},
"timing_breakdown_ms": {
"obs_read": {
"mean": 2.8568991100681678,
"std": 0.20988493064877758,
"min": 2.6854430325329304,
"max": 5.505044013261795
},
"preprocess": {
"mean": 6.912527064227366,
"std": 0.3468982959934229,
"min": 6.422812002710998,
"max": 13.460688991472125
},
"inference": {
"mean": 2.3523725509377464,
"std": 12.345277540311537,
"min": 0.4927710397168994,
"max": 315.18344301730394
},
"env_step": {
"mean": 46.12416496566896,
"std": 1.6786026968893601,
"min": 40.27245403267443,
"max": 69.19108401052654
},
"loop_total": {
"mean": 58.42185921626099,
"std": 12.395693709763066,
"min": 54.73730497760698,
"max": 368.8187550287694
}
},
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.8568991100681678,
"std": 0.20988493064877758,
"min": 2.6854430325329304,
"max": 5.505044013261795
},
"preprocess": {
"mean": 6.912527064227366,
"std": 0.3468982959934229,
"min": 6.422812002710998,
"max": 13.460688991472125
},
"inference": {
"mean": 2.3523725509377464,
"std": 12.345277540311537,
"min": 0.4927710397168994,
"max": 315.18344301730394
},
"env_step": {
"mean": 46.12416496566896,
"std": 1.6786026968893601,
"min": 40.27245403267443,
"max": 69.19108401052654
},
"loop_total": {
"mean": 58.42185921626099,
"std": 12.395693709763066,
"min": 54.73730497760698,
"max": 368.8187550287694
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.8195713563367133,
"std": 0.05933493418683723,
"min": 2.6968690217472613,
"max": 2.953937044367194
},
"preprocess": {
"mean": 7.006697107333986,
"std": 0.4376351299601935,
"min": 6.50148798013106,
"max": 10.386412963271141
},
"inference": {
"mean": 14.704165457012344,
"std": 32.214912229767435,
"min": 11.083905992563814,
"max": 315.18344301730394
},
"env_step": {
"mean": 46.088106907105114,
"std": 1.088099613967191,
"min": 40.27245403267443,
"max": 49.108261009678245
},
"loop_total": {
"mean": 70.80417735150232,
"std": 31.96448918725213,
"min": 65.67699601873755,
"max": 368.8187550287694
}
}
}
}
],
"artifact_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04",
"artifacts": {
"output_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04/rollout_summary.json",
"timing_json": null,
"trajectory_npz": null,
"video_mp4": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep04/rollout_front.mp4",
"video_camera_name": "front"
},
"avg_inference_fps": 425.1027328138825,
"avg_control_fps": 17.116880794537646,
"avg_obs_read_time_ms": 2.8568991100681678,
"avg_preprocess_time_ms": 6.912527064227366,
"avg_inference_time_ms": 2.3523725509377464,
"avg_env_step_time_ms": 46.12416496566896,
"avg_total_time_ms": 58.42185921626099,
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.8568991100681678,
"std": 0.20988493064877758,
"min": 2.6854430325329304,
"max": 5.505044013261795
},
"preprocess": {
"mean": 6.912527064227366,
"std": 0.3468982959934229,
"min": 6.422812002710998,
"max": 13.460688991472125
},
"inference": {
"mean": 2.3523725509377464,
"std": 12.345277540311537,
"min": 0.4927710397168994,
"max": 315.18344301730394
},
"env_step": {
"mean": 46.12416496566896,
"std": 1.6786026968893601,
"min": 40.27245403267443,
"max": 69.19108401052654
},
"loop_total": {
"mean": 58.42185921626099,
"std": 12.395693709763066,
"min": 54.73730497760698,
"max": 368.8187550287694
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.8195713563367133,
"std": 0.05933493418683723,
"min": 2.6968690217472613,
"max": 2.953937044367194
},
"preprocess": {
"mean": 7.006697107333986,
"std": 0.4376351299601935,
"min": 6.50148798013106,
"max": 10.386412963271141
},
"inference": {
"mean": 14.704165457012344,
"std": 32.214912229767435,
"min": 11.083905992563814,
"max": 315.18344301730394
},
"env_step": {
"mean": 46.088106907105114,
"std": 1.088099613967191,
"min": 40.27245403267443,
"max": 49.108261009678245
},
"loop_total": {
"mean": 70.80417735150232,
"std": 31.96448918725213,
"min": 65.67699601873755,
"max": 368.8187550287694
}
}
}
}
@@ -1,216 +0,0 @@
{
"num_episodes": 1,
"episode_rewards": [
1408.0
],
"episode_max_rewards": [
4.0
],
"avg_reward": 1408.0,
"avg_max_reward": 4.0,
"episodes": [
{
"inference_fps": 421.5143683898348,
"control_fps": 15.98697584872221,
"avg_obs_read_time_ms": 2.916401576450361,
"avg_preprocess_time_ms": 6.9587834345709,
"avg_inference_time_ms": 2.372398368814694,
"avg_env_step_time_ms": 50.12335285793857,
"avg_total_time_ms": 62.55091703787912,
"num_inferences": 88,
"num_model_forwards": 88,
"num_steps": 700,
"episode_reward": 1408.0,
"episode_max_reward": 4.0,
"artifact_paths": {
"video": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05/rollout_front.mp4",
"trajectory": null,
"timing": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05/rollout_summary.json"
},
"timing_breakdown_ms": {
"obs_read": {
"mean": 2.916401576450361,
"std": 0.19681075249117103,
"min": 2.723354031331837,
"max": 4.304414032958448
},
"preprocess": {
"mean": 6.9587834345709,
"std": 0.32918428959573737,
"min": 6.440367025788873,
"max": 10.836776986252517
},
"inference": {
"mean": 2.372398368814694,
"std": 12.755981125405425,
"min": 0.4968460416421294,
"max": 326.6250739688985
},
"env_step": {
"mean": 50.12335285793857,
"std": 2.0541933730540083,
"min": 41.243349958676845,
"max": 59.02870395220816
},
"loop_total": {
"mean": 62.55091703787912,
"std": 12.685506605997542,
"min": 57.05423402832821,
"max": 379.86371299484745
}
},
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.916401576450361,
"std": 0.19681075249117103,
"min": 2.723354031331837,
"max": 4.304414032958448
},
"preprocess": {
"mean": 6.9587834345709,
"std": 0.32918428959573737,
"min": 6.440367025788873,
"max": 10.836776986252517
},
"inference": {
"mean": 2.372398368814694,
"std": 12.755981125405425,
"min": 0.4968460416421294,
"max": 326.6250739688985
},
"env_step": {
"mean": 50.12335285793857,
"std": 2.0541933730540083,
"min": 41.243349958676845,
"max": 59.02870395220816
},
"loop_total": {
"mean": 62.55091703787912,
"std": 12.685506605997542,
"min": 57.05423402832821,
"max": 379.86371299484745
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.8879472850927743,
"std": 0.09183274389315361,
"min": 2.758314018137753,
"max": 3.517468983773142
},
"preprocess": {
"mean": 7.111402045914226,
"std": 0.5243812452094909,
"min": 6.641154002863914,
"max": 10.836776986252517
},
"inference": {
"mean": 14.798059454320041,
"std": 33.432189809551524,
"min": 10.985016997437924,
"max": 326.6250739688985
},
"env_step": {
"mean": 49.97409828435841,
"std": 2.1747279952000875,
"min": 41.243349958676845,
"max": 56.928464968223125
},
"loop_total": {
"mean": 74.9630101153426,
"std": 32.74896535841012,
"min": 67.58100300794467,
"max": 379.86371299484745
}
}
}
}
],
"artifact_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05",
"artifacts": {
"output_dir": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05",
"summary_json": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05/rollout_summary.json",
"timing_json": null,
"trajectory_npz": null,
"video_mp4": "/home/droid/roboimi_imf_attnres/runs/sim-transfer-imf-attnres-emb384-l12-ph16-step150k-roll5x5-lr2p5e4-restart-20260402-104553/rollout-step83124-front-5x-20260403-090744/ep05/rollout_front.mp4",
"video_camera_name": "front"
},
"avg_inference_fps": 421.5143683898348,
"avg_control_fps": 15.98697584872221,
"avg_obs_read_time_ms": 2.916401576450361,
"avg_preprocess_time_ms": 6.9587834345709,
"avg_inference_time_ms": 2.372398368814694,
"avg_env_step_time_ms": 50.12335285793857,
"avg_total_time_ms": 62.55091703787912,
"timing_summary": {
"count": 700,
"model_forward_count": 88,
"all_steps_ms": {
"obs_read": {
"mean": 2.916401576450361,
"std": 0.19681075249117103,
"min": 2.723354031331837,
"max": 4.304414032958448
},
"preprocess": {
"mean": 6.9587834345709,
"std": 0.32918428959573737,
"min": 6.440367025788873,
"max": 10.836776986252517
},
"inference": {
"mean": 2.372398368814694,
"std": 12.755981125405425,
"min": 0.4968460416421294,
"max": 326.6250739688985
},
"env_step": {
"mean": 50.12335285793857,
"std": 2.0541933730540083,
"min": 41.243349958676845,
"max": 59.02870395220816
},
"loop_total": {
"mean": 62.55091703787912,
"std": 12.685506605997542,
"min": 57.05423402832821,
"max": 379.86371299484745
}
},
"model_forward_steps_ms": {
"obs_read": {
"mean": 2.8879472850927743,
"std": 0.09183274389315361,
"min": 2.758314018137753,
"max": 3.517468983773142
},
"preprocess": {
"mean": 7.111402045914226,
"std": 0.5243812452094909,
"min": 6.641154002863914,
"max": 10.836776986252517
},
"inference": {
"mean": 14.798059454320041,
"std": 33.432189809551524,
"min": 10.985016997437924,
"max": 326.6250739688985
},
"env_step": {
"mean": 49.97409828435841,
"std": 2.1747279952000875,
"min": 41.243349958676845,
"max": 56.928464968223125
},
"loop_total": {
"mean": 74.9630101153426,
"std": 32.74896535841012,
"min": 67.58100300794467,
"max": 379.86371299484745
}
}
}
}
@@ -1,41 +0,0 @@
{
"status": "success",
"task_name": "sim_transfer",
"success_video": "/data/roboimi_rollout_videos/scripted_success_20260529/sim_transfer/success_front.mp4",
"max_reward_required": 4,
"attempts": [
{
"attempt": 1,
"task_name": "sim_transfer",
"success": true,
"sum_reward": 1238.0,
"max_reward": 4.0,
"max_reward_required": 4,
"max_reward_step": 503,
"episode_len": 700,
"elapsed_s": 29.355701208114624,
"sim_steps_per_wall_s": 23.845453223461178,
"video_path": "/data/roboimi_rollout_videos/scripted_success_20260529/sim_transfer/attempt_001_front.mp4",
"task_state": [
0.0338307802064175,
0.9599788062599779,
0.47
],
"reward_trace": [
{
"step": 0,
"reward": 0.0
},
{
"step": 278,
"reward": 2.0
},
{
"step": 503,
"reward": 4.0
}
]
}
],
"created_at": "2026-05-29T19:28:23+0800"
}
@@ -1,80 +0,0 @@
{
"status": "success",
"task_name": "sim_air_insert_socket_peg",
"success_video": "/data/roboimi_rollout_videos/scripted_success_20260529/sim_air_insert_socket_peg/success_front.mp4",
"max_reward_required": 5,
"attempts": [
{
"attempt": 1,
"task_name": "sim_air_insert_socket_peg",
"success": true,
"sum_reward": 1942.0,
"max_reward": 5.0,
"max_reward_required": 5,
"max_reward_step": 583,
"episode_len": 750,
"elapsed_s": 35.83620357513428,
"sim_steps_per_wall_s": 20.92855618557775,
"video_path": "/data/roboimi_rollout_videos/scripted_success_20260529/sim_air_insert_socket_peg/attempt_001_front.mp4",
"task_state": {
"socket_pos": [
-0.14154230058193207,
0.9299893975257874,
0.47200000286102295
],
"socket_quat": [
1.0,
0.0,
0.0,
0.0
],
"peg_pos": [
0.13059355318546295,
0.881460428237915,
0.46000000834465027
],
"peg_quat": [
1.0,
0.0,
0.0,
0.0
]
},
"reward_trace": [
{
"step": 0,
"reward": 0.0
},
{
"step": 132,
"reward": 1.0
},
{
"step": 171,
"reward": 2.0
},
{
"step": 185,
"reward": 3.0
},
{
"step": 186,
"reward": 2.0
},
{
"step": 190,
"reward": 3.0
},
{
"step": 351,
"reward": 4.0
},
{
"step": 583,
"reward": 5.0
}
]
}
],
"created_at": "2026-05-29T19:31:20+0800"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,191 +0,0 @@
{
"agent": {
"vision_backbone": {
"_target_": "roboimi.vla.models.backbones.resnet_diffusion.ResNetDiffusionBackbone",
"vision_backbone": "resnet18",
"pretrained_backbone_weights": null,
"vision_backbone_mode": "resnet",
"freeze_backbone": false,
"input_shape": [
3,
224,
224
],
"crop_shape": null,
"crop_is_random": true,
"use_group_norm": true,
"spatial_softmax_num_keypoints": 32,
"use_separate_rgb_encoder_per_camera": true,
"output_tokens_per_camera": false,
"num_cameras": 3,
"attnres_stem_dim": 64,
"attnres_stage_dims": [
64,
128,
256,
512
],
"attnres_stage_depths": [
2,
2,
2,
2
],
"attnres_stage_heads": [
4,
4,
8,
8
],
"attnres_stage_kv_heads": [
1,
1,
1,
1
],
"attnres_stage_window_sizes": [
7,
7,
7,
7
],
"attnres_dropout": 0.0,
"attnres_ffn_mult": 2.667,
"attnres_eps": 1e-06,
"attnres_rope_theta": 10000.0,
"camera_names": [
"r_vis",
"top",
"front"
]
},
"state_encoder": {
"_target_": "roboimi.vla.modules.encoders.IdentityStateEncoder"
},
"action_encoder": {
"_target_": "roboimi.vla.modules.encoders.IdentityActionEncoder"
},
"head": {
"_target_": "roboimi.vla.models.heads.transformer1d.Transformer1D",
"_partial_": true,
"n_layer": 18,
"n_head": 4,
"n_emb": 384,
"p_drop_emb": 0.05,
"p_drop_attn": 0.05,
"causal_attn": false,
"time_as_cond": true,
"obs_as_cond": true,
"n_cond_layers": 1,
"input_dim": 16,
"output_dim": 16,
"horizon": 16,
"n_obs_steps": 2,
"cond_dim": 208
},
"_target_": "roboimi.vla.agent.VLAAgent",
"action_dim": 16,
"obs_dim": 16,
"normalization_type": "min_max",
"pred_horizon": 16,
"obs_horizon": 2,
"num_action_steps": 8,
"camera_names": [
"r_vis",
"top",
"front"
],
"num_cams": 3,
"diffusion_steps": 100,
"inference_steps": 100,
"head_type": "transformer"
},
"data": {
"_target_": "roboimi.vla.data.simpe_robot_dataset.SimpleRobotDataset",
"dataset_dir": "/home/droid/project/diana_sim/sim_transfer",
"pred_horizon": 16,
"obs_horizon": 2,
"camera_names": [
"r_vis",
"top",
"front"
],
"image_resize_shape": [
224,
224
]
},
"eval": {
"ckpt_path": "/home/droid/project/roboimi/runs/sim-transfer-no-pretrain-ph16-emb384-l18-infer100-unfreeze-150k-roll5x5-20260331-235043/checkpoints/vla_model_best.pt",
"num_episodes": 3,
"num_workers": 1,
"cuda_devices": null,
"response_timeout_s": 300.0,
"server_startup_timeout_s": 300.0,
"max_timesteps": 700,
"device": "cuda",
"task_name": "sim_transfer",
"task_description": null,
"num_queries": 8,
"obs_horizon": 2,
"camera_names": [
"r_vis",
"top",
"front"
],
"use_smoothing": false,
"smooth_method": "ema",
"smooth_alpha": 0.3,
"headless": true,
"verbose_action": false,
"artifact_dir": null,
"save_artifacts": false,
"save_timing": false,
"save_trajectory": false,
"save_summary_json": false,
"save_trajectory_npz": false,
"save_trajectory_image": false,
"trajectory_image_camera": null,
"trajectory_image_camera_name": null,
"record_video": false,
"video_camera": null,
"video_camera_name": null,
"video_fps": 30
},
"train": {
"batch_size": 16,
"lr": 0.0001,
"max_steps": 100000,
"device": "cuda",
"disable_cudnn": false,
"num_workers": 12,
"val_split": 0.0,
"val_episode_indices": null,
"action_mse_val_freq_epochs": 0,
"seed": 42,
"log_freq": 100,
"save_freq": 2000,
"use_swanlab": false,
"swanlab_project": "roboimi-vla",
"swanlab_run_name": null,
"rollout_val_freq_epochs": 50,
"rollout_validate_on_checkpoint": false,
"rollout_num_episodes": 3,
"rollout_device": "cuda",
"rollout_num_workers": null,
"rollout_cuda_devices": null,
"rollout_response_timeout_s": 300.0,
"rollout_server_startup_timeout_s": 300.0,
"warmup_steps": 2000,
"scheduler_type": "cosine",
"min_lr": 1e-06,
"weight_decay": 1e-05,
"grad_clip": 1.0,
"pretrained_ckpt": null
},
"experiment": {
"name": "vla_diffusion",
"notes": "",
"tags": []
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,198 +0,0 @@
{
"agent": {
"vision_backbone": {
"_target_": "roboimi.vla.models.backbones.resnet_diffusion.ResNetDiffusionBackbone",
"vision_backbone": "resnet18",
"pretrained_backbone_weights": null,
"vision_backbone_mode": "resnet",
"freeze_backbone": false,
"input_shape": [
3,
224,
224
],
"crop_shape": null,
"crop_is_random": true,
"use_group_norm": true,
"spatial_softmax_num_keypoints": 32,
"use_separate_rgb_encoder_per_camera": true,
"output_tokens_per_camera": false,
"num_cameras": 3,
"attnres_stem_dim": 64,
"attnres_stage_dims": [
64,
128,
256,
512
],
"attnres_stage_depths": [
2,
2,
2,
2
],
"attnres_stage_heads": [
4,
4,
8,
8
],
"attnres_stage_kv_heads": [
1,
1,
1,
1
],
"attnres_stage_window_sizes": [
7,
7,
7,
7
],
"attnres_dropout": 0.0,
"attnres_ffn_mult": 2.667,
"attnres_eps": 1e-06,
"attnres_rope_theta": 10000.0,
"camera_names": [
"r_vis",
"top",
"front"
]
},
"state_encoder": {
"_target_": "roboimi.vla.modules.encoders.IdentityStateEncoder"
},
"action_encoder": {
"_target_": "roboimi.vla.modules.encoders.IdentityActionEncoder"
},
"head": {
"_target_": "roboimi.vla.models.heads.imf_transformer1d.IMFTransformer1D",
"_partial_": true,
"input_dim": 16,
"output_dim": 16,
"horizon": 32,
"n_obs_steps": 2,
"cond_dim": 208,
"n_layer": 12,
"n_head": 1,
"n_emb": 384,
"p_drop_emb": 0.1,
"p_drop_attn": 0.1,
"causal_attn": false,
"time_as_cond": true,
"obs_as_cond": true,
"n_cond_layers": 0,
"backbone_type": "attnres_full",
"n_kv_head": 1,
"attn_res_ffn_mult": 2.667,
"attn_res_eps": 1e-06,
"attn_res_rope_theta": 10000.0
},
"_target_": "roboimi.vla.agent_imf.IMFVLAAgent",
"action_dim": 16,
"obs_dim": 16,
"normalization_type": "min_max",
"pred_horizon": 32,
"obs_horizon": 2,
"num_action_steps": 16,
"camera_names": [
"r_vis",
"top",
"front"
],
"num_cams": 3,
"diffusion_steps": 100,
"inference_steps": 1,
"head_type": "transformer",
"loss_type": "pseudo_huber",
"pseudo_huber_delta": 1.0
},
"data": {
"_target_": "roboimi.vla.data.simpe_robot_dataset.SimpleRobotDataset",
"dataset_dir": "/home/droid/project/diana_sim/sim_transfer",
"pred_horizon": 32,
"obs_horizon": 2,
"camera_names": [
"r_vis",
"top",
"front"
],
"image_resize_shape": [
224,
224
]
},
"eval": {
"ckpt_path": "/home/droid/project/roboimi/.worktrees/feat-imf-attnres-policy/runs/sim-transfer-imf-attnres-ph32-exec16-emb384-l12-infer1-unfreeze-step50k-roll5x5-20260403-141304/checkpoints/vla_model_best.pt",
"num_episodes": 3,
"num_workers": 1,
"cuda_devices": null,
"response_timeout_s": 300.0,
"server_startup_timeout_s": 300.0,
"max_timesteps": 700,
"device": "cuda",
"task_name": "sim_transfer",
"task_description": null,
"num_queries": 16,
"obs_horizon": 2,
"camera_names": [
"r_vis",
"top",
"front"
],
"use_smoothing": false,
"smooth_method": "ema",
"smooth_alpha": 0.3,
"headless": true,
"verbose_action": false,
"artifact_dir": null,
"save_artifacts": false,
"save_timing": false,
"save_trajectory": false,
"save_summary_json": false,
"save_trajectory_npz": false,
"save_trajectory_image": false,
"trajectory_image_camera": null,
"trajectory_image_camera_name": null,
"record_video": false,
"video_camera": null,
"video_camera_name": null,
"video_fps": 30
},
"train": {
"batch_size": 16,
"lr": 0.0001,
"max_steps": 100000,
"device": "cuda",
"disable_cudnn": false,
"num_workers": 12,
"val_split": 0.0,
"val_episode_indices": null,
"action_mse_val_freq_epochs": 0,
"seed": 42,
"log_freq": 100,
"save_freq": 2000,
"use_swanlab": false,
"swanlab_project": "roboimi-vla",
"swanlab_run_name": null,
"rollout_val_freq_epochs": 50,
"rollout_validate_on_checkpoint": false,
"rollout_num_episodes": 3,
"rollout_device": "cuda",
"rollout_num_workers": null,
"rollout_cuda_devices": null,
"rollout_response_timeout_s": 300.0,
"rollout_server_startup_timeout_s": 300.0,
"warmup_steps": 2000,
"scheduler_type": "cosine",
"min_lr": 1e-06,
"weight_decay": 1e-05,
"grad_clip": 1.0,
"pretrained_ckpt": null
},
"experiment": {
"name": "vla_diffusion",
"notes": "",
"tags": []
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-200
View File
@@ -1,200 +0,0 @@
import mujoco
from mujoco import viewer
import sys
import numpy as np
import time
import threading
class MjBasicRenderer:
def __new__(cls, *args, **kwargs):
return super().__new__(cls)
def __init__(self, mj_model=None, mj_data=None):
# keyboard flag
self.render_paused = True
self.exit_flag = False
# init param
self.mj_model = mj_model
self.mj_data = mj_data
self.renderer = "viewer" # default
self.viewer = None
self._image = None
# Set up mujoco viewer
self.image_renderer = mujoco.Renderer(self.mj_model)
def __del__(self):
pass
def _init_renderer(self):
"""Initialize renderer, choose official renderer with "viewer"(joined from version 2.3.3),
another renderer with "mujoco_viewer"
"""
def key_callback(keycode):
if keycode == 32: # space
self.render_paused = not self.render_paused
elif keycode == 256: # escape
self.exit_flag = not self.exit_flag
if self.renderer == "viewer":
# This function does not block, allowing user code to continue execution.
self.viewer = viewer.launch_passive(
self.mj_model,
self.mj_data,
key_callback=key_callback,
show_left_ui=False,
show_right_ui=False,
)
self.set_renderer_config()
else:
raise ValueError("Invalid renderer for some reason.")
def render(self):
"""mujoco render"""
if self.viewer is not None and self.render_paused is True:
if self.viewer.is_running() and self.exit_flag is False:
self.viewer: viewer.Handle
self.viewer.sync()
else:
self.viewer.close()
def set_renderer_config(self):
"""Setup mujoco global config while using viewer as renderer.
It should be noted that the render thread need locked.
"""
self.viewer.cam.lookat = np.array([0.4, 0, 0.5])
self.viewer.cam.azimuth -= 0.005
with self.viewer.lock():
self.viewer.opt.flags[mujoco.mjtVisFlag.mjVIS_CONTACTPOINT] = int(
self.mj_data.time % 2
)
try:
import cv2
except ImportError:
print("Could not import cv2, please install it to enable camera viewer.")
class MjMultiRenderer(MjBasicRenderer):
# __slots__=('mj_model','mj_data','renderer','enable_camera_viewer')
def __new__(cls, *args, **kwargs):
return super().__new__(cls)
def __init__(
self,
mj_model=None,
mj_data=None,
renderer=None,
enable_camera_viewer=False,
enable_depth=False,
):
super().__init__(mj_model, mj_data)
self._depth = None
self.renderer = renderer
self._init_renderer()
self.enable_camera_viewer = enable_camera_viewer
if self.enable_camera_viewer:
self.enable_depth = enable_depth
self._init_window()
else:
self.enable_depth = False
print("No Camera View")
def __del__(self):
self.close()
def _init_renderer(self):
"""
Initialize renderer, choose official renderer with "viewer"(joined from version 2.3.3)
"""
if self.renderer == "unity":
# TODO: Support unity renderer.
raise ValueError("Unity renderer init failed for no supporting reason")
elif self.renderer == "viewer":
super()._init_renderer()
print("mujoco viewer init !")
else:
raise ValueError("renderer init failed for some reason.")
def _init_window(self, name="Camera view"):
if not self.enable_depth:
cv2.namedWindow(name, cv2.WINDOW_NORMAL)
else:
cv2.namedWindow(name, cv2.WINDOW_NORMAL)
cv2.namedWindow("Camera depth view", cv2.WINDOW_NORMAL)
def render(self):
"""render mujoco"""
if self.renderer == "viewer":
super().render()
elif self.renderer == "unity":
# TODO: Support unity renderer.
raise ValueError("Unity renderer not supported now.")
else:
raise ValueError("Invalid renderer for some reason.")
def render(self):
"""mujoco render"""
if self.viewer is not None and self.render_paused is True:
if self.viewer.is_running() and self.exit_flag is False:
self.viewer: viewer.Handle
self.viewer.sync()
else:
self.viewer.close()
def camera_render(self, cam=None):
if self.enable_camera_viewer:
if not self.enable_depth:
rgb, depth = self.render_from_camera(cam)
rgb = cv2.resize(rgb, (1920, 1600))
cv2.imshow("Camera view", rgb)
cv2.waitKey(1)
else:
rgb, depth = self.render_from_camera(cam)
cv2.imshow("Camera view", rgb)
cv2.imshow("Camera depth view", depth)
cv2.waitKey(1)
else:
print("camera info disable")
return
def render_from_camera(self, cam=None):
self.image_renderer.update_scene(self.mj_data, camera=cam)
if self.enable_depth is True:
self.image_renderer.enable_depth_rendering()
org = self.image_renderer.render()
depth = org[:, :]
self.image_renderer.disable_depth_rendering()
org = self.image_renderer.render()
image = org[:, :, ::-1]
else:
org = self.image_renderer.render()
image = org[:, :, ::-1]
depth = np.zeros([240, 320])
return image, depth
def close(self):
"""close the environment."""
if self.enable_camera_viewer and self.viewer.is_running() == False:
cv2.destroyAllWindows()
self.viewer.close()
# sys.exit(0)
# def get_cam_intrinsic(self, fovy=45.0, width=320, height=240):
# aspect = width * 1.0 / height
# fovx = np.degrees(2 * np.arctan(aspect * np.tan(np.radians(fovy / 2))))
# cx = 0.5 * width
# cy = 0.5 * height
# fx = cx / np.tan(fovx * np.pi / 180 * 0.5)
# fy = cy / np.tan(fovy * np.pi / 180 * 0.5)
# K = np.array([[fx, 0, cx],
# [0, fy, cy],
# [0, 0, 1]], dtype=np.float32)
@@ -76,7 +76,7 @@
<body name="ee_cam_left" pos="0.00 0.046 -0.075" euler="0.0 0.0 -0.0"> <body name="ee_cam_left" pos="0.00 0.046 -0.075" euler="0.0 0.0 -0.0">
<inertial pos="0 0 0" quat="1 0 0 0" mass="0" diaginertia="0 0 0" /> <inertial pos="0 0 0" quat="1 0 0 0" mass="0" diaginertia="0 0 0" />
<geom type="mesh" contype="1" conaffinity="1" group="1" rgba="0.69804 0.69804 0.69804 1" mesh="realsense_cam" /> <geom type="mesh" contype="1" conaffinity="1" group="1" rgba="0.69804 0.69804 0.69804 1" mesh="realsense_cam" />
<camera name="rs_cam_left" mode="fixed" pos="0.0 0.0 0.01" euler="0.0 9.4 0.0 " fovy="50" resolution="1920 1200"/> <camera name="rs_cam_left" mode="fixed" pos="0.0 0.0 -0.25" euler="0.0 9.4 0.0 " fovy="15" resolution="1920 1200"/>
</body> </body>
</body> </body>
<body name="l_finger_left" pos="0 0.01 0.0444"> <body name="l_finger_left" pos="0 0.01 0.0444">
@@ -1,6 +0,0 @@
<mujoco model="bi_diana_socket_peg">
<include file="./empty_world.xml" />
<include file="./table_square.xml" />
<include file="./socket_peg_objects.xml" />
<include file="./BiDianaMed_rethink.xml" />
</mujoco>
@@ -1,19 +0,0 @@
<mujoco model="socket_peg_objects">
<worldbody>
<body name="peg" pos="0.12 0.90 0.46">
<joint name="red_peg_joint" type="free" frictionloss="0.01" />
<inertial pos="0 0 0" mass="0.05" diaginertia="0.002 0.002 0.002" />
<geom condim="4" solimp="2 1 0.01" solref="0.01 1" friction="1 0.005 0.0001" pos="0 0 0" size="0.06 0.01 0.01" type="box" name="red_peg" rgba="1 0 0 1" />
</body>
<body name="socket" pos="-0.12 0.90 0.472">
<joint name="blue_socket_joint" type="free" frictionloss="0.01" />
<inertial pos="0 0 0" mass="0.05" diaginertia="0.002 0.002 0.002" />
<geom condim="4" solimp="2 1 0.01" solref="0.01 1" friction="1 0.05 0.001" pos="0 0 -0.02" size="0.06 0.018 0.002" type="box" name="socket-1" rgba="0 0 1 1" />
<geom condim="4" solimp="2 1 0.01" solref="0.01 1" friction="1 0.05 0.001" pos="0 0 0.02" size="0.06 0.018 0.002" type="box" name="socket-2" rgba="0 0 1 1" />
<geom condim="4" solimp="2 1 0.01" solref="0.01 1" friction="1 0.05 0.001" pos="0 0.02 0" size="0.06 0.002 0.018" type="box" name="socket-3" rgba="0 0 1 1" />
<geom condim="4" solimp="2 1 0.01" solref="0.01 1" friction="1 0.05 0.001" pos="0 -0.02 0" size="0.06 0.002 0.018" type="box" name="socket-4" rgba="0 0 1 1" />
<geom condim="4" solimp="2 1 0.01" solref="0.01 1" friction="1 0.005 0.0001" pos="0 0 0" size="0.04 0.01 0.01" type="box" name="pin" rgba="1 0 0 1" />
</body>
</worldbody>
</mujoco>

Some files were not shown because too many files have changed in this diff Show More