sarel commited on
Commit
8f721c5
·
verified ·
1 Parent(s): 54be433

Add GR00T-N1.7-LIBERO LIBERO smoke-test tooling, the 20260512_122756 10-rollout run artifacts (videos/frames/actions/plots/reports), setup logs, README/LICENSE/NOTICE. Built on NVIDIA Isaac-GR00T (Apache-2.0); upstream source / weights / gated backbone not included.

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +234 -0
  2. LICENSE +190 -0
  3. NOTICE +30 -0
  4. README.md +122 -0
  5. examples/LIBERO/smoke_tests/ACTIVATION_HOOK_NOTES.md +100 -0
  6. examples/LIBERO/smoke_tests/README.md +160 -0
  7. examples/LIBERO/smoke_tests/_libero_rollout_worker.py +311 -0
  8. examples/LIBERO/smoke_tests/make_video_montage.py +578 -0
  9. examples/LIBERO/smoke_tests/review_smoke_tests.py +139 -0
  10. examples/LIBERO/smoke_tests/run_10_smoke_tests.py +590 -0
  11. examples/LIBERO/smoke_tests/scenarios_10.yaml +167 -0
  12. examples/LIBERO/smoke_tests/visualize_smoke_run.py +739 -0
  13. outputs/_setup_logs/_dryrun.log +57 -0
  14. outputs/_setup_logs/_hf_download.log +4 -0
  15. outputs/_setup_logs/_libero_setup.log +439 -0
  16. outputs/_setup_logs/_server.log +85 -0
  17. outputs/_setup_logs/_uv_sync.log +6 -0
  18. outputs/_setup_logs/_venv_install.log +218 -0
  19. outputs/_setup_logs/server_live.log +20 -0
  20. outputs/_setup_logs/smoke_run.log +67 -0
  21. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/actions.npy +3 -0
  22. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00000.png +3 -0
  23. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00001.png +3 -0
  24. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00002.png +3 -0
  25. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00003.png +3 -0
  26. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00004.png +3 -0
  27. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00005.png +3 -0
  28. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00006.png +3 -0
  29. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00007.png +3 -0
  30. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00008.png +3 -0
  31. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00009.png +3 -0
  32. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00010.png +3 -0
  33. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00011.png +3 -0
  34. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00012.png +3 -0
  35. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00013.png +3 -0
  36. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00014.png +3 -0
  37. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00015.png +3 -0
  38. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00016.png +3 -0
  39. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00017.png +3 -0
  40. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00018.png +3 -0
  41. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00019.png +3 -0
  42. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00020.png +3 -0
  43. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00021.png +3 -0
  44. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00022.png +3 -0
  45. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00023.png +3 -0
  46. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00024.png +3 -0
  47. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/metadata.json +36 -0
  48. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/rollout_summary.json +22 -0
  49. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/stderr.log +22 -0
  50. outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/stdout.log +7 -0
.gitattributes CHANGED
@@ -33,3 +33,237 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
37
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
38
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
39
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
40
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
41
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
42
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
43
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
44
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
45
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
46
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
47
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
48
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
49
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
50
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
51
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
52
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
53
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
54
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
55
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
56
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
57
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
58
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
59
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
60
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
61
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
62
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
63
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
64
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
65
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
66
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
67
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
68
+ outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
69
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
70
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
71
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
72
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
73
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
74
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
75
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
76
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
77
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
78
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
79
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
80
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
81
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
82
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
83
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
84
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
85
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
86
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
87
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
88
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
89
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
90
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
91
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
92
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
93
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
94
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
95
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
96
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
97
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
98
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
99
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
100
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
101
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
102
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
103
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
104
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
105
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
106
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
107
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
108
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
109
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
110
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
111
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
112
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
113
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
114
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
115
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
116
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
117
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
118
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
119
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
120
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
121
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
122
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
123
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
124
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
125
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
126
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
127
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
128
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
129
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
130
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
131
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
132
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
133
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
134
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
135
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
136
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
137
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
138
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
139
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
140
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
141
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
142
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
143
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
144
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
145
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
146
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
147
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
148
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
149
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
150
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
151
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
152
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
153
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
154
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
155
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
156
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
157
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
158
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
159
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
160
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
161
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
162
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
163
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
164
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
165
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
166
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
167
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
168
+ outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
169
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
170
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
171
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
172
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
173
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
174
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
175
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
176
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
177
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
178
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
179
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
180
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
181
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
182
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
183
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
184
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
185
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
186
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
187
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
188
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
189
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
190
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
191
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
192
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
193
+ outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
194
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
195
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
196
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
197
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
198
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
199
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
200
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
201
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
202
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
203
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
204
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
205
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
206
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
207
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
208
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
209
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
210
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
211
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
212
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
213
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
214
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
215
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
216
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
217
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
218
+ outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
219
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
220
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
221
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
222
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
223
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
224
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
225
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
226
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
227
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
228
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
229
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
230
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
231
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
232
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
233
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
234
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
235
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
236
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
237
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
238
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
239
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
240
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
241
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
242
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
243
+ outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
244
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00000.png filter=lfs diff=lfs merge=lfs -text
245
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00001.png filter=lfs diff=lfs merge=lfs -text
246
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00002.png filter=lfs diff=lfs merge=lfs -text
247
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00003.png filter=lfs diff=lfs merge=lfs -text
248
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00004.png filter=lfs diff=lfs merge=lfs -text
249
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00005.png filter=lfs diff=lfs merge=lfs -text
250
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00006.png filter=lfs diff=lfs merge=lfs -text
251
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00007.png filter=lfs diff=lfs merge=lfs -text
252
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00008.png filter=lfs diff=lfs merge=lfs -text
253
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00009.png filter=lfs diff=lfs merge=lfs -text
254
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00010.png filter=lfs diff=lfs merge=lfs -text
255
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00011.png filter=lfs diff=lfs merge=lfs -text
256
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00012.png filter=lfs diff=lfs merge=lfs -text
257
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00013.png filter=lfs diff=lfs merge=lfs -text
258
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00014.png filter=lfs diff=lfs merge=lfs -text
259
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00015.png filter=lfs diff=lfs merge=lfs -text
260
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00016.png filter=lfs diff=lfs merge=lfs -text
261
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00017.png filter=lfs diff=lfs merge=lfs -text
262
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00018.png filter=lfs diff=lfs merge=lfs -text
263
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00019.png filter=lfs diff=lfs merge=lfs -text
264
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00020.png filter=lfs diff=lfs merge=lfs -text
265
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00021.png filter=lfs diff=lfs merge=lfs -text
266
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00022.png filter=lfs diff=lfs merge=lfs -text
267
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00023.png filter=lfs diff=lfs merge=lfs -text
268
+ outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy/frames/frame_00024.png filter=lfs diff=lfs merge=lfs -text
269
+ outputs/libero_smoke_tests/20260512_122756/review_montage.mp4 filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by the Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding any notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
NOTICE ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ vla-sae-libero
2
+ ==============
3
+
4
+ This repository contains tooling and run artifacts that build on, wrap, and run:
5
+
6
+ NVIDIA Isaac-GR00T — https://github.com/NVIDIA/Isaac-GR00T
7
+ Copyright (c) NVIDIA CORPORATION & AFFILIATES.
8
+ Licensed under the Apache License, Version 2.0 (see LICENSE).
9
+
10
+ The following were authored as additions on top of that codebase and are
11
+ likewise released under the Apache License, Version 2.0:
12
+
13
+ examples/LIBERO/smoke_tests/ (scenarios_10.yaml, run_10_smoke_tests.py,
14
+ _libero_rollout_worker.py, visualize_smoke_run.py,
15
+ make_video_montage.py, review_smoke_tests.py,
16
+ ACTIVATION_HOOK_NOTES.md, README.md)
17
+ tests/test_libero_smoke_tests.py
18
+
19
+ NOT redistributed in this repository:
20
+ - the NVIDIA Isaac-GR00T source tree (obtain from the URL above);
21
+ - the model checkpoint nvidia/GR00T-N1.7-LIBERO
22
+ (https://huggingface.co/nvidia/GR00T-N1.7-LIBERO);
23
+ - the gated VLM backbone nvidia/Cosmos-Reason2-2B
24
+ (https://huggingface.co/nvidia/Cosmos-Reason2-2B);
25
+ - any Python virtual environments or third-party dependencies.
26
+
27
+ The files under outputs/ are artifacts (rendered videos, frames, recorded
28
+ action chunks, logs, plots, HTML reports, summaries) produced by running the
29
+ nvidia/GR00T-N1.7-LIBERO model in the LIBERO simulator on CPU/GPU; they contain
30
+ no model weights and no credentials.
README.md ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ pipeline_tag: robotics
6
+ tags:
7
+ - robotics
8
+ - vla
9
+ - vision-language-action
10
+ - libero
11
+ - gr00t
12
+ - isaac-gr00t
13
+ - imitation-learning
14
+ - sae
15
+ - mechanistic-interpretability
16
+ - simulation
17
+ pretty_name: "VLA-SAE — GR00T-N1.7-LIBERO LIBERO smoke-test tooling & run artifacts"
18
+ ---
19
+
20
+ # vla-sae-libero — LIBERO smoke-test tooling & run artifacts for `nvidia/GR00T-N1.7-LIBERO`
21
+
22
+ This repo holds **only** the smoke-test *tooling* and the *artifacts* of one completed run from deploying
23
+ **[`nvidia/GR00T-N1.7-LIBERO`](https://huggingface.co/nvidia/GR00T-N1.7-LIBERO)** (the `libero_10` checkpoint)
24
+ and running **10 short, simulation-only LIBERO rollouts** to verify the deploy/eval plumbing — a stepping
25
+ stone toward activation-capture / SAE work on the GR00T VLA.
26
+
27
+ > ## Built on NVIDIA Isaac-GR00T — *not redistributed here*
28
+ > The tooling in this repo runs **on top of** [**NVIDIA Isaac-GR00T**](https://github.com/NVIDIA/Isaac-GR00T)
29
+ > (Apache-2.0, © NVIDIA CORPORATION & AFFILIATES). **This repo does NOT contain the upstream Isaac-GR00T
30
+ > source tree, the model weights, or the gated VLM backbone.** To use the tooling: clone Isaac-GR00T, install
31
+ > its `gr00t` package, drop `examples/LIBERO/smoke_tests/` (and `tests/test_libero_smoke_tests.py`) into the
32
+ > checkout, download the checkpoint, and follow the steps below.
33
+ >
34
+ > Specifically, **not included** (get them from upstream):
35
+ > - the Isaac-GR00T source tree (`gr00t/`, `examples/` except `examples/LIBERO/smoke_tests/`, `getting_started/`, `scripts/`, `docker/`, `demo_data/`, `media/`, …) — <https://github.com/NVIDIA/Isaac-GR00T>
36
+ > - the model checkpoint `nvidia/GR00T-N1.7-LIBERO` (~6.5 GB) — <https://huggingface.co/nvidia/GR00T-N1.7-LIBERO>
37
+ > - the **gated** VLM backbone `nvidia/Cosmos-Reason2-2B` — <https://huggingface.co/nvidia/Cosmos-Reason2-2B>
38
+ > - Python virtualenvs (`.venv/`, `gr00t/eval/sim/LIBERO/libero_uv/`) and the LIBERO submodule (`external_dependencies/LIBERO/`)
39
+
40
+ ## Contents
41
+
42
+ ```
43
+ examples/LIBERO/smoke_tests/ # the smoke-test tooling
44
+ ├── scenarios_10.yaml # 10-scenario manifest (8 normal + 2 abnormal_probe)
45
+ ├── run_10_smoke_tests.py # runner: validates model/server, runs each scenario via the official eval path, writes summaries
46
+ ├── _libero_rollout_worker.py # single-episode worker (runs in the LIBERO venv): records action chunks, optional sim-only probes
47
+ ├── visualize_smoke_run.py # builds visual_report.html + per-scenario action plots + visual_summary.{csv,json}
48
+ ├── make_video_montage.py # builds review_montage.mp4 + per-scenario captioned clips + review_playlist.html (needs ffmpeg)
49
+ ├── review_smoke_tests.py # CLI: list/play the rollouts of a run
50
+ ├── ACTIVATION_HOOK_NOTES.md # where to insert future SAE activation capture (module names, hook points)
51
+ └── README.md # setup & run instructions
52
+
53
+ tests/test_libero_smoke_tests.py # lightweight tests for the manifest + summary writer
54
+
55
+ outputs/libero_smoke_tests/20260512_122756/ # the completed run (10/10 rollouts OK, 0 errors; success=false everywhere — these are 50-step smoke rollouts, not a benchmark)
56
+ ├── summary.json, summary.md # run-level results + the printed comparison table
57
+ ├── visual_report.html # self-contained report: embedded videos + plots + comparison table
58
+ ├── visual_summary.csv, visual_summary.json # per-scenario action-stat table
59
+ ├── review_montage.mp4 # ~22 s: all 10 scenarios back-to-back with captions
60
+ ├── review_clips/<scenario_id>.mp4 # 10 short captioned clips
61
+ ├── review_playlist.html # the montage + each clip + metadata + links
62
+ ├── plots/<scenario_id>/ # action_norm / action_mean_per_dof / gripper_over_time / action_delta_norm (.png)
63
+ └── <scenario_id>/ # ×10
64
+ ├── video.mp4 # rendered rollout (agentview | wrist, 512×256)
65
+ ├── frames/ # decoded PNG frames (25 each; 8 for the short-timeout probe)
66
+ ├── actions.npy # recorded action chunks, shape (n_calls, 1, 16, 7) float32 [DoF column order: gripper, pitch, roll, x, y, yaw, z]
67
+ ├── metadata.json, rollout_summary.json # scenario config + results
68
+ └── stdout.log, stderr.log # the worker subprocess logs
69
+
70
+ outputs/_setup_logs/ # deployment record: uv install / LIBERO setup / server / smoke-run logs (no secrets)
71
+
72
+ LICENSE # Apache-2.0 (same license as upstream Isaac-GR00T)
73
+ NOTICE # attribution
74
+ ```
75
+
76
+ ## The run, in one line
77
+
78
+ 10 LIBERO simulation rollouts of `nvidia/GR00T-N1.7-LIBERO` (`libero_10`), `max_episode_steps = 50` (16 for the short-timeout probe), `n_action_steps = 8`, `n_envs = 1`, **no physical hardware**. 8 `normal` long-horizon tasks + 2 `abnormal_probe` (mild Gaussian observation noise; deliberately shortened timeout). All 10 ran cleanly with actions + video saved; `success = false` everywhere — a 50-step rollout cannot complete these long-horizon tasks, and that's the point: this verifies the deploy/serve/client/sim/render/save plumbing, not task performance.
79
+
80
+ ## Reproduce
81
+
82
+ Full instructions: [`examples/LIBERO/smoke_tests/README.md`](examples/LIBERO/smoke_tests/README.md). Summary:
83
+
84
+ ```bash
85
+ # 0. start from a clone of NVIDIA Isaac-GR00T and copy this repo's files into it
86
+ git clone https://github.com/NVIDIA/Isaac-GR00T.git && cd Isaac-GR00T
87
+ git submodule update --init external_dependencies/LIBERO
88
+ # ... then place examples/LIBERO/smoke_tests/ and tests/test_libero_smoke_tests.py from THIS repo here ...
89
+
90
+ # install gr00t (uv sync, or the minimal recipe in examples/LIBERO/smoke_tests/README.md), then:
91
+ sudo apt install libegl1-mesa-dev libglu1-mesa cmake && bash gr00t/eval/sim/LIBERO/setup_libero.sh
92
+ uv run hf download nvidia/GR00T-N1.7-LIBERO --include "libero_10/*" --local-dir checkpoints/GR00T-N1.7-LIBERO
93
+ # request access to https://huggingface.co/nvidia/Cosmos-Reason2-2B and export HF_TOKEN=hf_...
94
+
95
+ # 1. server (terminal 1) — this pulls the gated nvidia/Cosmos-Reason2-2B backbone
96
+ uv run python gr00t/eval/run_gr00t_server.py \
97
+ --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 \
98
+ --embodiment-tag LIBERO_PANDA --use-sim-policy-wrapper
99
+
100
+ # 2. smoke tests (terminal 2)
101
+ uv run python examples/LIBERO/smoke_tests/run_10_smoke_tests.py \
102
+ --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 --host 127.0.0.1 --port 5555 \
103
+ --manifest examples/LIBERO/smoke_tests/scenarios_10.yaml --output-dir outputs/libero_smoke_tests \
104
+ --max-episode-steps 50 --save-video --render
105
+
106
+ # 3. review artifacts (no GPU, no rerun)
107
+ python examples/LIBERO/smoke_tests/visualize_smoke_run.py --run-dir outputs/libero_smoke_tests/<ts> --open
108
+ python examples/LIBERO/smoke_tests/make_video_montage.py --run-dir outputs/libero_smoke_tests/<ts> --mode sequential --open
109
+ ```
110
+
111
+ ## Next: activation capture / SAE
112
+
113
+ See [`examples/LIBERO/smoke_tests/ACTIVATION_HOOK_NOTES.md`](examples/LIBERO/smoke_tests/ACTIVATION_HOOK_NOTES.md). Suggested first step: register a `forward_hook` on `policy.model.backbone` (the `Qwen3Backbone` — its `forward` returns `backbone_features`, the last Qwen3-VL hidden state = the fused image+instruction representation that conditions action generation), dump that per `get_action` call alongside the already-saved `actions.npy` + frames, and train an SAE on it.
114
+
115
+ ## License & attribution
116
+
117
+ This repo is released under **Apache-2.0**, matching upstream **NVIDIA Isaac-GR00T**
118
+ (<https://github.com/NVIDIA/Isaac-GR00T>, © NVIDIA CORPORATION & AFFILIATES) — see `LICENSE` and `NOTICE`.
119
+ The added tooling (`examples/LIBERO/smoke_tests/`, `tests/test_libero_smoke_tests.py`) is Apache-2.0 and
120
+ imports/wraps the upstream `gr00t` package. The artifacts under `outputs/` are outputs of the
121
+ `nvidia/GR00T-N1.7-LIBERO` model run in LIBERO simulation; **no model weights or upstream source files
122
+ (other than this `LICENSE`) are redistributed here.**
examples/LIBERO/smoke_tests/ACTIVATION_HOOK_NOTES.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Notes for future SAE activation capture (GR00T-N1.7-LIBERO)
2
+
3
+ > Status: **not implemented yet.** This file just records the hook points found
4
+ > while wiring up the LIBERO smoke tests, so a later change can add SAE
5
+ > activation collection with minimal surface area. Nothing here changes model
6
+ > behaviour.
7
+
8
+ ## Where actions are produced (the model call chain)
9
+
10
+ 1. `gr00t/eval/rollout_policy.py :: run_rollout_gymnasium_policy`
11
+ calls `policy.get_action(observations)` once per action chunk.
12
+ - For the smoke tests the `policy` is a `PolicyClient`
13
+ (`gr00t/policy/server_client.py`) talking over ZMQ to a server, so the
14
+ *actual* model lives in the server process. The
15
+ `examples/LIBERO/smoke_tests/_libero_rollout_worker.py` already wraps
16
+ `PolicyClient.get_action` to record the returned action chunks
17
+ (`actions.npy`); that wrapper is the cheapest place to record
18
+ *input/output* of the policy from the client side, but it cannot see
19
+ internal activations.
20
+
21
+ 2. Server side: `gr00t/eval/run_gr00t_server.py` builds a
22
+ `Gr00tPolicy` (`gr00t/policy/gr00t_policy.py`), optionally wrapped in
23
+ `Gr00tSimPolicyWrapper`. The endpoint `get_action` →
24
+ `Gr00tPolicy._get_action()` (≈ line 371):
25
+ - builds `collated_inputs` from the observation (images + proprio state +
26
+ language instruction), casts to bf16, then:
27
+ - `model_pred = self.model.get_action(**collated_inputs)` ← **the forward pass**
28
+ - `normalized_action = model_pred["action_pred"]`, then
29
+ `self.processor.decode_action(...)` → physical-unit action dict returned.
30
+
31
+ 3. The model: `gr00t/model/gr00t_n1d7/gr00t_n1d7.py :: GR00T_N1_7` (registered
32
+ `Gr00tN1d7`). `GR00T_N1_7.get_action(inputs)` (≈ line 589):
33
+ ```
34
+ backbone_inputs, action_inputs = self.prepare_input(inputs)
35
+ backbone_outputs = self.backbone(backbone_inputs) # VLM
36
+ action_outputs = self.action_head.get_action(backbone_outputs, action_inputs, options)
37
+ return action_outputs # {"action_pred": ...}
38
+ ```
39
+
40
+ ## Likely hook points / module names
41
+
42
+ Top-level model attributes (`policy.model` on the server, an
43
+ `nn.Module` of class `Gr00tN1d7` / `GR00T_N1_7`):
44
+
45
+ | Attribute | Class / file | What it computes |
46
+ |---|---|---|
47
+ | `model.backbone` | `Qwen3Backbone` (`gr00t/model/modules/qwen3_backbone.py`) | VLM over images + text instruction. Wraps HF `Qwen3VLForConditionalGeneration` at `model.backbone.model`; transformer decoder layers at `model.backbone.model.language_model.layers[...]` (truncated to `select_layer`); vision tower at `model.backbone.model.visual` (name may vary with the HF Qwen3-VL version). `forward()` runs with `output_hidden_states=True`, takes `hidden_states[-1]`, and returns `BatchFeature({"backbone_features": <Tensor [B, seq, hidden]>, "backbone_attention_mask": ...})`. |
48
+ | `model.action_head` | `Gr00tN1d7ActionHead` (`gr00t/model/gr00t_n1d7/gr00t_n1d7.py`, ≈ line 38) | Flow-matching / diffusion action decoder. Inner net `model.action_head.model` is a `DiT` or `AlternateVLDiT` (`gr00t/model/modules/dit.py`, `gr00t/model/modules/flowmatching_modules.py`). Has `get_action(backbone_output, action_input, options)` and `get_action_with_features(...)` (≈ line 312) which is the natural place to also surface intermediate features. |
49
+ | `model.collator` | `Gr00tN1d7DataCollator` (`gr00t/model/gr00t_n1d7/processing_gr00t_n1d7.py`) | builds VLM batch inputs from `vlm_content`. |
50
+
51
+ **Recommended primary capture site:** the output of `model.backbone` — i.e.
52
+ the tensor stored under key `backbone_features` returned by
53
+ `Qwen3Backbone.forward` (last LLM hidden state, shape `[B, seq_len, hidden]`).
54
+ This is the single fused representation of *image + instruction + (implicit)
55
+ proprioception context* that conditions action generation, so it's the most
56
+ informative single activation to feed an SAE. Secondary sites: per-layer LLM
57
+ hidden states (`outputs.hidden_states[k]` inside `Qwen3Backbone.forward`),
58
+ and the DiT block activations inside `model.action_head.model`.
59
+
60
+ **Cheapest mechanism:** register `torch.nn.Module.register_forward_hook` on
61
+ `policy.model.backbone` (and/or specific `...language_model.layers[k]`) right
62
+ after the policy is constructed in `run_gr00t_server.py`, before
63
+ `server.run()`. Hooks can append detached CPU/float16 tensors to a buffer that
64
+ is flushed to disk per get_action call. No edits to model code required.
65
+ Alternative: subclass `Gr00tPolicy` and override `_get_action` to also stash
66
+ `self.model`'s intermediates.
67
+
68
+ ## Where observation / instruction / proprioception enter the model
69
+
70
+ - **LIBERO env → observation dict**: `gr00t/eval/sim/LIBERO/libero_env.py ::
71
+ LiberoEnv._process_observation` produces:
72
+ - `video.image` (agentview 256×256×3), `video.wrist_image` (eye-in-hand),
73
+ - `state.x/y/z/roll/pitch/yaw` (EEF pose from `robot0_eef_pos` + axis-angle
74
+ of `robot0_eef_quat`), `state.gripper` (2-dim `robot0_gripper_qpos`),
75
+ - `annotation.human.action.task_description` (the **language instruction**,
76
+ a fixed string per task).
77
+ - These are batched/temporally-stacked by `MultiStepWrapper`
78
+ (`gr00t/eval/sim/wrapper/multistep_wrapper.py`) and then, in
79
+ `Gr00tPolicy._get_action`, turned into `VLAStepData` via
80
+ `self._to_vla_step_data(obs)` and run through `self.processor(messages)`
81
+ (`Gr00tN1d7Processor`, `gr00t/model/gr00t_n1d7/processing_gr00t_n1d7.py`):
82
+ - images + instruction → `vlm_content` → tokenized/pixel-processed by the
83
+ Qwen3-VL processor → `model.backbone`.
84
+ - proprio `state.*` → `action_inputs` → `model.action_head` (conditioning).
85
+ - The smoke-test worker can optionally perturb the image observations
86
+ *before* `policy.get_action` (`--obs-noise-std`); a future activation-capture
87
+ run could pair clean vs. perturbed activations from the same seeds.
88
+
89
+ ## Suggested next step for activation capture
90
+
91
+ 1. Add an opt-in flag to `run_gr00t_server.py` (e.g. `--capture-activations
92
+ DIR` / `--capture-layers backbone,llm.20`) that, after building the policy,
93
+ registers forward hooks on the chosen modules and writes one
94
+ `.npz`/`.safetensors` per `get_action` call (keyed by an episode/step id
95
+ passed through `options`).
96
+ 2. Have `_libero_rollout_worker.py` thread a stable `(scenario_id, episode,
97
+ step)` tag into `policy.get_action(obs, options=...)` so captured
98
+ activations can be joined back to `actions.npy` and the saved video frames.
99
+ 3. Train the SAE on `backbone_features` first (one SAE), then expand to
100
+ per-layer LLM hidden states if needed.
examples/LIBERO/smoke_tests/README.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LIBERO simulation-only smoke tests for `nvidia/GR00T-N1.7-LIBERO`
2
+
3
+ These scripts deploy the `libero_10` checkpoint of
4
+ [`nvidia/GR00T-N1.7-LIBERO`](https://huggingface.co/nvidia/GR00T-N1.7-LIBERO)
5
+ and run **10 short, simulation-only LIBERO rollouts** to verify the deploy /
6
+ eval plumbing:
7
+
8
+ 1. the model checkpoint is present and loads,
9
+ 2. the GR00T inference server starts,
10
+ 3. the LIBERO rollout client connects to it,
11
+ 4. short rollouts run with **no physical robot hardware** (LIBERO sim only),
12
+ 5. a video / rendered frames are saved per scenario for human review,
13
+ 6. basic rollout metadata + recorded action chunks are saved,
14
+ 7. activation hooks can be added later — see [`ACTIVATION_HOOK_NOTES.md`](ACTIVATION_HOOK_NOTES.md).
15
+
16
+ This is **not** a benchmark. `max_episode_steps` is deliberately tiny, so the
17
+ reported success rates are meaningless — only the plumbing matters here. The
18
+ scripts do **not** rewrite the official evaluation path: each scenario runs
19
+ through `gr00t.eval.rollout_policy.run_rollout_gymnasium_policy` via the thin
20
+ `_libero_rollout_worker.py`, which only adds action recording and two optional,
21
+ simulation-only perturbations (Gaussian pixel noise; action-chunk repeat).
22
+
23
+ ## Files
24
+
25
+ | File | Purpose |
26
+ |---|---|
27
+ | `scenarios_10.yaml` | the 10-scenario manifest (8 `normal`, 2 `abnormal_probe`) |
28
+ | `run_10_smoke_tests.py` | runner: validates paths/server, runs all scenarios, writes summaries |
29
+ | `_libero_rollout_worker.py` | runs **one** rollout against the server (called by the runner; runs in the LIBERO venv) |
30
+ | `review_smoke_tests.py` | lists scenarios + video paths from a run dir, optionally plays videos |
31
+ | `ACTIVATION_HOOK_NOTES.md` | where to insert future SAE activation capture |
32
+
33
+ Tests live at `tests/test_libero_smoke_tests.py`.
34
+
35
+ ## One-time setup
36
+
37
+ ```bash
38
+ # 1. Install deps (project venv) — uses uv
39
+ uv sync # or: uv pip install -e . (see note below)
40
+
41
+ # 2. LIBERO simulation env (separate venv; only needed once)
42
+ sudo apt update && sudo apt install libegl1-mesa-dev libglu1-mesa
43
+ bash gr00t/eval/sim/LIBERO/setup_libero.sh
44
+
45
+ # 3. Download the libero_10 checkpoint
46
+ # (HuggingFace does not support nested repo paths directly)
47
+ uv run hf download nvidia/GR00T-N1.7-LIBERO \
48
+ --include "libero_10/config.json" \
49
+ "libero_10/embodiment_id.json" \
50
+ "libero_10/model-*.safetensors" \
51
+ "libero_10/model.safetensors.index.json" \
52
+ "libero_10/processor_config.json" \
53
+ "libero_10/statistics.json" \
54
+ --local-dir checkpoints/GR00T-N1.7-LIBERO
55
+ # If the glob skips config.json, fetch it explicitly:
56
+ uv run hf download nvidia/GR00T-N1.7-LIBERO libero_10/config.json \
57
+ --local-dir checkpoints/GR00T-N1.7-LIBERO
58
+ ```
59
+
60
+ > **Note on `uv sync`:** the project `pyproject.toml` pins GPU-deployment-only
61
+ > packages (`flash-attn`, `deepspeed`, `tensorrt-*`) and the lockfile references
62
+ > aarch64 wheels that are stored via git-LFS. On a plain x86 box without git-LFS
63
+ > / CUDA toolkit, `uv sync` can fail on those. They are **not needed for the
64
+ > smoke test** (the model falls back to `sdpa` attention if `flash_attn` is
65
+ > missing). A working minimal install is:
66
+ > ```bash
67
+ > uv venv .venv --python 3.10
68
+ > uv pip install --python .venv/bin/python -e . --no-deps
69
+ > uv pip install --python .venv/bin/python \
70
+ > torch==2.7.1 torchvision==0.22.1 transformers==4.57.3 numpy==1.26.4 \
71
+ > albumentations==1.4.18 av==16.1.0 diffusers==0.35.1 dm-tree lmdb==1.7.5 \
72
+ > msgpack==1.1.0 msgpack-numpy==0.4.8 pandas==2.2.3 peft==0.17.1 termcolor==3.2.0 \
73
+ > tyro==0.9.17 click==8.1.8 datasets==3.6.0 cryptography einops==0.8.1 \
74
+ > gitpython==3.1.46 jsonlines==4.0.0 gymnasium==1.2.2 matplotlib==3.10.1 \
75
+ > omegaconf==2.3.0 scipy==1.15.3 torchcodec==0.4.0 wandb==0.23.0 pyzmq==27.0.1 \
76
+ > "huggingface-hub[cli]" "opencv-python-headless>=4.5,<4.13" safetensors accelerate \
77
+ > sentencepiece protobuf pyyaml tqdm
78
+ > ```
79
+
80
+ ## Running the smoke tests
81
+
82
+ **Terminal 1 — start the GR00T inference server:**
83
+
84
+ ```bash
85
+ uv run python gr00t/eval/run_gr00t_server.py \
86
+ --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 \
87
+ --embodiment-tag LIBERO_PANDA \
88
+ --use-sim-policy-wrapper
89
+ ```
90
+
91
+ (Server defaults: `--host 0.0.0.0 --port 5555`. This is exactly the command
92
+ documented in `examples/LIBERO/README.md`.)
93
+
94
+ > **Gated backbone:** GR00T-N1.7 uses `nvidia/Cosmos-Reason2-2B` (a Qwen3-VL
95
+ > model) as its VLM backbone, and that repo is **gated** on HuggingFace.
96
+ > Starting the server pulls the base repo's config/processor, so it fails
97
+ > without HF auth. One-time fix:
98
+ > ```bash
99
+ > # 1. request access (one click): https://huggingface.co/nvidia/Cosmos-Reason2-2B
100
+ > # 2. authenticate:
101
+ > export HF_TOKEN=hf_xxx # or: uv run hf auth login
102
+ > ```
103
+ > The `run_10_smoke_tests.py` runner detects this failure mode (when launched
104
+ > with `--start-server`) and prints the same hint.
105
+
106
+ **Terminal 2 — run the 10 smoke tests:**
107
+
108
+ ```bash
109
+ uv run python examples/LIBERO/smoke_tests/run_10_smoke_tests.py \
110
+ --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 \
111
+ --host 127.0.0.1 --port 5555 \
112
+ --manifest examples/LIBERO/smoke_tests/scenarios_10.yaml \
113
+ --output-dir outputs/libero_smoke_tests \
114
+ --max-episode-steps 50 \
115
+ --save-video --render
116
+ ```
117
+
118
+ Other flags:
119
+
120
+ - `--start-server` — launch (and later kill) the server from the runner itself,
121
+ so you only need one terminal.
122
+ - `--dry-run` — validate the manifest, model path and (optionally) the server,
123
+ write `metadata.json` per scenario, but run no simulations.
124
+ - `--resume` + `--run-dir <output-dir>/<timestamp>` — re-enter an existing run
125
+ dir and skip scenarios that already produced a `rollout_summary.json`.
126
+ - `--libero-python <path>` — override the LIBERO venv python (default:
127
+ `gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/python`, falls back to the current
128
+ interpreter with a warning).
129
+
130
+ If the model path is missing, or the server is unreachable, or the LIBERO sim
131
+ env is not importable, the runner stops with an actionable error — it does
132
+ **not** fake a successful rollout.
133
+
134
+ ## Outputs
135
+
136
+ ```
137
+ outputs/libero_smoke_tests/<timestamp>/
138
+ summary.json
139
+ summary.md
140
+ server.log # only if --start-server
141
+ <scenario_id>/
142
+ metadata.json # the manifest entry + resolved params
143
+ rollout_summary.json # env_name, seed, steps, success, reward, ...
144
+ actions.npy # recorded action chunks (if the rollout ran)
145
+ video.mp4 # rendered rollout (if recording succeeded)
146
+ frames/ # decoded PNG frames (only with --render)
147
+ stdout.log / stderr.log # the worker subprocess logs
148
+ ```
149
+
150
+ At the end the runner prints a table:
151
+
152
+ ```
153
+ scenario_id | label | seed | rollout_started | actions_produced | video_saved | success | output_dir | error_if_any
154
+ ```
155
+
156
+ and writes the same to `summary.json` / `summary.md`. Review videos with:
157
+
158
+ ```bash
159
+ python examples/LIBERO/smoke_tests/review_smoke_tests.py --run-dir outputs/libero_smoke_tests/<timestamp> --open
160
+ ```
examples/LIBERO/smoke_tests/_libero_rollout_worker.py ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License").
5
+ """Single-episode LIBERO rollout worker for the smoke-test harness.
6
+
7
+ This is a *thin* wrapper around the official evaluation path
8
+ (``gr00t.eval.rollout_policy.run_rollout_gymnasium_policy``). It exists only
9
+ so that, for one short rollout, we can additionally:
10
+
11
+ * connect to an already-running GR00T policy server (``PolicyClient``),
12
+ * record the action chunks the policy returns (-> ``actions.npy``),
13
+ * optionally apply a tiny, simulation-only observation perturbation
14
+ (Gaussian pixel noise) or repeat the previous action chunk -- these are
15
+ placeholders for future "abnormal" probes, not real anomaly logic,
16
+ * collect the recorded video into the per-scenario output directory.
17
+
18
+ It does **not** modify the official rollout/eval code. It is meant to be run
19
+ *inside the LIBERO uv venv* created by
20
+ ``gr00t/eval/sim/LIBERO/setup_libero.sh``, e.g.::
21
+
22
+ gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/python \
23
+ examples/LIBERO/smoke_tests/_libero_rollout_worker.py --help
24
+
25
+ Normally you do not call this directly -- ``run_10_smoke_tests.py`` invokes it.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import argparse
31
+ import glob
32
+ import json
33
+ import os
34
+ from pathlib import Path
35
+ import shutil
36
+ import sys
37
+ import time
38
+ import traceback
39
+
40
+ # LIBERO renders headlessly via EGL; set this before importing the env modules.
41
+ os.environ.setdefault("MUJOCO_GL", "egl")
42
+ os.environ.setdefault("PYOPENGL_PLATFORM", "egl")
43
+
44
+ import numpy as np
45
+
46
+ RESULT_PREFIX = "SMOKE_RESULT_JSON:"
47
+
48
+
49
+ def _emit_result(result: dict) -> None:
50
+ """Print the machine-readable result line the parent runner parses."""
51
+ sys.stdout.flush()
52
+ print(RESULT_PREFIX + " " + json.dumps(result), flush=True)
53
+
54
+
55
+ def _action_to_numpy(action):
56
+ """Best-effort conversion of a policy action (dict of arrays or array) to ndarray."""
57
+ if isinstance(action, dict):
58
+ # LIBERO action space: action.x, action.y, ..., action.gripper
59
+ keys = sorted(action.keys())
60
+ try:
61
+ parts = [np.asarray(action[k]) for k in keys]
62
+ return np.concatenate(parts, axis=-1)
63
+ except Exception:
64
+ return {k: np.asarray(v) for k, v in action.items()}
65
+ return np.asarray(action)
66
+
67
+
68
+ def main() -> int:
69
+ ap = argparse.ArgumentParser(description="Single LIBERO rollout against a running GR00T server.")
70
+ ap.add_argument("--env-name", required=True)
71
+ ap.add_argument("--host", default="127.0.0.1")
72
+ ap.add_argument("--port", type=int, default=5555)
73
+ ap.add_argument("--max-episode-steps", type=int, default=50)
74
+ ap.add_argument("--n-action-steps", type=int, default=8)
75
+ ap.add_argument("--seed", type=int, default=0)
76
+ ap.add_argument("--out-dir", required=True)
77
+ ap.add_argument("--save-video", dest="save_video", action="store_true", default=True)
78
+ ap.add_argument("--no-save-video", dest="save_video", action="store_false")
79
+ ap.add_argument("--save-frames", action="store_true", default=False,
80
+ help="Also dump decoded video frames into <out-dir>/frames/.")
81
+ ap.add_argument("--obs-noise-std", type=float, default=0.0,
82
+ help="Std-dev of zero-mean Gaussian noise added to image observations "
83
+ "before policy inference (0 = disabled). Simulation-only probe.")
84
+ ap.add_argument("--action-repeat", type=int, default=1,
85
+ help="If >1, re-use the previous action chunk instead of querying the "
86
+ "policy on (k-1)/k of the steps (1 = disabled). Simulation-only probe.")
87
+ ap.add_argument("--timeout-ms", type=int, default=120000,
88
+ help="ZMQ send/recv timeout for the policy client.")
89
+ args = ap.parse_args()
90
+
91
+ out_dir = Path(args.out_dir)
92
+ out_dir.mkdir(parents=True, exist_ok=True)
93
+ video_raw_dir = out_dir / "_video_raw"
94
+
95
+ result: dict = {
96
+ "ok": False,
97
+ "rollout_started": False,
98
+ "actions_produced": False,
99
+ "video_saved": False,
100
+ "success": None,
101
+ "error": None,
102
+ "env_name": args.env_name,
103
+ "seed": args.seed,
104
+ "n_action_calls": 0,
105
+ "episode_length": None,
106
+ "episode_reward": None,
107
+ "video_path": None,
108
+ "actions_path": None,
109
+ }
110
+
111
+ try:
112
+ from gr00t.eval.rollout_policy import (
113
+ MultiStepConfig,
114
+ VideoConfig,
115
+ WrapperConfigs,
116
+ run_rollout_gymnasium_policy,
117
+ )
118
+ from gr00t.eval.sim.env_utils import get_embodiment_tag_from_env_name # noqa: F401
119
+ from gr00t.policy.server_client import PolicyClient
120
+
121
+ try:
122
+ from gr00t.utils.determinism import seed_everything
123
+
124
+ seed_everything(args.seed)
125
+ except Exception:
126
+ pass
127
+
128
+ # --- connect to the running server -----------------------------------
129
+ # Quick reachability probe with a SHORT timeout (so we fail fast instead
130
+ # of blocking for `timeout_ms` when nothing is listening).
131
+ import socket as _socket
132
+
133
+ reachable = False
134
+ try:
135
+ with _socket.create_connection((args.host, args.port), timeout=4.0):
136
+ pass
137
+ probe = PolicyClient(host=args.host, port=args.port, timeout_ms=4000)
138
+ reachable = bool(probe.ping())
139
+ del probe
140
+ except OSError:
141
+ reachable = False
142
+ if not reachable:
143
+ result["error"] = (
144
+ f"GR00T policy server is not reachable at {args.host}:{args.port}. "
145
+ "Start it first (see run_10_smoke_tests.py output / smoke_tests/README.md). "
146
+ "Note: GR00T-N1.7's backbone 'nvidia/Cosmos-Reason2-2B' is a gated HF repo, so "
147
+ "the server needs HF auth (request access + export HF_TOKEN)."
148
+ )
149
+ _emit_result(result)
150
+ return 3
151
+
152
+ policy = PolicyClient(host=args.host, port=args.port, timeout_ms=args.timeout_ms)
153
+
154
+ # --- instrument get_action: record actions + optional perturbations ---
155
+ recorded_actions: list = []
156
+ rng = np.random.default_rng(args.seed)
157
+ n_calls = {"n": 0}
158
+ last_av = {"v": None}
159
+ orig_get_action = policy.get_action
160
+
161
+ def patched_get_action(observation, *a, **k):
162
+ if args.obs_noise_std and args.obs_noise_std > 0:
163
+ for key, val in list(observation.items()):
164
+ if isinstance(val, np.ndarray) and val.ndim >= 3 and "image" in key.lower():
165
+ noisy = val.astype(np.float32) + rng.normal(
166
+ 0.0, args.obs_noise_std, size=val.shape
167
+ )
168
+ observation[key] = np.clip(noisy, 0, 255).astype(val.dtype)
169
+ if (
170
+ args.action_repeat
171
+ and args.action_repeat > 1
172
+ and last_av["v"] is not None
173
+ and (n_calls["n"] % args.action_repeat) != 0
174
+ ):
175
+ action, info = last_av["v"]
176
+ else:
177
+ action, info = orig_get_action(observation, *a, **k)
178
+ last_av["v"] = (action, info)
179
+ n_calls["n"] += 1
180
+ recorded_actions.append(_action_to_numpy(action))
181
+ return action, info
182
+
183
+ policy.get_action = patched_get_action # type: ignore[assignment]
184
+
185
+ # --- run one short rollout via the official path ---------------------
186
+ wrapper_configs = WrapperConfigs(
187
+ video=VideoConfig(
188
+ video_dir=str(video_raw_dir) if args.save_video else None,
189
+ max_episode_steps=args.max_episode_steps,
190
+ ),
191
+ multistep=MultiStepConfig(
192
+ n_action_steps=args.n_action_steps,
193
+ max_episode_steps=args.max_episode_steps,
194
+ terminate_on_success=True,
195
+ ),
196
+ )
197
+
198
+ result["rollout_started"] = True
199
+ t0 = time.time()
200
+ env_name, episode_successes, episode_infos = run_rollout_gymnasium_policy(
201
+ env_name=args.env_name,
202
+ policy=policy,
203
+ wrapper_configs=wrapper_configs,
204
+ n_episodes=1,
205
+ n_envs=1,
206
+ seed=args.seed,
207
+ )
208
+ elapsed = time.time() - t0
209
+
210
+ # --- collect results -------------------------------------------------
211
+ success = bool(np.any(episode_successes)) if len(episode_successes) else None
212
+ ep_lengths = list(episode_infos.get("episode_lengths", []) or [])
213
+ ep_rewards = list(episode_infos.get("episode_rewards", []) or [])
214
+ episode_length = int(ep_lengths[0]) if ep_lengths else None
215
+ episode_reward = float(ep_rewards[0]) if ep_rewards else None
216
+
217
+ result["n_action_calls"] = n_calls["n"]
218
+ result["success"] = success
219
+ result["episode_length"] = episode_length
220
+ result["episode_reward"] = episode_reward
221
+
222
+ # --- save actions ----------------------------------------------------
223
+ actions_path = None
224
+ if recorded_actions:
225
+ actions_path = out_dir / "actions.npy"
226
+ try:
227
+ stacked = np.stack(recorded_actions, axis=0)
228
+ np.save(actions_path, stacked)
229
+ except Exception:
230
+ np.save(actions_path, np.array(recorded_actions, dtype=object), allow_pickle=True)
231
+ result["actions_produced"] = True
232
+ result["actions_path"] = str(actions_path)
233
+
234
+ # --- collect video / frames -----------------------------------------
235
+ video_path = None
236
+ if args.save_video and video_raw_dir.exists():
237
+ mp4s = sorted(
238
+ glob.glob(str(video_raw_dir / "**" / "*.mp4"), recursive=True),
239
+ key=lambda p: os.path.getsize(p),
240
+ reverse=True,
241
+ )
242
+ if mp4s:
243
+ video_path = out_dir / "video.mp4"
244
+ shutil.copyfile(mp4s[0], video_path)
245
+ result["video_saved"] = True
246
+ result["video_path"] = str(video_path)
247
+ # keep a record of the original (success-suffixed) filename
248
+ result["video_source_name"] = os.path.basename(mp4s[0])
249
+ if args.save_frames:
250
+ try:
251
+ import cv2
252
+
253
+ frames_dir = out_dir / "frames"
254
+ frames_dir.mkdir(exist_ok=True)
255
+ cap = cv2.VideoCapture(str(video_path))
256
+ idx = 0
257
+ while True:
258
+ ok, frame = cap.read()
259
+ if not ok:
260
+ break
261
+ cv2.imwrite(str(frames_dir / f"frame_{idx:05d}.png"), frame)
262
+ idx += 1
263
+ cap.release()
264
+ result["frames_dir"] = str(frames_dir)
265
+ result["n_frames"] = idx
266
+ except Exception as e: # noqa: BLE001
267
+ result["frames_error"] = repr(e)
268
+
269
+ # --- write per-scenario rollout summary ------------------------------
270
+ rollout_summary = {
271
+ "env_name": env_name,
272
+ "seed": args.seed,
273
+ "requested_max_episode_steps": args.max_episode_steps,
274
+ "n_action_steps": args.n_action_steps,
275
+ "n_get_action_calls": n_calls["n"],
276
+ "episode_successes": [bool(x) for x in episode_successes],
277
+ "success": success,
278
+ "episode_length": episode_length,
279
+ "episode_reward": episode_reward,
280
+ "elapsed_sec": elapsed,
281
+ "obs_noise_std": args.obs_noise_std,
282
+ "action_repeat": args.action_repeat,
283
+ "video_path": str(video_path) if video_path else None,
284
+ "actions_path": str(actions_path) if actions_path else None,
285
+ "policy_server": {"host": args.host, "port": args.port},
286
+ }
287
+ with open(out_dir / "rollout_summary.json", "w") as f:
288
+ json.dump(rollout_summary, f, indent=2)
289
+
290
+ result["ok"] = True
291
+ _emit_result(result)
292
+ return 0
293
+
294
+ except Exception: # noqa: BLE001
295
+ tb = traceback.format_exc()
296
+ result["error"] = tb.strip().splitlines()[-1] if tb.strip() else "unknown error"
297
+ result["traceback"] = tb
298
+ # also write a partial rollout_summary so the runner has something
299
+ try:
300
+ with open(out_dir / "rollout_summary.json", "w") as f:
301
+ json.dump({"error": result["error"], "traceback": tb,
302
+ "env_name": args.env_name, "seed": args.seed}, f, indent=2)
303
+ except Exception:
304
+ pass
305
+ print(tb, file=sys.stderr, flush=True)
306
+ _emit_result(result)
307
+ return 1
308
+
309
+
310
+ if __name__ == "__main__":
311
+ raise SystemExit(main())
examples/LIBERO/smoke_tests/make_video_montage.py ADDED
@@ -0,0 +1,578 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License").
5
+ """Build a compact human-review video montage for a completed LIBERO smoke run.
6
+
7
+ Reads every scenario directory under ``--run-dir``, takes a short clip from each
8
+ ``video.mp4`` (default 5 s; full video if shorter), burns in an overlay
9
+ (scenario_id / label / seed / instruction / success), and writes:
10
+
11
+ <run-dir>/review_clips/<scenario_id>.mp4 one short, captioned clip per scenario
12
+ <run-dir>/review_montage.mp4 all scenarios back-to-back (sequential) or in a grid
13
+ <run-dir>/review_playlist.html the montage + each clip with metadata and a link to the full video
14
+
15
+ It does NOT rerun LIBERO and does NOT modify any existing rollout artifact —
16
+ it only reads ``video.mp4`` / ``metadata.json`` / ``rollout_summary.json`` and
17
+ writes the three review artifacts above. Requires ``ffmpeg`` (and ``ffprobe``)
18
+ on PATH; no GPU.
19
+
20
+ CLI::
21
+
22
+ python examples/LIBERO/smoke_tests/make_video_montage.py \
23
+ --run-dir outputs/libero_smoke_tests/20260512_122756 \
24
+ --clip-seconds 5 --mode sequential --open
25
+
26
+ Flags: --run-dir --clip-seconds --mode {sequential,grid} --hold-seconds
27
+ --instruction-chars --cols --open --dry-run
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import datetime as _dt
34
+ import html
35
+ import json
36
+ import math
37
+ import os
38
+ from pathlib import Path
39
+ import shutil
40
+ import subprocess
41
+ import sys
42
+ import tempfile
43
+
44
+ FONT_CANDIDATES = [
45
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
46
+ "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
47
+ "/usr/share/fonts/dejavu/DejaVuSans.ttf",
48
+ "/Library/Fonts/Arial.ttf",
49
+ "/System/Library/Fonts/Supplemental/Arial.ttf",
50
+ ]
51
+
52
+
53
+ # --------------------------------------------------------------------------- #
54
+ # small helpers
55
+ # --------------------------------------------------------------------------- #
56
+ def _die(msg: str, code: int = 2) -> None:
57
+ print(msg, file=sys.stderr)
58
+ raise SystemExit(code)
59
+
60
+
61
+ def _ensure_ffmpeg() -> tuple[str, str]:
62
+ ff = shutil.which("ffmpeg")
63
+ fp = shutil.which("ffprobe")
64
+ if ff and fp:
65
+ return ff, fp
66
+ missing = " and ".join(x for x, ok in [("ffmpeg", ff), ("ffprobe", fp)] if not ok)
67
+ _die(
68
+ f"ERROR: required tool(s) not found on PATH: {missing}.\n"
69
+ "Install ffmpeg, e.g.:\n"
70
+ " sudo apt-get update && sudo apt-get install -y ffmpeg # Debian/Ubuntu\n"
71
+ " conda install -c conda-forge ffmpeg # conda\n"
72
+ " brew install ffmpeg # macOS\n"
73
+ " uv pip install imageio-ffmpeg && export PATH=... (its bundled ffmpeg) # last resort\n"
74
+ "Then re-run this script."
75
+ )
76
+ raise AssertionError # unreachable
77
+
78
+
79
+ def _find_font() -> str | None:
80
+ for f in FONT_CANDIDATES:
81
+ if os.path.exists(f):
82
+ return f
83
+ return None
84
+
85
+
86
+ def _load_json(path: Path):
87
+ try:
88
+ with open(path) as f:
89
+ return json.load(f)
90
+ except Exception:
91
+ return None
92
+
93
+
94
+ def _probe_video(ffprobe: str, path: Path) -> dict | None:
95
+ try:
96
+ out = subprocess.run(
97
+ [ffprobe, "-v", "error", "-select_streams", "v:0",
98
+ "-show_entries", "stream=width,height,r_frame_rate,duration,nb_frames",
99
+ "-show_entries", "format=duration", "-of", "json", str(path)],
100
+ capture_output=True, text=True, check=True,
101
+ ).stdout
102
+ info = json.loads(out)
103
+ st = (info.get("streams") or [{}])[0]
104
+ w = int(st.get("width") or 0)
105
+ h = int(st.get("height") or 0)
106
+ rfr = st.get("r_frame_rate") or "0/1"
107
+ try:
108
+ num, den = rfr.split("/")
109
+ fps = float(num) / float(den) if float(den) else 0.0
110
+ except Exception:
111
+ fps = 0.0
112
+ dur = st.get("duration") or (info.get("format") or {}).get("duration")
113
+ try:
114
+ dur = float(dur)
115
+ except (TypeError, ValueError):
116
+ dur = 0.0
117
+ nb = st.get("nb_frames")
118
+ try:
119
+ nb = int(nb)
120
+ except (TypeError, ValueError):
121
+ nb = None
122
+ if (not dur or dur <= 0) and nb and fps:
123
+ dur = nb / fps
124
+ return {"width": w, "height": h, "fps": fps or 20.0, "duration": dur, "nb_frames": nb}
125
+ except Exception:
126
+ return None
127
+
128
+
129
+ def _truncate(s: str, n: int) -> str:
130
+ s = " ".join(str(s).split())
131
+ return s if len(s) <= n else s[: max(0, n - 1)].rstrip() + "…"
132
+
133
+
134
+ def _drawtext_chain(font: str | None, lines: list[str], tmp_dir: Path,
135
+ box_h: int, fontsize: int = 15) -> str:
136
+ """Build a filter chain: one translucent box + one drawtext per line (read from textfiles)."""
137
+ parts = [f"drawbox=x=0:y=0:w=iw:h={box_h}:color=black@0.55:t=fill"]
138
+ y = 5
139
+ for i, line in enumerate(lines):
140
+ tf = tmp_dir / f"line_{i}.txt"
141
+ tf.write_text(line if line else " ", encoding="utf-8")
142
+ # textfile path uses only safe chars (our tmp dir); expansion=none -> fully literal text.
143
+ fontspec = f"fontfile='{tf.parent / 'FONT'}'" # placeholder, replaced below
144
+ if font:
145
+ fontspec = f"fontfile={font}"
146
+ else:
147
+ fontspec = "font=sans"
148
+ parts.append(
149
+ f"drawtext={fontspec}:textfile={tf}:expansion=none:reload=0:"
150
+ f"fontsize={fontsize}:fontcolor=white:shadowcolor=black@0.8:shadowx=1:shadowy=1:"
151
+ f"x=6:y={y}"
152
+ )
153
+ y += fontsize + 7
154
+ return ",".join(parts)
155
+
156
+
157
+ def _run_ffmpeg(ffmpeg: str, args: list[str], desc: str) -> tuple[bool, str]:
158
+ try:
159
+ r = subprocess.run([ffmpeg, "-hide_banner", "-y", "-nostdin", *args],
160
+ capture_output=True, text=True)
161
+ if r.returncode != 0:
162
+ tail = "\n".join((r.stderr or "").strip().splitlines()[-12:])
163
+ return False, f"{desc}: ffmpeg exited {r.returncode}\n{tail}"
164
+ return True, ""
165
+ except Exception as e: # noqa: BLE001
166
+ return False, f"{desc}: {e!r}"
167
+
168
+
169
+ # --------------------------------------------------------------------------- #
170
+ # planning
171
+ # --------------------------------------------------------------------------- #
172
+ def discover_scenarios(run_dir: Path) -> tuple[list[str], dict]:
173
+ summary = _load_json(run_dir / "summary.json")
174
+ order: list[str] = []
175
+ rows_by_id: dict[str, dict] = {}
176
+ if summary and isinstance(summary.get("scenarios"), list):
177
+ for r in summary["scenarios"]:
178
+ sid = r.get("scenario_id")
179
+ if sid:
180
+ order.append(sid)
181
+ rows_by_id[sid] = r
182
+ skip = {"plots", "review_clips"}
183
+ for p in sorted(run_dir.iterdir()):
184
+ if p.is_dir() and p.name not in skip and p.name not in order:
185
+ order.append(p.name)
186
+ return order, {"summary": summary, "rows_by_id": rows_by_id}
187
+
188
+
189
+ def build_plan(run_dir: Path, ffprobe: str, clip_seconds: float, instr_chars: int) -> list[dict]:
190
+ order, ctx = discover_scenarios(run_dir)
191
+ rows_by_id = ctx["rows_by_id"]
192
+ plan: list[dict] = []
193
+ for sid in order:
194
+ sc_dir = run_dir / sid
195
+ meta = _load_json(sc_dir / "metadata.json") or {}
196
+ roll = _load_json(sc_dir / "rollout_summary.json") or {}
197
+ srow = rows_by_id.get(sid, {})
198
+ manifest_entry = meta.get("manifest_entry", {}) or {}
199
+ resolved = meta.get("resolved", {}) or {}
200
+ label = manifest_entry.get("label") or meta.get("label") or srow.get("label") or "?"
201
+ seed = manifest_entry.get("seed", resolved.get("seed", srow.get("seed")))
202
+ instruction = manifest_entry.get("instruction") or roll.get("instruction") or "(unknown)"
203
+ env_name = resolved.get("env_name") or manifest_entry.get("env_name") or roll.get("env_name")
204
+ success = roll.get("success", srow.get("success"))
205
+ video = sc_dir / "video.mp4"
206
+ info = _probe_video(ffprobe, video) if video.exists() else None
207
+ src_dur = (info or {}).get("duration") or 0.0
208
+ clip_dur = src_dur if (src_dur and src_dur < clip_seconds) else clip_seconds
209
+ warn = None
210
+ if not video.exists():
211
+ warn = "video.mp4 not found"
212
+ elif info is None:
213
+ warn = "ffprobe could not read video.mp4"
214
+ elif src_dur <= 0:
215
+ warn = "video.mp4 has zero/unknown duration"
216
+ plan.append({
217
+ "scenario_id": sid, "label": label, "seed": seed,
218
+ "instruction": instruction, "instruction_short": _truncate(instruction, instr_chars),
219
+ "env_name": env_name, "success": success,
220
+ "video": video if video.exists() else None,
221
+ "video_rel": os.path.relpath(video, run_dir) if video.exists() else None,
222
+ "src_duration": round(float(src_dur), 3) if src_dur else 0.0,
223
+ "src_info": info,
224
+ "clip_duration": round(float(clip_dur), 3),
225
+ "clip_path": run_dir / "review_clips" / f"{sid}.mp4",
226
+ "clip_rel": os.path.join("review_clips", f"{sid}.mp4"),
227
+ "warning": warn,
228
+ "ok_source": video.exists() and info is not None and src_dur > 0,
229
+ })
230
+ return plan
231
+
232
+
233
+ def _success_str(s) -> str:
234
+ if s is True:
235
+ return "true"
236
+ if s is False:
237
+ return "false"
238
+ return "unknown"
239
+
240
+
241
+ # --------------------------------------------------------------------------- #
242
+ # clip / montage generation
243
+ # --------------------------------------------------------------------------- #
244
+ def make_clip(ffmpeg: str, font: str | None, entry: dict, idx: int, n: int,
245
+ tmp_root: Path, target_w: int) -> tuple[bool, str]:
246
+ """Trim entry['video'] to clip_duration, burn overlay, write entry['clip_path']."""
247
+ src = entry["video"]
248
+ out = entry["clip_path"]
249
+ out.parent.mkdir(parents=True, exist_ok=True)
250
+ tdir = tmp_root / f"clip_{idx:02d}"
251
+ tdir.mkdir(parents=True, exist_ok=True)
252
+ lines = [
253
+ f"[{idx + 1}/{n}] {entry['scenario_id']}",
254
+ f"label={entry['label']} seed={entry['seed']} success={_success_str(entry['success'])}",
255
+ f"task: {entry['instruction_short']}",
256
+ ]
257
+ box_h = 5 + 3 * (15 + 7) + 4
258
+ overlay = _drawtext_chain(font, lines, tdir, box_h=box_h, fontsize=15)
259
+ # scale to a consistent width (keep aspect, force even dims), then overlay text.
260
+ vf = (f"scale={target_w}:-2:flags=bicubic,setsar=1,{overlay},format=yuv420p")
261
+ args = [
262
+ "-t", f"{entry['clip_duration']:.3f}", "-i", str(src),
263
+ "-an", "-vf", vf, "-r", "20",
264
+ "-c:v", "libx264", "-pix_fmt", "yuv420p", "-preset", "veryfast", "-crf", "23",
265
+ "-movflags", "+faststart", str(out),
266
+ ]
267
+ return _run_ffmpeg(ffmpeg, args, f"clip[{entry['scenario_id']}]")
268
+
269
+
270
+ def make_montage_sequential(ffmpeg: str, clips: list[Path], out_path: Path,
271
+ hold_seconds: float, target_w: int) -> tuple[bool, str]:
272
+ if not clips:
273
+ return False, "no clips to montage"
274
+ inputs: list[str] = []
275
+ for c in clips:
276
+ inputs += ["-i", str(c)]
277
+ # pad the tail of each clip by hold_seconds (freeze last frame) so the overlay is readable,
278
+ # normalize fps/size/sar/pixfmt, then concat.
279
+ n = len(clips)
280
+ parts = []
281
+ for i in range(n):
282
+ parts.append(
283
+ f"[{i}:v]scale={target_w}:-2:flags=bicubic,setsar=1,fps=20,"
284
+ f"tpad=stop_mode=clone:stop_duration={hold_seconds:.3f},format=yuv420p[v{i}]"
285
+ )
286
+ concat_in = "".join(f"[v{i}]" for i in range(n))
287
+ parts.append(f"{concat_in}concat=n={n}:v=1:a=0[out]")
288
+ fc = ";".join(parts)
289
+ args = [*inputs, "-filter_complex", fc, "-map", "[out]", "-an",
290
+ "-c:v", "libx264", "-pix_fmt", "yuv420p", "-preset", "veryfast", "-crf", "23",
291
+ "-movflags", "+faststart", str(out_path)]
292
+ return _run_ffmpeg(ffmpeg, args, "montage(sequential)")
293
+
294
+
295
+ def make_montage_grid(ffmpeg: str, clips: list[Path], out_path: Path, cols: int,
296
+ cell_w: int, cell_h: int, ffprobe: str) -> tuple[bool, str]:
297
+ if not clips:
298
+ return False, "no clips to montage"
299
+ n = len(clips)
300
+ cols = max(1, min(cols, n))
301
+ rows = math.ceil(n / cols)
302
+ n_cells = cols * rows
303
+ # max duration across clips (to pad shorter ones)
304
+ durs = []
305
+ for c in clips:
306
+ info = _probe_video(ffprobe, c)
307
+ durs.append((info or {}).get("duration") or 0.0)
308
+ max_dur = max(durs) if durs else 0.0
309
+ if max_dur <= 0:
310
+ return False, "could not determine clip durations for grid"
311
+
312
+ inputs: list[str] = []
313
+ for c in clips:
314
+ inputs += ["-i", str(c)]
315
+ # extra black inputs to fill the grid
316
+ n_pad = n_cells - n
317
+ for _ in range(n_pad):
318
+ inputs += ["-f", "lavfi", "-t", f"{max_dur:.3f}", "-i",
319
+ f"color=c=black:s={cell_w}x{cell_h}:r=20"]
320
+
321
+ parts = []
322
+ for i in range(n):
323
+ # scale to fit the cell, pad to exact cell size (centered), freeze-pad to max_dur
324
+ parts.append(
325
+ f"[{i}:v]scale={cell_w}:{cell_h}:force_original_aspect_ratio=decrease,"
326
+ f"pad={cell_w}:{cell_h}:(ow-iw)/2:(oh-ih)/2:color=black,setsar=1,fps=20,"
327
+ f"tpad=stop_mode=clone:stop_duration={max(0.0, max_dur - durs[i]):.3f},format=yuv420p[v{i}]"
328
+ )
329
+ for j in range(n_pad):
330
+ k = n + j
331
+ parts.append(f"[{k}:v]setsar=1,fps=20,format=yuv420p[v{k}]")
332
+ layout = "|".join(f"{(i % cols) * cell_w}_{(i // cols) * cell_h}" for i in range(n_cells))
333
+ stack_in = "".join(f"[v{i}]" for i in range(n_cells))
334
+ parts.append(f"{stack_in}xstack=inputs={n_cells}:layout={layout}[out]")
335
+ fc = ";".join(parts)
336
+ args = [*inputs, "-filter_complex", fc, "-map", "[out]", "-an",
337
+ "-c:v", "libx264", "-pix_fmt", "yuv420p", "-preset", "veryfast", "-crf", "23",
338
+ "-movflags", "+faststart", str(out_path)]
339
+ return _run_ffmpeg(ffmpeg, args, "montage(grid)")
340
+
341
+
342
+ # --------------------------------------------------------------------------- #
343
+ # HTML
344
+ # --------------------------------------------------------------------------- #
345
+ _CSS = """
346
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;margin:0;padding:24px;background:#f4f5f7;color:#1a1a1a;}
347
+ h1{margin:0 0 4px 0;} .sub{color:#666;font-size:14px;margin-bottom:18px;}
348
+ .top{background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:16px;margin-bottom:22px;box-shadow:0 1px 3px rgba(0,0,0,.06);}
349
+ .top video{width:100%;max-width:760px;border-radius:8px;background:#000;display:block;}
350
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px;}
351
+ .card{background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:14px;box-shadow:0 1px 3px rgba(0,0,0,.05);}
352
+ .card h3{margin:0 0 6px 0;font-size:15px;}
353
+ .card video{width:100%;border-radius:6px;background:#000;}
354
+ .badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:11px;font-weight:600;}
355
+ .b-normal{background:#e3f0ff;color:#1a5fb4;} .b-abn{background:#ffe9d6;color:#b35a00;}
356
+ .b-ok{background:#e6f6ea;color:#1a7f37;} .b-fail{background:#fde8e8;color:#b42318;} .b-unk{background:#eee;color:#555;}
357
+ .kv{font-size:12.5px;line-height:1.6;margin:6px 0;} .kv b{color:#444;}
358
+ .warn{background:#fff8e1;border:1px solid #ffe082;border-radius:6px;padding:8px 10px;margin:6px 0;font-size:12.5px;color:#7a5c00;}
359
+ a{color:#1a5fb4;} code{background:#f0f0f0;padding:1px 4px;border-radius:3px;font-size:12px;}
360
+ """
361
+
362
+
363
+ def _badge_label(label):
364
+ if label == "normal":
365
+ return '<span class="badge b-normal">normal</span>'
366
+ if label == "abnormal_probe":
367
+ return '<span class="badge b-abn">abnormal_probe</span>'
368
+ return f'<span class="badge b-unk">{html.escape(str(label))}</span>'
369
+
370
+
371
+ def _badge_success(s):
372
+ if s is True:
373
+ return '<span class="badge b-ok">success: true</span>'
374
+ if s is False:
375
+ return '<span class="badge b-fail">success: false</span>'
376
+ return '<span class="badge b-unk">success: unknown</span>'
377
+
378
+
379
+ def render_playlist_html(run_dir: Path, plan: list[dict], montage_rel: str | None,
380
+ montage_mode: str, clip_seconds: float, clips_ok: dict) -> str:
381
+ p = []
382
+ p.append("<!DOCTYPE html><html><head><meta charset='utf-8'>")
383
+ p.append(f"<title>LIBERO smoke-test review montage — {html.escape(run_dir.name)}</title>")
384
+ p.append(f"<style>{_CSS}</style></head><body>")
385
+ p.append("<h1>LIBERO smoke-test — video review</h1>")
386
+ p.append(f"<div class='sub'>run dir: <code>{html.escape(str(run_dir))}</code> &nbsp;|&nbsp; "
387
+ f"generated {html.escape(_dt.datetime.now().isoformat(timespec='seconds'))} &nbsp;|&nbsp; "
388
+ f"{len(plan)} scenarios &nbsp;|&nbsp; clip length ≤ {clip_seconds:g}s &nbsp;|&nbsp; montage mode: {html.escape(montage_mode)}</div>")
389
+ p.append("<div class='top'>")
390
+ if montage_rel:
391
+ p.append(f"<h3 style='margin-top:0'>review_montage.mp4</h3>")
392
+ p.append(f"<video controls preload='metadata' src='{html.escape(montage_rel)}'></video>")
393
+ p.append(f"<div class='kv'><a href='{html.escape(montage_rel)}'>{html.escape(montage_rel)}</a></div>")
394
+ else:
395
+ p.append("<div class='warn'>review_montage.mp4 was not produced (no usable clips, or ffmpeg failed — see console output).</div>")
396
+ p.append("</div>")
397
+ p.append("<div class='grid'>")
398
+ for i, e in enumerate(plan):
399
+ sid = e["scenario_id"]
400
+ p.append("<div class='card'>")
401
+ p.append(f"<h3>[{i + 1}/{len(plan)}] {html.escape(sid)}</h3>")
402
+ p.append("<div class='kv'>" + _badge_label(e["label"]) + " " + _badge_success(e["success"])
403
+ + f" &nbsp; seed={html.escape(str(e['seed']))}</div>")
404
+ clip_ok = clips_ok.get(sid, False)
405
+ if clip_ok and (run_dir / e["clip_rel"]).exists():
406
+ p.append(f"<video controls preload='metadata' src='{html.escape(e['clip_rel'])}'></video>")
407
+ elif e["warning"]:
408
+ p.append(f"<div class='warn'>⚠ {html.escape(str(e['warning']))} — no review clip for this scenario.</div>")
409
+ else:
410
+ p.append("<div class='warn'>⚠ review clip not generated (ffmpeg failed for this scenario — see console).</div>")
411
+ p.append("<div class='kv'>"
412
+ f"<b>instruction:</b> {html.escape(str(e['instruction']))}<br>"
413
+ + (f"<b>env:</b> <code>{html.escape(str(e['env_name']))}</code><br>" if e.get("env_name") else "")
414
+ + f"<b>source video:</b> {e['src_duration']:g}s"
415
+ + (f" &nbsp; <a href='{html.escape(e['video_rel'])}'>full video.mp4</a>" if e.get("video_rel") else " (missing)")
416
+ + (f"<br><b>clip:</b> <a href='{html.escape(e['clip_rel'])}'>{html.escape(e['clip_rel'])}</a> ({e['clip_duration']:g}s)" if clip_ok else "")
417
+ + "</div>")
418
+ p.append("</div>")
419
+ p.append("</div></body></html>")
420
+ return "\n".join(p)
421
+
422
+
423
+ # --------------------------------------------------------------------------- #
424
+ # main
425
+ # --------------------------------------------------------------------------- #
426
+ def main() -> int:
427
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
428
+ ap.add_argument("--run-dir", required=True)
429
+ ap.add_argument("--clip-seconds", type=float, default=5.0, help="Max clip length per scenario (full video if shorter).")
430
+ ap.add_argument("--mode", choices=["sequential", "grid"], default="sequential")
431
+ ap.add_argument("--hold-seconds", type=float, default=1.0,
432
+ help="In sequential mode, freeze the last frame of each clip this long so the overlay is readable.")
433
+ ap.add_argument("--instruction-chars", type=int, default=56, help="Truncate the overlaid instruction to this many chars.")
434
+ ap.add_argument("--cols", type=int, default=0, help="Grid mode: number of columns (0 = auto = ceil(sqrt(N))).")
435
+ ap.add_argument("--width", type=int, default=512, help="Clip width in px (height auto, aspect preserved).")
436
+ ap.add_argument("--open", action="store_true", help="Open review_playlist.html when done.")
437
+ ap.add_argument("--dry-run", action="store_true", help="Print the planned clips and exit without writing anything.")
438
+ args = ap.parse_args()
439
+
440
+ run_dir = Path(args.run_dir).resolve()
441
+ if not run_dir.is_dir():
442
+ _die(f"ERROR: --run-dir does not exist or is not a directory: {run_dir}")
443
+
444
+ ffmpeg, ffprobe = _ensure_ffmpeg()
445
+ font = _find_font()
446
+
447
+ plan = build_plan(run_dir, ffprobe, args.clip_seconds, args.instruction_chars)
448
+ if not plan:
449
+ _die(f"ERROR: no scenario directories found under {run_dir}")
450
+
451
+ n = len(plan)
452
+ n_with_video = sum(1 for e in plan if e["ok_source"])
453
+ n_missing = n - n_with_video
454
+
455
+ # --- dry run -------------------------------------------------------------
456
+ if args.dry_run:
457
+ print(f"[dry-run] run dir: {run_dir}")
458
+ print(f"[dry-run] {n} scenarios; {n_with_video} with usable video.mp4; {n_missing} missing/unreadable")
459
+ print(f"[dry-run] mode={args.mode} clip-seconds={args.clip_seconds:g} hold-seconds={args.hold_seconds:g} font={font or '(builtin sans)'}")
460
+ print(f"[dry-run] would write:")
461
+ print(f" {run_dir / 'review_clips'}/<scenario_id>.mp4")
462
+ print(f" {run_dir / 'review_montage.mp4'}")
463
+ print(f" {run_dir / 'review_playlist.html'}")
464
+ print()
465
+ hdr = f"{'#':>2} {'scenario_id':40s} {'label':14s} {'seed':>5s} {'src_s':>6s} {'clip_s':>6s} {'success':8s} note"
466
+ print(hdr)
467
+ print("-" * len(hdr))
468
+ for i, e in enumerate(plan):
469
+ print(f"{i + 1:>2} {e['scenario_id'][:40]:40s} {str(e['label'])[:14]:14s} "
470
+ f"{str(e['seed']):>5s} {e['src_duration']:>6.2f} {e['clip_duration']:>6.2f} "
471
+ f"{_success_str(e['success']):8s} {e['warning'] or 'ok -> ' + e['clip_rel']}")
472
+ print(f" instruction: {e['instruction_short']}")
473
+ return 0
474
+
475
+ if n_with_video == 0:
476
+ # still write an HTML so the user has something, but no montage.
477
+ print(f"WARNING: none of the {n} scenarios has a usable video.mp4 — writing an HTML with warnings only.", file=sys.stderr)
478
+ html_path = run_dir / "review_playlist.html"
479
+ html_path.write_text(render_playlist_html(run_dir, plan, None, args.mode, args.clip_seconds, {}), encoding="utf-8")
480
+ print("\n==== SUMMARY ====")
481
+ print(f"total scenarios : {n}")
482
+ print(f"clips generated : 0")
483
+ print(f"missing videos : {n_missing} ({', '.join(e['scenario_id'] for e in plan if not e['ok_source'])})")
484
+ print(f"montage path : (not produced)")
485
+ print(f"html path : {html_path}")
486
+ return 1
487
+
488
+ # --- generate per-scenario clips ----------------------------------------
489
+ (run_dir / "review_clips").mkdir(parents=True, exist_ok=True)
490
+ tmp_root = Path(tempfile.mkdtemp(prefix="libero_montage_"))
491
+ clips_ok: dict[str, bool] = {}
492
+ generated_clips: list[Path] = []
493
+ warnings: list[str] = []
494
+ target_w = args.width - (args.width % 2) # even
495
+ for i, e in enumerate(plan):
496
+ sid = e["scenario_id"]
497
+ if not e["ok_source"]:
498
+ clips_ok[sid] = False
499
+ warnings.append(f"[{sid}] {e['warning'] or 'no usable video'} — skipped")
500
+ print(f"[{i + 1}/{n}] {sid}: SKIP ({e['warning'] or 'no usable video'})")
501
+ continue
502
+ ok, err = make_clip(ffmpeg, font, e, i, n, tmp_root, target_w)
503
+ clips_ok[sid] = ok
504
+ if ok and e["clip_path"].exists():
505
+ generated_clips.append(e["clip_path"])
506
+ print(f"[{i + 1}/{n}] {sid}: clip -> {e['clip_rel']} ({e['clip_duration']:g}s)")
507
+ else:
508
+ warnings.append(f"[{sid}] clip generation failed: {err}")
509
+ print(f"[{i + 1}/{n}] {sid}: CLIP FAILED\n{err}", file=sys.stderr)
510
+
511
+ # --- montage -------------------------------------------------------------
512
+ montage_path = run_dir / "review_montage.mp4"
513
+ montage_rel = None
514
+ if generated_clips:
515
+ if args.mode == "grid":
516
+ cols = args.cols if args.cols and args.cols > 0 else max(1, math.ceil(math.sqrt(len(generated_clips))))
517
+ # cell size: keep clips' aspect (target_w x ~target_w/2), shrink so total width <= ~1920
518
+ src_info = next((e["src_info"] for e in plan if e["ok_source"] and e["src_info"]), {"width": 512, "height": 256})
519
+ ar = (src_info.get("height") or 256) / (src_info.get("width") or 512)
520
+ cell_w = min(target_w, max(160, (1920 // cols) - ((1920 // cols) % 2)))
521
+ cell_h = int(round(cell_w * ar))
522
+ cell_h -= cell_h % 2
523
+ ok, err = make_montage_grid(ffmpeg, generated_clips, montage_path, cols, cell_w, cell_h, ffprobe)
524
+ if not ok:
525
+ print(f"WARNING: grid montage failed ({err}); falling back to sequential montage.", file=sys.stderr)
526
+ warnings.append(f"grid montage failed: {err} (fell back to sequential)")
527
+ ok, err = make_montage_sequential(ffmpeg, generated_clips, montage_path, args.hold_seconds, target_w)
528
+ else:
529
+ ok, err = make_montage_sequential(ffmpeg, generated_clips, montage_path, args.hold_seconds, target_w)
530
+ if ok and montage_path.exists():
531
+ montage_rel = os.path.relpath(montage_path, run_dir)
532
+ print(f"montage -> {montage_rel}")
533
+ else:
534
+ warnings.append(f"montage generation failed: {err}")
535
+ print(f"WARNING: montage generation failed: {err}", file=sys.stderr)
536
+ else:
537
+ warnings.append("no clips were generated, so no montage was produced")
538
+
539
+ # --- HTML ----------------------------------------------------------------
540
+ html_path = run_dir / "review_playlist.html"
541
+ html_path.write_text(
542
+ render_playlist_html(run_dir, plan, montage_rel, args.mode, args.clip_seconds, clips_ok),
543
+ encoding="utf-8",
544
+ )
545
+
546
+ # cleanup temp dir
547
+ shutil.rmtree(tmp_root, ignore_errors=True)
548
+
549
+ # --- summary -------------------------------------------------------------
550
+ missing_ids = [e["scenario_id"] for e in plan if not e["ok_source"]]
551
+ failed_ids = [sid for sid, ok in clips_ok.items() if ok is False and sid not in missing_ids]
552
+ print("\n==== SUMMARY ====")
553
+ print(f"total scenarios : {n}")
554
+ print(f"clips generated : {len(generated_clips)} ({run_dir / 'review_clips'}/)")
555
+ if failed_ids:
556
+ print(f"clip failures : {len(failed_ids)} ({', '.join(failed_ids)})")
557
+ print(f"missing videos : {len(missing_ids)}" + (f" ({', '.join(missing_ids)})" if missing_ids else ""))
558
+ print(f"montage path : {montage_path if montage_rel else '(not produced)'}")
559
+ print(f"html path : {html_path}")
560
+ if warnings:
561
+ print(f"\n{len(warnings)} warning(s):")
562
+ for w in warnings:
563
+ print(f" - {w}")
564
+
565
+ if args.open:
566
+ import webbrowser
567
+ try:
568
+ webbrowser.open(html_path.as_uri())
569
+ print(f"\nOpened {html_path.as_uri()}")
570
+ except Exception:
571
+ print(f"\nCould not auto-open a browser; open manually:\n {html_path}")
572
+
573
+ # exit non-zero only if we produced *nothing* useful
574
+ return 0 if generated_clips else 1
575
+
576
+
577
+ if __name__ == "__main__":
578
+ raise SystemExit(main())
examples/LIBERO/smoke_tests/review_smoke_tests.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License").
5
+ """List smoke-test scenarios + their video paths, and optionally play them.
6
+
7
+ Usage::
8
+
9
+ # most recent run under outputs/libero_smoke_tests/
10
+ python examples/LIBERO/smoke_tests/review_smoke_tests.py
11
+
12
+ # a specific run dir
13
+ python examples/LIBERO/smoke_tests/review_smoke_tests.py --run-dir outputs/libero_smoke_tests/20260512_120000
14
+
15
+ # try to open each video one by one (xdg-open / open / ffplay, if available)
16
+ python examples/LIBERO/smoke_tests/review_smoke_tests.py --open
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import json
23
+ from pathlib import Path
24
+ import shutil
25
+ import subprocess
26
+ import sys
27
+
28
+ REPO_ROOT = Path(__file__).resolve().parents[3]
29
+ DEFAULT_OUTPUT_DIR = REPO_ROOT / "outputs" / "libero_smoke_tests"
30
+
31
+
32
+ def _latest_run_dir(output_dir: Path) -> Path | None:
33
+ if not output_dir.exists():
34
+ return None
35
+ runs = sorted((p for p in output_dir.iterdir() if p.is_dir()), key=lambda p: p.name)
36
+ return runs[-1] if runs else None
37
+
38
+
39
+ def _find_video(scenario_dir: Path) -> Path | None:
40
+ cand = scenario_dir / "video.mp4"
41
+ if cand.exists():
42
+ return cand
43
+ vids = sorted(scenario_dir.glob("**/*.mp4"))
44
+ return vids[0] if vids else None
45
+
46
+
47
+ def _open_video(path: Path) -> None:
48
+ for opener in ("xdg-open", "open", "ffplay"):
49
+ exe = shutil.which(opener)
50
+ if exe:
51
+ args = [exe, str(path)]
52
+ if opener == "ffplay":
53
+ args = [exe, "-autoexit", "-loglevel", "error", str(path)]
54
+ print(f" opening with: {' '.join(args)}")
55
+ try:
56
+ subprocess.run(args, check=False)
57
+ except Exception as e: # noqa: BLE001
58
+ print(f" (failed to open: {e})")
59
+ return
60
+ print(" no video opener found (xdg-open / open / ffplay). Path printed above.")
61
+
62
+
63
+ def main() -> int:
64
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
65
+ ap.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR))
66
+ ap.add_argument("--run-dir", default=None, help="Specific <output-dir>/<timestamp> dir.")
67
+ ap.add_argument("--open", action="store_true", help="Try to play each video, pausing between.")
68
+ args = ap.parse_args()
69
+
70
+ run_dir = Path(args.run_dir) if args.run_dir else _latest_run_dir(Path(args.output_dir))
71
+ if run_dir is None or not run_dir.exists():
72
+ print(f"No run directory found under {args.output_dir}. Run run_10_smoke_tests.py first.",
73
+ file=sys.stderr)
74
+ return 1
75
+
76
+ print(f"Run directory: {run_dir}\n")
77
+
78
+ summary_path = run_dir / "summary.json"
79
+ rows = []
80
+ if summary_path.exists():
81
+ try:
82
+ rows = json.loads(summary_path.read_text()).get("scenarios", [])
83
+ except Exception:
84
+ rows = []
85
+
86
+ scenario_dirs = sorted(p for p in run_dir.iterdir() if p.is_dir())
87
+ by_id = {r.get("scenario_id"): r for r in rows}
88
+
89
+ for i, sc_dir in enumerate(scenario_dirs, 1):
90
+ sid = sc_dir.name
91
+ row = by_id.get(sid, {})
92
+ meta = {}
93
+ mp = sc_dir / "metadata.json"
94
+ if mp.exists():
95
+ try:
96
+ meta = json.loads(mp.read_text())
97
+ except Exception:
98
+ meta = {}
99
+ label = row.get("label") or meta.get("label", "?")
100
+ success = row.get("success", "unknown")
101
+ video = _find_video(sc_dir)
102
+ actions = sc_dir / "actions.npy"
103
+ print(f"[{i:2d}] {sid}")
104
+ print(f" label={label} success={success} "
105
+ f"rollout_started={row.get('rollout_started')} "
106
+ f"actions={'yes' if actions.exists() else 'no'}")
107
+ env_name = (meta.get("resolved") or {}).get("env_name") or (meta.get("manifest_entry") or {}).get("env_name")
108
+ if env_name:
109
+ print(f" env: {env_name}")
110
+ if row.get("error_if_any"):
111
+ print(f" error: {row['error_if_any']}")
112
+ if video:
113
+ print(f" video: {video}")
114
+ else:
115
+ frames = sc_dir / "frames"
116
+ if frames.exists():
117
+ n = len(list(frames.glob('*.png')))
118
+ print(f" frames: {frames}/ ({n} png)")
119
+ else:
120
+ print(f" video: <none saved>")
121
+ print()
122
+ if args.open and video:
123
+ _open_video(video)
124
+ if i < len(scenario_dirs):
125
+ try:
126
+ input(" [enter] for next, Ctrl-C to stop... ")
127
+ except (EOFError, KeyboardInterrupt):
128
+ print()
129
+ break
130
+
131
+ print(f"Summary: {summary_path if summary_path.exists() else '(not found)'}")
132
+ md = run_dir / "summary.md"
133
+ if md.exists():
134
+ print(f"Markdown summary: {md}")
135
+ return 0
136
+
137
+
138
+ if __name__ == "__main__":
139
+ raise SystemExit(main())
examples/LIBERO/smoke_tests/run_10_smoke_tests.py ADDED
@@ -0,0 +1,590 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License").
5
+ """Run 10 short, simulation-only LIBERO smoke tests against a GR00T policy server.
6
+
7
+ What this does (and does NOT do):
8
+ * It does NOT use any physical robot hardware -- LIBERO simulation only.
9
+ * It does NOT rewrite the official evaluation path. Each scenario is run
10
+ through ``gr00t.eval.rollout_policy.run_rollout_gymnasium_policy`` via the
11
+ thin ``_libero_rollout_worker.py`` (which only adds action recording and a
12
+ couple of tiny, optional simulation-only perturbations).
13
+ * It does NOT fake successful rollouts. If the model checkpoint is missing,
14
+ or the server is unreachable, or the LIBERO sim env is not installed, it
15
+ fails with a clear, actionable error message.
16
+
17
+ Typical usage (two terminals):
18
+
19
+ Terminal 1 - start the GR00T inference server::
20
+
21
+ uv run python gr00t/eval/run_gr00t_server.py \
22
+ --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 \
23
+ --embodiment-tag LIBERO_PANDA \
24
+ --use-sim-policy-wrapper
25
+
26
+ Terminal 2 - run the smoke tests::
27
+
28
+ uv run python examples/LIBERO/smoke_tests/run_10_smoke_tests.py \
29
+ --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 \
30
+ --manifest examples/LIBERO/smoke_tests/scenarios_10.yaml \
31
+ --output-dir outputs/libero_smoke_tests \
32
+ --max-episode-steps 50 --save-video --render
33
+
34
+ Pass ``--start-server`` to have this script launch (and later kill) the server
35
+ itself, or ``--dry-run`` to validate the manifest / paths without running sims.
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import argparse
41
+ import datetime as _dt
42
+ import json
43
+ import os
44
+ from pathlib import Path
45
+ import shutil
46
+ import subprocess
47
+ import sys
48
+ import time
49
+ import traceback
50
+
51
+ REPO_ROOT = Path(__file__).resolve().parents[3]
52
+ WORKER = Path(__file__).resolve().parent / "_libero_rollout_worker.py"
53
+ DEFAULT_MANIFEST = Path(__file__).resolve().parent / "scenarios_10.yaml"
54
+ RESULT_PREFIX = "SMOKE_RESULT_JSON:"
55
+
56
+ # Path to the dedicated LIBERO uv venv created by setup_libero.sh.
57
+ LIBERO_VENV_PYTHON = (
58
+ REPO_ROOT / "gr00t" / "eval" / "sim" / "LIBERO" / "libero_uv" / ".venv" / "bin" / "python"
59
+ )
60
+
61
+ SERVER_CMD_TEMPLATE = [
62
+ "uv", "run", "python", "gr00t/eval/run_gr00t_server.py",
63
+ "--model-path", "{model_path}",
64
+ "--embodiment-tag", "LIBERO_PANDA",
65
+ "--use-sim-policy-wrapper",
66
+ ]
67
+
68
+
69
+ # --------------------------------------------------------------------------- #
70
+ # Helpers
71
+ # --------------------------------------------------------------------------- #
72
+ def _load_manifest(path: Path) -> list[dict]:
73
+ try:
74
+ import yaml
75
+ except ImportError as e: # pragma: no cover
76
+ raise SystemExit(
77
+ "PyYAML is required to read the scenario manifest. Install it with "
78
+ "`uv pip install pyyaml` (it is a transitive dependency of the gr00t "
79
+ "package, so this normally just works inside the project venv)."
80
+ ) from e
81
+ if not path.exists():
82
+ raise SystemExit(f"Manifest not found: {path}")
83
+ with open(path) as f:
84
+ data = yaml.safe_load(f)
85
+ scenarios = data.get("scenarios") if isinstance(data, dict) else data
86
+ if not isinstance(scenarios, list) or not scenarios:
87
+ raise SystemExit(f"Manifest {path} does not contain a non-empty 'scenarios' list.")
88
+ return scenarios
89
+
90
+
91
+ def _check_model_path(model_path: Path) -> None:
92
+ if model_path.exists():
93
+ # Must look like a real checkpoint dir.
94
+ has_cfg = (model_path / "config.json").exists()
95
+ has_weights = any(model_path.glob("*.safetensors")) or (model_path / "pytorch_model.bin").exists()
96
+ if has_cfg and has_weights:
97
+ return
98
+ missing = []
99
+ if not has_cfg:
100
+ missing.append("config.json")
101
+ if not has_weights:
102
+ missing.append("model-*.safetensors / model.safetensors.index.json")
103
+ raise SystemExit(
104
+ f"Model path {model_path} exists but is missing: {', '.join(missing)}.\n"
105
+ "Re-download the checkpoint:\n\n" + _download_hint()
106
+ )
107
+ raise SystemExit(
108
+ f"Model checkpoint not found at: {model_path}\n\n"
109
+ "Download it first (HuggingFace does not support nested repo paths directly):\n\n"
110
+ + _download_hint()
111
+ )
112
+
113
+
114
+ def _download_hint() -> str:
115
+ return (
116
+ " uv run hf download nvidia/GR00T-N1.7-LIBERO \\\n"
117
+ " --include \"libero_10/config.json\" \\\n"
118
+ " \"libero_10/embodiment_id.json\" \\\n"
119
+ " \"libero_10/model-*.safetensors\" \\\n"
120
+ " \"libero_10/model.safetensors.index.json\" \\\n"
121
+ " \"libero_10/processor_config.json\" \\\n"
122
+ " \"libero_10/statistics.json\" \\\n"
123
+ " --local-dir checkpoints/GR00T-N1.7-LIBERO\n"
124
+ " # (also fetch libero_10/config.json explicitly if the glob above skips it)\n"
125
+ )
126
+
127
+
128
+ def _server_command(model_path: Path) -> list[str]:
129
+ return [tok.format(model_path=str(model_path)) for tok in SERVER_CMD_TEMPLATE]
130
+
131
+
132
+ # The GR00T-N1.7 backbone (nvidia/Cosmos-Reason2-2B, a Qwen3-VL model) is a
133
+ # *gated* HuggingFace repo. Loading any GR00T-N1.7 checkpoint pulls that base
134
+ # repo's config/processor, so the server will fail to start without HF auth +
135
+ # granted access. We surface a clear hint when we detect this.
136
+ GATED_BACKBONE_HINT = (
137
+ "The GR00T-N1.7 backbone 'nvidia/Cosmos-Reason2-2B' is a GATED HuggingFace repo.\n"
138
+ "To start the server you must:\n"
139
+ " 1. Request access at https://huggingface.co/nvidia/Cosmos-Reason2-2B (one click, usually instant).\n"
140
+ " 2. Authenticate, e.g. export HF_TOKEN=hf_xxx (or: uv run hf auth login)\n"
141
+ " 3. Re-run the server / smoke tests.\n"
142
+ )
143
+
144
+
145
+ def _scan_log_for_gated_repo(log_path: Path) -> bool:
146
+ try:
147
+ text = log_path.read_text(errors="replace")
148
+ except OSError:
149
+ return False
150
+ return ("gated repo" in text) or ("Cosmos-Reason2-2B is restricted" in text) or (
151
+ "Access to model nvidia/Cosmos-Reason2-2B" in text
152
+ )
153
+
154
+
155
+ def _ping_server(host: str, port: int, timeout_ms: int = 3000) -> bool:
156
+ """Return True if a GR00T policy server answers on host:port.
157
+
158
+ First does a cheap TCP connect (so we don't drag in the heavy torch/gr00t
159
+ import stack when nothing is listening); only if *something* is listening do
160
+ we import ``PolicyClient`` and validate the msgpack ``ping`` endpoint.
161
+ """
162
+ import socket
163
+
164
+ try:
165
+ with socket.create_connection((host, port), timeout=timeout_ms / 1000.0):
166
+ pass
167
+ except OSError:
168
+ return False
169
+ # Something is listening -- confirm it actually speaks the GR00T protocol.
170
+ try:
171
+ from gr00t.policy.server_client import PolicyClient
172
+
173
+ client = PolicyClient(host=host, port=port, timeout_ms=timeout_ms)
174
+ return bool(client.ping())
175
+ except Exception:
176
+ # Reachable on TCP but the client/import failed; treat as "up enough".
177
+ return True
178
+
179
+
180
+ def _resolve_libero_python(explicit: str | None) -> str:
181
+ if explicit:
182
+ p = Path(explicit)
183
+ if not p.exists():
184
+ raise SystemExit(f"--libero-python {explicit} does not exist.")
185
+ return str(p)
186
+ if LIBERO_VENV_PYTHON.exists():
187
+ return str(LIBERO_VENV_PYTHON)
188
+ # Fall back to the current interpreter, but warn -- LIBERO needs its own venv.
189
+ print(
190
+ "WARNING: the dedicated LIBERO uv venv was not found at\n"
191
+ f" {LIBERO_VENV_PYTHON}\n"
192
+ "Falling back to the current Python interpreter. If LIBERO / robosuite\n"
193
+ "are not importable there, set up the sim env first:\n"
194
+ " sudo apt update && sudo apt install libegl1-mesa-dev libglu1-mesa\n"
195
+ " bash gr00t/eval/sim/LIBERO/setup_libero.sh\n",
196
+ file=sys.stderr,
197
+ )
198
+ return sys.executable
199
+
200
+
201
+ def _start_server(model_path: Path, host: str, port: int, log_path: Path):
202
+ cmd = [
203
+ sys.executable, str(REPO_ROOT / "gr00t" / "eval" / "run_gr00t_server.py"),
204
+ "--model-path", str(model_path),
205
+ "--embodiment-tag", "LIBERO_PANDA",
206
+ "--use-sim-policy-wrapper",
207
+ "--host", host,
208
+ "--port", str(port),
209
+ ]
210
+ log_f = open(log_path, "w")
211
+ print(f"Starting GR00T server (logging to {log_path}):\n {' '.join(cmd)}")
212
+ proc = subprocess.Popen(cmd, stdout=log_f, stderr=subprocess.STDOUT, cwd=str(REPO_ROOT))
213
+ return proc, log_f
214
+
215
+
216
+ # --------------------------------------------------------------------------- #
217
+ # Main
218
+ # --------------------------------------------------------------------------- #
219
+ def main() -> int:
220
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
221
+ ap.add_argument("--model-path", default="checkpoints/GR00T-N1.7-LIBERO/libero_10")
222
+ ap.add_argument("--host", default="127.0.0.1")
223
+ ap.add_argument("--port", type=int, default=5555)
224
+ ap.add_argument("--manifest", default=str(DEFAULT_MANIFEST))
225
+ ap.add_argument("--output-dir", default="outputs/libero_smoke_tests")
226
+ ap.add_argument("--max-episode-steps", type=int, default=50,
227
+ help="Upper cap (and default) on max_episode_steps for every scenario.")
228
+ ap.add_argument("--n-action-steps", type=int, default=8)
229
+ ap.add_argument("--save-video", action="store_true",
230
+ help="Force-enable video recording for every scenario.")
231
+ ap.add_argument("--render", action="store_true",
232
+ help="Also dump decoded frames into <scenario>/frames/.")
233
+ ap.add_argument("--dry-run", action="store_true",
234
+ help="Validate manifest/model path/server, write metadata, run no sims.")
235
+ ap.add_argument("--resume", action="store_true",
236
+ help="Skip scenarios that already have a rollout_summary.json in the run dir.")
237
+ ap.add_argument("--run-dir", default=None,
238
+ help="Existing <output-dir>/<timestamp> dir to resume into "
239
+ "(default: create a new timestamped dir).")
240
+ ap.add_argument("--libero-python", default=None,
241
+ help="Path to the LIBERO uv venv python (default: auto-detect).")
242
+ ap.add_argument("--start-server", action="store_true",
243
+ help="Launch the GR00T server in a subprocess and kill it at the end.")
244
+ ap.add_argument("--per-scenario-timeout", type=int, default=900,
245
+ help="Hard timeout (seconds) for each scenario subprocess.")
246
+ args = ap.parse_args()
247
+
248
+ os.chdir(REPO_ROOT)
249
+
250
+ model_path = Path(args.model_path)
251
+ manifest_path = Path(args.manifest)
252
+ scenarios = _load_manifest(manifest_path)
253
+
254
+ print(f"Loaded {len(scenarios)} scenarios from {manifest_path}")
255
+ n_normal = sum(1 for s in scenarios if s.get("label") == "normal")
256
+ n_abnormal = sum(1 for s in scenarios if s.get("label") == "abnormal_probe")
257
+ print(f" labels: {n_normal} normal, {n_abnormal} abnormal_probe")
258
+
259
+ server_cmd_str = " ".join(_server_command(model_path))
260
+ print("\nExpected GR00T server command (run this in a separate terminal):\n " + server_cmd_str + "\n")
261
+ print("NOTE: " + GATED_BACKBONE_HINT)
262
+
263
+ # ---- validate model path -------------------------------------------------
264
+ _check_model_path(model_path)
265
+ print(f"Model checkpoint OK: {model_path}")
266
+
267
+ # ---- output dir ----------------------------------------------------------
268
+ out_root = Path(args.output_dir)
269
+ if args.run_dir:
270
+ run_dir = Path(args.run_dir)
271
+ run_dir.mkdir(parents=True, exist_ok=True)
272
+ else:
273
+ ts = _dt.datetime.now().strftime("%Y%m%d_%H%M%S")
274
+ run_dir = out_root / ts
275
+ run_dir.mkdir(parents=True, exist_ok=True)
276
+ print(f"Run directory: {run_dir}")
277
+
278
+ # ---- optionally start server --------------------------------------------
279
+ server_proc = None
280
+ server_log_f = None
281
+ if args.start_server and not args.dry_run:
282
+ server_proc, server_log_f = _start_server(model_path, args.host, args.port,
283
+ run_dir / "server.log")
284
+
285
+ # ---- check server reachability ------------------------------------------
286
+ reachable = _ping_server(args.host, args.port)
287
+ if args.start_server and not args.dry_run and not reachable:
288
+ # give the server time to load the (multi-GB) model
289
+ print("Waiting for the GR00T server to come up (loading model can take a few minutes)...")
290
+ for _ in range(120):
291
+ time.sleep(5)
292
+ if server_proc is not None and server_proc.poll() is not None:
293
+ hint = ""
294
+ if _scan_log_for_gated_repo(run_dir / "server.log"):
295
+ hint = "\n\n" + GATED_BACKBONE_HINT
296
+ raise SystemExit(
297
+ f"The GR00T server process exited (rc={server_proc.returncode}); "
298
+ f"see {run_dir / 'server.log'}.{hint}"
299
+ )
300
+ if _ping_server(args.host, args.port):
301
+ reachable = True
302
+ break
303
+
304
+ if not reachable and not args.dry_run:
305
+ gated_hint = ""
306
+ if args.start_server and _scan_log_for_gated_repo(run_dir / "server.log"):
307
+ gated_hint = "\n" + GATED_BACKBONE_HINT
308
+ msg = (
309
+ f"\nERROR: no GR00T policy server is reachable at {args.host}:{args.port}.\n\n"
310
+ "Start it first in a separate terminal:\n\n " + server_cmd_str + "\n\n"
311
+ "...then re-run this script. (Or pass --start-server to have this script\n"
312
+ "launch it for you, or --dry-run to validate everything without running sims.)\n"
313
+ + gated_hint
314
+ )
315
+ print(msg, file=sys.stderr)
316
+ return 2
317
+ if reachable:
318
+ print(f"GR00T server reachable at {args.host}:{args.port}.")
319
+ else:
320
+ print("(dry-run) skipping server reachability requirement.")
321
+
322
+ libero_python = _resolve_libero_python(args.libero_python)
323
+ print(f"LIBERO rollout interpreter: {libero_python}")
324
+
325
+ # ---- run scenarios -------------------------------------------------------
326
+ rows: list[dict] = []
327
+ try:
328
+ for i, sc in enumerate(scenarios, 1):
329
+ sid = sc["id"]
330
+ label = sc.get("label", "normal")
331
+ seed = int(sc.get("seed", 0))
332
+ # Keep a scenario's own (smaller) budget, but cap larger ones at --max-episode-steps.
333
+ mes = min(int(sc.get("max_episode_steps", args.max_episode_steps)), args.max_episode_steps)
334
+ n_action_steps = int(sc.get("n_action_steps", args.n_action_steps))
335
+ env_name = sc["env_name"]
336
+ save_video = bool(sc.get("save_video", True)) or args.save_video
337
+ obs_noise_std = float(sc.get("obs_noise_std", 0.0) or 0.0)
338
+ action_repeat = int(sc.get("action_repeat", 1) or 1)
339
+ instruction_override = sc.get("instruction_override")
340
+
341
+ sc_dir = run_dir / sid
342
+ sc_dir.mkdir(parents=True, exist_ok=True)
343
+
344
+ print(f"\n[{i}/{len(scenarios)}] {sid} ({label}) env={env_name} "
345
+ f"seed={seed} max_episode_steps={mes}")
346
+
347
+ # metadata.json (always written)
348
+ metadata = {
349
+ "scenario_id": sid,
350
+ "label": label,
351
+ "manifest_entry": sc,
352
+ "resolved": {
353
+ "env_name": env_name,
354
+ "seed": seed,
355
+ "max_episode_steps": mes,
356
+ "n_action_steps": n_action_steps,
357
+ "save_video": save_video,
358
+ "save_frames": bool(args.render),
359
+ "obs_noise_std": obs_noise_std,
360
+ "action_repeat": action_repeat,
361
+ "instruction_override": instruction_override,
362
+ },
363
+ "model_path": str(model_path),
364
+ "policy_server": {"host": args.host, "port": args.port},
365
+ "libero_python": libero_python,
366
+ "timestamp": _dt.datetime.now().isoformat(timespec="seconds"),
367
+ }
368
+ with open(sc_dir / "metadata.json", "w") as f:
369
+ json.dump(metadata, f, indent=2)
370
+
371
+ if instruction_override:
372
+ print(f" NOTE: instruction_override is set ({instruction_override!r}) but is not "
373
+ "yet wired into the LIBERO env path; it will be ignored by this rollout.")
374
+
375
+ # resume?
376
+ if args.resume and (sc_dir / "rollout_summary.json").exists():
377
+ try:
378
+ prev = json.loads((sc_dir / "rollout_summary.json").read_text())
379
+ except Exception:
380
+ prev = {}
381
+ print(f" --resume: existing rollout_summary.json found, skipping.")
382
+ rows.append({
383
+ "scenario_id": sid, "label": label, "seed": seed,
384
+ "rollout_started": prev.get("error") is None,
385
+ "actions_produced": bool(prev.get("actions_path")),
386
+ "video_saved": bool(prev.get("video_path")),
387
+ "success": prev.get("success", "unknown"),
388
+ "output_dir": str(sc_dir),
389
+ "error_if_any": prev.get("error"),
390
+ "resumed": True,
391
+ })
392
+ continue
393
+
394
+ if args.dry_run:
395
+ with open(sc_dir / "rollout_summary.json", "w") as f:
396
+ json.dump({"status": "dry_run", "env_name": env_name, "seed": seed}, f, indent=2)
397
+ rows.append({
398
+ "scenario_id": sid, "label": label, "seed": seed,
399
+ "rollout_started": False, "actions_produced": False, "video_saved": False,
400
+ "success": "unknown", "output_dir": str(sc_dir),
401
+ "error_if_any": "dry-run (no rollout executed)",
402
+ })
403
+ continue
404
+
405
+ # build worker command
406
+ cmd = [
407
+ libero_python, str(WORKER),
408
+ "--env-name", env_name,
409
+ "--host", args.host, "--port", str(args.port),
410
+ "--max-episode-steps", str(mes),
411
+ "--n-action-steps", str(n_action_steps),
412
+ "--seed", str(seed),
413
+ "--out-dir", str(sc_dir),
414
+ "--obs-noise-std", str(obs_noise_std),
415
+ "--action-repeat", str(action_repeat),
416
+ ]
417
+ cmd += ["--save-video"] if save_video else ["--no-save-video"]
418
+ if args.render and save_video:
419
+ cmd += ["--save-frames"]
420
+
421
+ stdout_path = sc_dir / "stdout.log"
422
+ stderr_path = sc_dir / "stderr.log"
423
+ t0 = time.time()
424
+ try:
425
+ with open(stdout_path, "w") as so, open(stderr_path, "w") as se:
426
+ proc = subprocess.run(
427
+ cmd, stdout=so, stderr=se, cwd=str(REPO_ROOT),
428
+ timeout=args.per_scenario_timeout,
429
+ )
430
+ rc = proc.returncode
431
+ except subprocess.TimeoutExpired:
432
+ rc = -9
433
+ with open(stderr_path, "a") as se:
434
+ se.write(f"\n[runner] scenario timed out after {args.per_scenario_timeout}s\n")
435
+ elapsed = time.time() - t0
436
+
437
+ # parse worker result
438
+ result = None
439
+ try:
440
+ for line in reversed(stdout_path.read_text().splitlines()):
441
+ line = line.strip()
442
+ if line.startswith(RESULT_PREFIX):
443
+ result = json.loads(line[len(RESULT_PREFIX):].strip())
444
+ break
445
+ except Exception:
446
+ pass
447
+ if result is None:
448
+ err_tail = ""
449
+ try:
450
+ err_tail = "\n".join(stderr_path.read_text().splitlines()[-5:])
451
+ except Exception:
452
+ pass
453
+ result = {
454
+ "ok": False, "rollout_started": False, "actions_produced": False,
455
+ "video_saved": False, "success": None,
456
+ "error": (f"worker exited rc={rc} with no result line; "
457
+ f"see {stderr_path}. tail:\n{err_tail}"),
458
+ }
459
+
460
+ status = "ok" if result.get("ok") else "FAILED"
461
+ print(f" -> {status} rc={rc} {elapsed:.1f}s "
462
+ f"actions={'yes' if result.get('actions_produced') else 'no'} "
463
+ f"video={'yes' if result.get('video_saved') else 'no'} "
464
+ f"success={result.get('success')}")
465
+ if result.get("error"):
466
+ print(f" error: {result['error']}")
467
+
468
+ rows.append({
469
+ "scenario_id": sid, "label": label, "seed": seed,
470
+ "rollout_started": bool(result.get("rollout_started")),
471
+ "actions_produced": bool(result.get("actions_produced")),
472
+ "video_saved": bool(result.get("video_saved")),
473
+ "success": result.get("success") if result.get("success") is not None else "unknown",
474
+ "output_dir": str(sc_dir),
475
+ "error_if_any": result.get("error"),
476
+ "rc": rc,
477
+ "elapsed_sec": round(elapsed, 1),
478
+ "n_action_calls": result.get("n_action_calls"),
479
+ "episode_length": result.get("episode_length"),
480
+ "video_path": result.get("video_path"),
481
+ "actions_path": result.get("actions_path"),
482
+ })
483
+ finally:
484
+ if server_proc is not None:
485
+ print("\nStopping GR00T server subprocess...")
486
+ server_proc.terminate()
487
+ try:
488
+ server_proc.wait(timeout=15)
489
+ except subprocess.TimeoutExpired:
490
+ server_proc.kill()
491
+ if server_log_f is not None:
492
+ server_log_f.close()
493
+
494
+ # ---- summary -------------------------------------------------------------
495
+ summary = {
496
+ "run_dir": str(run_dir),
497
+ "manifest": str(manifest_path),
498
+ "model_path": str(model_path),
499
+ "policy_server": {"host": args.host, "port": args.port},
500
+ "server_command": server_cmd_str,
501
+ "dry_run": args.dry_run,
502
+ "n_scenarios": len(scenarios),
503
+ "n_normal": n_normal,
504
+ "n_abnormal_probe": n_abnormal,
505
+ "timestamp": _dt.datetime.now().isoformat(timespec="seconds"),
506
+ "scenarios": rows,
507
+ "totals": {
508
+ "rollout_started": sum(1 for r in rows if r.get("rollout_started")),
509
+ "actions_produced": sum(1 for r in rows if r.get("actions_produced")),
510
+ "video_saved": sum(1 for r in rows if r.get("video_saved")),
511
+ "errors": sum(1 for r in rows if r.get("error_if_any") and "dry-run" not in str(r.get("error_if_any"))),
512
+ },
513
+ }
514
+ write_summary(run_dir, summary)
515
+ print_table(rows)
516
+ print(f"\nWrote: {run_dir / 'summary.json'}")
517
+ print(f"Wrote: {run_dir / 'summary.md'}")
518
+ print(f"\nReview the videos with:\n python {Path(__file__).resolve().parent / 'review_smoke_tests.py'} "
519
+ f"--run-dir {run_dir}\n")
520
+
521
+ # exit code: 0 if dry-run or every scenario at least started; else 1
522
+ if args.dry_run:
523
+ return 0
524
+ failed = summary["totals"]["errors"]
525
+ if failed == 0:
526
+ return 0
527
+ print(f"{failed}/{len(scenarios)} scenario(s) reported an error -- exiting non-zero.", file=sys.stderr)
528
+ return 1
529
+
530
+
531
+ def write_summary(run_dir: Path, summary: dict) -> None:
532
+ """Write summary.json and summary.md. Returns nothing; raises on JSON errors."""
533
+ with open(run_dir / "summary.json", "w") as f:
534
+ json.dump(summary, f, indent=2)
535
+ # markdown
536
+ lines = []
537
+ lines.append(f"# LIBERO smoke-test summary\n")
538
+ lines.append(f"- run dir: `{summary['run_dir']}`")
539
+ lines.append(f"- model: `{summary['model_path']}`")
540
+ lines.append(f"- server: `{summary['policy_server']['host']}:{summary['policy_server']['port']}`")
541
+ lines.append(f"- server command: `{summary['server_command']}`")
542
+ lines.append(f"- scenarios: {summary['n_scenarios']} ({summary['n_normal']} normal, "
543
+ f"{summary['n_abnormal_probe']} abnormal_probe)")
544
+ if summary.get("dry_run"):
545
+ lines.append(f"- **DRY RUN** (no rollouts executed)")
546
+ t = summary["totals"]
547
+ lines.append(f"- totals: rollout_started={t['rollout_started']}, actions_produced={t['actions_produced']}, "
548
+ f"video_saved={t['video_saved']}, errors={t['errors']}\n")
549
+ lines.append("| scenario_id | label | seed | rollout_started | actions_produced | video_saved | success | output_dir | error_if_any |")
550
+ lines.append("|---|---|---|---|---|---|---|---|---|")
551
+ for r in summary["scenarios"]:
552
+ err = (str(r.get("error_if_any")) or "").replace("\n", " ").replace("|", "\\|")
553
+ if len(err) > 160:
554
+ err = err[:157] + "..."
555
+ lines.append("| {id} | {label} | {seed} | {rs} | {ap} | {vs} | {succ} | {od} | {err} |".format(
556
+ id=r["scenario_id"], label=r["label"], seed=r["seed"],
557
+ rs="yes" if r.get("rollout_started") else "no",
558
+ ap="yes" if r.get("actions_produced") else "no",
559
+ vs="yes" if r.get("video_saved") else "no",
560
+ succ=r.get("success"), od=r["output_dir"], err=err or "",
561
+ ))
562
+ (run_dir / "summary.md").write_text("\n".join(lines) + "\n")
563
+
564
+
565
+ def print_table(rows: list[dict]) -> None:
566
+ headers = ["scenario_id", "label", "seed", "rollout_started", "actions_produced",
567
+ "video_saved", "success", "output_dir", "error_if_any"]
568
+ def cell(r, h):
569
+ if h == "rollout_started":
570
+ return "yes" if r.get("rollout_started") else "no"
571
+ if h == "actions_produced":
572
+ return "yes" if r.get("actions_produced") else "no"
573
+ if h == "video_saved":
574
+ return "yes" if r.get("video_saved") else "no"
575
+ if h == "error_if_any":
576
+ e = str(r.get("error_if_any") or "")
577
+ e = e.replace("\n", " ")
578
+ return (e[:57] + "...") if len(e) > 60 else e
579
+ return str(r.get(h, ""))
580
+ table = [headers] + [[cell(r, h) for h in headers] for r in rows]
581
+ widths = [max(len(row[i]) for row in table) for i in range(len(headers))]
582
+ print("\n" + "=" * 8 + " SMOKE-TEST SUMMARY " + "=" * 8)
583
+ for ri, row in enumerate(table):
584
+ print(" " + " | ".join(c.ljust(widths[i]) for i, c in enumerate(row)))
585
+ if ri == 0:
586
+ print(" " + "-+-".join("-" * w for w in widths))
587
+
588
+
589
+ if __name__ == "__main__":
590
+ raise SystemExit(main())
examples/LIBERO/smoke_tests/scenarios_10.yaml ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LIBERO simulation-only smoke-test manifest (10 scenarios)
2
+ #
3
+ # These are *short* rollouts intended to verify the deploy/eval plumbing for
4
+ # nvidia/GR00T-N1.7-LIBERO (libero_10 checkpoint). They are NOT a benchmark:
5
+ # max_episode_steps is tiny on purpose, so success rates here are meaningless.
6
+ #
7
+ # Scenario fields
8
+ # id : unique short identifier (used as the output sub-directory name)
9
+ # label : "normal" | "abnormal_probe"
10
+ # env_name : gymnasium id registered by gr00t.eval.sim.LIBERO.libero_env
11
+ # (register_libero_envs registers libero_10 / spatial / object / goal / 90)
12
+ # instruction : human-readable task instruction (informational; the *real*
13
+ # instruction is supplied by the LIBERO env itself)
14
+ # instruction_override : optional string; if set, overrides the env's task_description
15
+ # (used for instruction-mismatch probes). null = use env default.
16
+ # seed : RNG seed forwarded to env.reset()/seed_everything for reproducibility
17
+ # max_episode_steps : max inner-env steps for this rollout (kept small for a smoke test)
18
+ # n_action_steps : action chunk size executed per policy.get_action() call
19
+ # save_video : whether to record an mp4 / frames for human review
20
+ # obs_noise_std : optional float; std-dev of Gaussian noise added to image
21
+ # observations *before* policy inference (0 = disabled)
22
+ # action_repeat : optional int >= 1; if > 1, the previous action chunk is re-used
23
+ # instead of querying the policy on (action_repeat-1)/action_repeat
24
+ # of the steps (1 = disabled, i.e. normal behaviour)
25
+ # notes : free-form description
26
+
27
+ scenarios:
28
+
29
+ # -------------------- 8 normal scenarios --------------------
30
+ - id: normal_kitchen3_moka_pot_on_stove
31
+ label: normal
32
+ env_name: libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it
33
+ instruction: "turn on the stove and put the moka pot on it"
34
+ instruction_override: null
35
+ seed: 1000
36
+ max_episode_steps: 50
37
+ n_action_steps: 8
38
+ save_video: true
39
+ obs_noise_std: 0.0
40
+ action_repeat: 1
41
+ notes: "Baseline libero_10 long-horizon task; short rollout to check the full server<->client loop."
42
+
43
+ - id: normal_kitchen4_bowl_in_drawer
44
+ label: normal
45
+ env_name: libero_sim/KITCHEN_SCENE4_put_the_black_bowl_in_the_bottom_drawer_of_the_cabinet_and_close_it
46
+ instruction: "put the black bowl in the bottom drawer of the cabinet and close it"
47
+ instruction_override: null
48
+ seed: 1001
49
+ max_episode_steps: 50
50
+ n_action_steps: 8
51
+ save_video: true
52
+ obs_noise_std: 0.0
53
+ action_repeat: 1
54
+ notes: "Drawer manipulation task; verifies action decoding + sim stepping."
55
+
56
+ - id: normal_living1_soup_and_cheese_in_basket
57
+ label: normal
58
+ env_name: libero_sim/LIVING_ROOM_SCENE1_put_both_the_alphabet_soup_and_the_cream_cheese_box_in_the_basket
59
+ instruction: "put both the alphabet soup and the cream cheese box in the basket"
60
+ instruction_override: null
61
+ seed: 1002
62
+ max_episode_steps: 50
63
+ n_action_steps: 8
64
+ save_video: true
65
+ obs_noise_std: 0.0
66
+ action_repeat: 1
67
+ notes: "Multi-object pick-and-place; checks video recording wrapper output."
68
+
69
+ - id: normal_living2_soup_and_tomato_in_basket
70
+ label: normal
71
+ env_name: libero_sim/LIVING_ROOM_SCENE2_put_both_the_alphabet_soup_and_the_tomato_sauce_in_the_basket
72
+ instruction: "put both the alphabet soup and the tomato sauce in the basket"
73
+ instruction_override: null
74
+ seed: 1003
75
+ max_episode_steps: 50
76
+ n_action_steps: 8
77
+ save_video: true
78
+ obs_noise_std: 0.0
79
+ action_repeat: 1
80
+ notes: "Second living-room scene; different seed for variety."
81
+
82
+ - id: normal_study1_book_in_caddy
83
+ label: normal
84
+ env_name: libero_sim/STUDY_SCENE1_pick_up_the_book_and_place_it_in_the_back_compartment_of_the_caddy
85
+ instruction: "pick up the book and place it in the back compartment of the caddy"
86
+ instruction_override: null
87
+ seed: 1004
88
+ max_episode_steps: 50
89
+ n_action_steps: 8
90
+ save_video: true
91
+ obs_noise_std: 0.0
92
+ action_repeat: 1
93
+ notes: "Study scene; thin/awkward object grasp."
94
+
95
+ - id: normal_kitchen8_both_moka_pots_on_stove
96
+ label: normal
97
+ env_name: libero_sim/KITCHEN_SCENE8_put_both_moka_pots_on_the_stove
98
+ instruction: "put both moka pots on the stove"
99
+ instruction_override: null
100
+ seed: 1005
101
+ max_episode_steps: 50
102
+ n_action_steps: 8
103
+ save_video: true
104
+ obs_noise_std: 0.0
105
+ action_repeat: 1
106
+ notes: "Two-object task; checks repeated subgoal handling within a short window."
107
+
108
+ - id: normal_kitchen6_mug_in_microwave
109
+ label: normal
110
+ env_name: libero_sim/KITCHEN_SCENE6_put_the_yellow_and_white_mug_in_the_microwave_and_close_it
111
+ instruction: "put the yellow and white mug in the microwave and close it"
112
+ instruction_override: null
113
+ seed: 1006
114
+ max_episode_steps: 50
115
+ n_action_steps: 8
116
+ save_video: true
117
+ obs_noise_std: 0.0
118
+ action_repeat: 1
119
+ notes: "Articulated-object (microwave door) task."
120
+
121
+ - id: normal_living5_two_mugs_on_plates
122
+ label: normal
123
+ env_name: libero_sim/LIVING_ROOM_SCENE5_put_the_white_mug_on_the_left_plate_and_put_the_yellow_and_white_mug_on_the_right_plate
124
+ instruction: "put the white mug on the left plate and put the yellow and white mug on the right plate"
125
+ instruction_override: null
126
+ seed: 1007
127
+ max_episode_steps: 50
128
+ n_action_steps: 8
129
+ save_video: true
130
+ obs_noise_std: 0.0
131
+ action_repeat: 1
132
+ notes: "Long-horizon dual placement; longest of the normal set but still capped short."
133
+
134
+ # -------------------- 2 abnormal_probe scenarios --------------------
135
+ - id: abnormal_probe_obs_noise
136
+ label: abnormal_probe
137
+ env_name: libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it
138
+ instruction: "turn on the stove and put the moka pot on it"
139
+ instruction_override: null
140
+ seed: 2000
141
+ max_episode_steps: 50
142
+ n_action_steps: 8
143
+ save_video: true
144
+ obs_noise_std: 6.0
145
+ action_repeat: 1
146
+ notes: >-
147
+ Simulation-only perturbation: mild zero-mean Gaussian noise (sigma=6 in
148
+ 0-255 pixel units) is added to the image observations *before* they are
149
+ sent to the policy. Smoke test only - this is a placeholder for future,
150
+ more principled distribution-shift probes; no complex anomaly logic.
151
+
152
+ - id: abnormal_probe_short_timeout
153
+ label: abnormal_probe
154
+ env_name: libero_sim/LIVING_ROOM_SCENE2_put_both_the_alphabet_soup_and_the_tomato_sauce_in_the_basket
155
+ instruction: "put both the alphabet soup and the tomato sauce in the basket"
156
+ instruction_override: null
157
+ seed: 2001
158
+ max_episode_steps: 16
159
+ n_action_steps: 8
160
+ save_video: true
161
+ obs_noise_std: 0.0
162
+ action_repeat: 1
163
+ notes: >-
164
+ Simulation-only perturbation: deliberately shortened episode budget
165
+ (16 inner steps ~= 2 action chunks) so the long-horizon task cannot
166
+ possibly complete. Verifies that truncation / "unknown success" is
167
+ reported cleanly rather than crashing.
examples/LIBERO/smoke_tests/visualize_smoke_run.py ADDED
@@ -0,0 +1,739 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License").
5
+ """Build an HTML visual-review report for a completed LIBERO smoke-test run.
6
+
7
+ Reads the artifacts produced by ``run_10_smoke_tests.py`` (summary.json,
8
+ per-scenario metadata.json / rollout_summary.json / actions.npy / video.mp4 /
9
+ frames/), computes a few simple action statistics, renders one card per
10
+ scenario (with a video preview, links to the raw files, and 4 small plots),
11
+ and writes:
12
+
13
+ <run-dir>/visual_report.html
14
+ <run-dir>/visual_summary.csv
15
+ <run-dir>/visual_summary.json
16
+ <run-dir>/plots/<scenario_id>/{action_norm,action_mean_per_dof,gripper_over_time,action_delta_norm}.png
17
+
18
+ It does NOT touch any existing rollout output, does NOT need a GPU, and does
19
+ NOT rerun LIBERO. Missing files become a warning inside the relevant card
20
+ instead of crashing.
21
+
22
+ CLI::
23
+
24
+ python examples/LIBERO/smoke_tests/visualize_smoke_run.py \
25
+ --run-dir outputs/libero_smoke_tests/20260512_122756 --open
26
+
27
+ Flags: --run-dir --open --no-plots --no-video-embed
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import base64
34
+ import csv
35
+ import datetime as _dt
36
+ import html
37
+ import json
38
+ import os
39
+ from pathlib import Path
40
+ import sys
41
+ import tempfile
42
+ import traceback
43
+
44
+ # matplotlib must be configured before import; use a headless backend + a
45
+ # writable config dir (the default ~/.config/matplotlib may be read-only).
46
+ os.environ.setdefault("MPLCONFIGDIR", tempfile.mkdtemp(prefix="mpl-visualize-"))
47
+ import numpy as np
48
+
49
+ _MPL_OK = True
50
+ _MPL_ERR = None
51
+ try:
52
+ import matplotlib
53
+
54
+ matplotlib.use("Agg")
55
+ import matplotlib.pyplot as plt
56
+ except Exception as e: # noqa: BLE001
57
+ _MPL_OK = False
58
+ _MPL_ERR = repr(e)
59
+
60
+ # The LIBERO action dict has 7 keys; `_libero_rollout_worker._action_to_numpy`
61
+ # concatenates them in *sorted (alphabetical)* key order, so the columns of
62
+ # actions.npy for this run are exactly:
63
+ LIBERO_DOF_NAMES = [
64
+ "action.gripper", # col 0
65
+ "action.pitch", # col 1
66
+ "action.roll", # col 2
67
+ "action.x", # col 3
68
+ "action.y", # col 4
69
+ "action.yaw", # col 5
70
+ "action.z", # col 6
71
+ ]
72
+ LIBERO_GRIPPER_COL = 0
73
+
74
+
75
+ # --------------------------------------------------------------------------- #
76
+ # small IO helpers
77
+ # --------------------------------------------------------------------------- #
78
+ def _load_json(path: Path):
79
+ try:
80
+ with open(path) as f:
81
+ return json.load(f), None
82
+ except FileNotFoundError:
83
+ return None, f"missing file: {path.name}"
84
+ except Exception as e: # noqa: BLE001
85
+ return None, f"failed to read {path.name}: {e!r}"
86
+
87
+
88
+ def _rel(path: Path, start: Path) -> str:
89
+ try:
90
+ return os.path.relpath(path, start)
91
+ except ValueError:
92
+ return str(path)
93
+
94
+
95
+ def _fmt(v) -> str:
96
+ if v is None:
97
+ return "—"
98
+ if isinstance(v, float):
99
+ return f"{v:.4g}"
100
+ return str(v)
101
+
102
+
103
+ def _b64_data_uri(path: Path, mime: str) -> str | None:
104
+ try:
105
+ data = path.read_bytes()
106
+ except OSError:
107
+ return None
108
+ return f"data:{mime};base64," + base64.b64encode(data).decode("ascii")
109
+
110
+
111
+ # --------------------------------------------------------------------------- #
112
+ # action statistics
113
+ # --------------------------------------------------------------------------- #
114
+ def _squeeze_actions(arr: np.ndarray) -> np.ndarray:
115
+ """Best-effort normalize an actions.npy array to shape (n_calls, horizon, n_dof).
116
+
117
+ The worker saves shape (n_calls, batch=1, horizon, n_dof). We also tolerate
118
+ (n_calls, horizon, n_dof) and (n_calls, n_dof).
119
+ """
120
+ a = np.asarray(arr)
121
+ if a.dtype == object:
122
+ # list of heterogeneous things saved with allow_pickle; try to stack
123
+ try:
124
+ a = np.stack([np.asarray(x) for x in a], axis=0)
125
+ except Exception:
126
+ raise ValueError(f"cannot interpret object-dtype actions array of shape {arr.shape}")
127
+ if a.ndim == 4 and a.shape[1] == 1:
128
+ a = a[:, 0, :, :]
129
+ if a.ndim == 2: # (n_calls, n_dof) -> add a length-1 horizon
130
+ a = a[:, None, :]
131
+ if a.ndim != 3:
132
+ raise ValueError(f"unexpected actions array shape {arr.shape} (squeezed to {a.shape})")
133
+ return a.astype(np.float64)
134
+
135
+
136
+ def _detect_gripper_col(acts3: np.ndarray) -> tuple[int, list[str]]:
137
+ """Return (gripper_col_index, dof_names)."""
138
+ n_dof = acts3.shape[-1]
139
+ if n_dof == 7:
140
+ return LIBERO_GRIPPER_COL, list(LIBERO_DOF_NAMES)
141
+ names = [f"dof_{i}" for i in range(n_dof)]
142
+ # generic heuristic: the DoF whose values most often saturate near +/-1,
143
+ # falling back to the last column ("7th-style" gripper convention).
144
+ flat = acts3.reshape(-1, n_dof)
145
+ sat = (np.abs(flat) > 0.5).mean(axis=0)
146
+ col = int(np.argmax(sat)) if sat.size and sat.max() > 0.5 else n_dof - 1
147
+ return col, names
148
+
149
+
150
+ def compute_action_stats(acts3: np.ndarray) -> dict:
151
+ """acts3: (n_calls, horizon, n_dof) -> dict of arrays + scalars."""
152
+ n_calls, horizon, n_dof = acts3.shape
153
+ gripper_col, dof_names = _detect_gripper_col(acts3)
154
+
155
+ # per-policy-call norms
156
+ step0_norm = np.linalg.norm(acts3[:, 0, :], axis=-1) # (n_calls,) norm of the first executed step
157
+ chunk_mean = acts3.mean(axis=1) # (n_calls, n_dof) mean over the chunk
158
+ chunk_mean_norm = np.linalg.norm(chunk_mean, axis=-1) # (n_calls,)
159
+ per_step_norm = np.linalg.norm(acts3.reshape(-1, n_dof), axis=-1) # (n_calls*horizon,)
160
+
161
+ # mean (and std) action value per DoF over all calls x steps
162
+ flat = acts3.reshape(-1, n_dof)
163
+ mean_per_dof = flat.mean(axis=0)
164
+ std_per_dof = flat.std(axis=0)
165
+
166
+ # gripper command over time: value at chunk-step 0 per call
167
+ gripper_step0 = acts3[:, 0, gripper_col] # (n_calls,)
168
+ gripper_all = flat[:, gripper_col]
169
+
170
+ # action-delta norm between consecutive *chunks* (Frobenius over horizon x dof)
171
+ if n_calls >= 2:
172
+ delta = acts3[1:] - acts3[:-1] # (n_calls-1, horizon, n_dof)
173
+ delta_norm = np.linalg.norm(delta.reshape(n_calls - 1, -1), axis=-1)
174
+ else:
175
+ delta_norm = np.zeros((0,), dtype=np.float64)
176
+
177
+ return {
178
+ "n_calls": n_calls,
179
+ "horizon": horizon,
180
+ "n_dof": n_dof,
181
+ "dof_names": dof_names,
182
+ "gripper_col": gripper_col,
183
+ "step0_norm": step0_norm,
184
+ "chunk_mean_norm": chunk_mean_norm,
185
+ "per_step_norm": per_step_norm,
186
+ "mean_per_dof": mean_per_dof,
187
+ "std_per_dof": std_per_dof,
188
+ "gripper_step0": gripper_step0,
189
+ "gripper_all": gripper_all,
190
+ "delta_norm": delta_norm,
191
+ # scalars for the comparison table
192
+ "mean_action_norm": float(per_step_norm.mean()) if per_step_norm.size else None,
193
+ "max_action_norm": float(per_step_norm.max()) if per_step_norm.size else None,
194
+ "mean_delta_norm": float(delta_norm.mean()) if delta_norm.size else 0.0,
195
+ "gripper_min": float(gripper_all.min()) if gripper_all.size else None,
196
+ "gripper_max": float(gripper_all.max()) if gripper_all.size else None,
197
+ }
198
+
199
+
200
+ # --------------------------------------------------------------------------- #
201
+ # plotting
202
+ # --------------------------------------------------------------------------- #
203
+ def _save_fig(fig, path: Path) -> None:
204
+ path.parent.mkdir(parents=True, exist_ok=True)
205
+ fig.tight_layout()
206
+ fig.savefig(path, dpi=110)
207
+ plt.close(fig)
208
+
209
+
210
+ def make_plots(stats: dict, out_dir: Path, scenario_id: str) -> dict:
211
+ """Write the 4 PNGs; return {logical_name: Path}. Requires matplotlib."""
212
+ out_dir.mkdir(parents=True, exist_ok=True)
213
+ calls = np.arange(stats["n_calls"])
214
+ paths: dict[str, Path] = {}
215
+
216
+ # 1) action norm over policy calls
217
+ fig, ax = plt.subplots(figsize=(5.6, 3.2))
218
+ ax.plot(calls, stats["step0_norm"], "o-", label="‖action‖ at chunk step 0")
219
+ ax.plot(calls, stats["chunk_mean_norm"], "s--", label="‖mean action over 16-step chunk‖")
220
+ ax.set_xlabel("policy call index")
221
+ ax.set_ylabel("L2 norm")
222
+ ax.set_title(f"{scenario_id}\naction L2 norm per policy call")
223
+ ax.grid(alpha=0.3)
224
+ ax.legend(fontsize=8)
225
+ p = out_dir / "action_norm.png"
226
+ _save_fig(fig, p)
227
+ paths["action_norm"] = p
228
+
229
+ # 2) mean action value per DoF (bar + std error bars)
230
+ fig, ax = plt.subplots(figsize=(5.6, 3.2))
231
+ x = np.arange(stats["n_dof"])
232
+ ax.bar(x, stats["mean_per_dof"], yerr=stats["std_per_dof"], capsize=3, color="#4C78A8")
233
+ ax.axhline(0.0, color="k", lw=0.6)
234
+ ax.set_xticks(x)
235
+ ax.set_xticklabels(stats["dof_names"], rotation=35, ha="right", fontsize=8)
236
+ ax.set_ylabel("mean value (±std) over all calls×steps")
237
+ gname = stats["dof_names"][stats["gripper_col"]]
238
+ ax.set_title(f"{scenario_id}\nmean action per DoF (gripper DoF = {gname}, col {stats['gripper_col']})")
239
+ ax.grid(alpha=0.3, axis="y")
240
+ p = out_dir / "action_mean_per_dof.png"
241
+ _save_fig(fig, p)
242
+ paths["action_mean_per_dof"] = p
243
+
244
+ # 3) gripper command over time
245
+ fig, ax = plt.subplots(figsize=(5.6, 3.2))
246
+ ax.step(calls, stats["gripper_step0"], where="post", marker="o", color="#E45756")
247
+ ax.set_ylim(min(-1.1, float(stats["gripper_step0"].min()) - 0.1) if stats["gripper_step0"].size else -1.1,
248
+ max(1.1, float(stats["gripper_step0"].max()) + 0.1) if stats["gripper_step0"].size else 1.1)
249
+ ax.set_xlabel("policy call index")
250
+ ax.set_ylabel(f"{gname} (chunk step 0)")
251
+ ax.set_title(f"{scenario_id}\ngripper command over time (DoF '{gname}', col {stats['gripper_col']})")
252
+ ax.grid(alpha=0.3)
253
+ p = out_dir / "gripper_over_time.png"
254
+ _save_fig(fig, p)
255
+ paths["gripper_over_time"] = p
256
+
257
+ # 4) action delta norm between consecutive chunks
258
+ fig, ax = plt.subplots(figsize=(5.6, 3.2))
259
+ if stats["delta_norm"].size:
260
+ ax.plot(np.arange(1, stats["n_calls"]), stats["delta_norm"], "o-", color="#54A24B")
261
+ else:
262
+ ax.text(0.5, 0.5, "only one policy call\n(no consecutive delta)", ha="center", va="center",
263
+ transform=ax.transAxes, fontsize=10)
264
+ ax.set_xlabel("policy call index i (delta between chunk i and i-1)")
265
+ ax.set_ylabel("‖Δ chunk‖_F (Frobenius over 16×n_dof)")
266
+ ax.set_title(f"{scenario_id}\naction delta norm between consecutive chunks")
267
+ ax.grid(alpha=0.3)
268
+ p = out_dir / "action_delta_norm.png"
269
+ _save_fig(fig, p)
270
+ paths["action_delta_norm"] = p
271
+
272
+ return paths
273
+
274
+
275
+ # --------------------------------------------------------------------------- #
276
+ # per-scenario processing
277
+ # --------------------------------------------------------------------------- #
278
+ def process_scenario(scenario_id: str, run_dir: Path, summary_row: dict | None,
279
+ make_plots_flag: bool, embed_video: bool) -> dict:
280
+ """Return a dict with everything needed to render the card + table row."""
281
+ sc_dir = run_dir / scenario_id
282
+ warnings: list[str] = []
283
+ rec: dict = {"scenario_id": scenario_id, "scenario_dir": sc_dir, "warnings": warnings}
284
+
285
+ if not sc_dir.is_dir():
286
+ warnings.append(f"scenario directory not found: {sc_dir}")
287
+ rec.update(label=summary_row.get("label") if summary_row else None, seed=None,
288
+ instruction=None, success=summary_row.get("success") if summary_row else None,
289
+ max_episode_steps=None, num_policy_calls=None, actions_shape=None,
290
+ error=(summary_row or {}).get("error_if_any"), stats=None, plot_paths={},
291
+ video_path=None, frames_dir=None, n_frames=0,
292
+ files={}, video_data_uri=None)
293
+ return rec
294
+
295
+ meta, err = _load_json(sc_dir / "metadata.json")
296
+ if err:
297
+ warnings.append(err)
298
+ meta = {}
299
+ roll, err = _load_json(sc_dir / "rollout_summary.json")
300
+ if err:
301
+ warnings.append(err)
302
+ roll = {}
303
+
304
+ manifest_entry = (meta or {}).get("manifest_entry", {}) or {}
305
+ resolved = (meta or {}).get("resolved", {}) or {}
306
+ label = manifest_entry.get("label") or (meta or {}).get("label") or (summary_row or {}).get("label")
307
+ seed = manifest_entry.get("seed", resolved.get("seed", (summary_row or {}).get("seed")))
308
+ instruction = manifest_entry.get("instruction") or roll.get("instruction")
309
+ env_name = resolved.get("env_name") or manifest_entry.get("env_name") or roll.get("env_name")
310
+ max_episode_steps = (resolved.get("max_episode_steps")
311
+ or manifest_entry.get("max_episode_steps")
312
+ or (roll or {}).get("requested_max_episode_steps"))
313
+ success = roll.get("success", (summary_row or {}).get("success"))
314
+ error = (roll or {}).get("error") or (summary_row or {}).get("error_if_any")
315
+ num_policy_calls = (roll or {}).get("n_get_action_calls") or (summary_row or {}).get("n_action_calls")
316
+ obs_noise_std = resolved.get("obs_noise_std", manifest_entry.get("obs_noise_std"))
317
+ action_repeat = resolved.get("action_repeat", manifest_entry.get("action_repeat"))
318
+ notes = manifest_entry.get("notes")
319
+
320
+ # actions.npy
321
+ actions_shape = None
322
+ stats = None
323
+ plot_paths: dict[str, Path] = {}
324
+ actions_path = sc_dir / "actions.npy"
325
+ if actions_path.exists():
326
+ try:
327
+ arr = np.load(actions_path, allow_pickle=True)
328
+ actions_shape = tuple(int(x) for x in np.asarray(arr).shape) if np.asarray(arr).dtype != object else f"object[{len(arr)}]"
329
+ acts3 = _squeeze_actions(arr)
330
+ stats = compute_action_stats(acts3)
331
+ if num_policy_calls is None:
332
+ num_policy_calls = stats["n_calls"]
333
+ if make_plots_flag and _MPL_OK:
334
+ try:
335
+ plot_paths = make_plots(stats, run_dir / "plots" / scenario_id, scenario_id)
336
+ except Exception as e: # noqa: BLE001
337
+ warnings.append(f"plot generation failed: {e!r}")
338
+ elif make_plots_flag and not _MPL_OK:
339
+ warnings.append(f"matplotlib unavailable, plots skipped: {_MPL_ERR}")
340
+ except Exception as e: # noqa: BLE001
341
+ warnings.append(f"failed to load/parse actions.npy: {e!r}")
342
+ else:
343
+ warnings.append("missing file: actions.npy")
344
+
345
+ # video
346
+ video_path = sc_dir / "video.mp4"
347
+ video_present = video_path.exists()
348
+ if not video_present:
349
+ warnings.append("missing file: video.mp4")
350
+ video_data_uri = None
351
+ if video_present and embed_video:
352
+ video_data_uri = _b64_data_uri(video_path, "video/mp4")
353
+ if video_data_uri is None:
354
+ warnings.append("failed to base64-embed video.mp4")
355
+
356
+ # frames/
357
+ frames_dir = sc_dir / "frames"
358
+ n_frames = 0
359
+ first_frame_uri = None
360
+ if frames_dir.is_dir():
361
+ frame_files = sorted(frames_dir.glob("*.png"))
362
+ n_frames = len(frame_files)
363
+ if not video_present and frame_files:
364
+ first_frame_uri = _b64_data_uri(frame_files[0], "image/png")
365
+ # (frames/ is optional; only warn if neither video nor frames exist)
366
+ if not video_present and n_frames == 0:
367
+ warnings.append("no video.mp4 and no frames/ — nothing to preview")
368
+
369
+ # raw-file links (present-or-not)
370
+ files = {}
371
+ for name in ("metadata.json", "rollout_summary.json", "stdout.log", "stderr.log"):
372
+ p = sc_dir / name
373
+ files[name] = {"rel": _rel(p, run_dir), "exists": p.exists()}
374
+ if not p.exists():
375
+ warnings.append(f"missing file: {name}")
376
+
377
+ rec.update(
378
+ label=label, seed=seed, instruction=instruction, env_name=env_name,
379
+ success=success, max_episode_steps=max_episode_steps, num_policy_calls=num_policy_calls,
380
+ actions_shape=actions_shape, error=error, obs_noise_std=obs_noise_std,
381
+ action_repeat=action_repeat, notes=notes, stats=stats, plot_paths=plot_paths,
382
+ video_path=video_path if video_present else None, video_present=video_present,
383
+ video_data_uri=video_data_uri, frames_dir=frames_dir if frames_dir.is_dir() else None,
384
+ n_frames=n_frames, first_frame_uri=first_frame_uri, files=files,
385
+ output_dir=str((summary_row or {}).get("output_dir") or _rel(sc_dir, run_dir.parent.parent)),
386
+ elapsed_sec=(roll or {}).get("elapsed_sec", (summary_row or {}).get("elapsed_sec")),
387
+ video_saved=video_present,
388
+ )
389
+ return rec
390
+
391
+
392
+ # --------------------------------------------------------------------------- #
393
+ # HTML rendering
394
+ # --------------------------------------------------------------------------- #
395
+ _CSS = """
396
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
397
+ margin:0;padding:24px;background:#f4f5f7;color:#1a1a1a;}
398
+ h1{margin:0 0 4px 0;} .sub{color:#666;margin-bottom:20px;font-size:14px;}
399
+ .meta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:6px 18px;
400
+ background:#fff;border:1px solid #e0e0e0;border-radius:8px;padding:14px 18px;margin-bottom:22px;font-size:13px;}
401
+ .meta-grid b{color:#444;}
402
+ table.cmp{border-collapse:collapse;width:100%;background:#fff;font-size:12.5px;margin-bottom:28px;
403
+ box-shadow:0 1px 3px rgba(0,0,0,.08);border-radius:8px;overflow:hidden;}
404
+ table.cmp th,table.cmp td{border-bottom:1px solid #eee;padding:7px 10px;text-align:left;white-space:nowrap;}
405
+ table.cmp th{background:#2d3748;color:#fff;position:sticky;top:0;}
406
+ table.cmp tr:hover{background:#f7f9fc;}
407
+ .badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:11px;font-weight:600;}
408
+ .b-normal{background:#e3f0ff;color:#1a5fb4;} .b-abn{background:#ffe9d6;color:#b35a00;}
409
+ .b-ok{background:#e6f6ea;color:#1a7f37;} .b-fail{background:#fde8e8;color:#b42318;} .b-unk{background:#eee;color:#555;}
410
+ .card{background:#fff;border:1px solid #e0e0e0;border-radius:10px;padding:18px;margin-bottom:22px;
411
+ box-shadow:0 1px 3px rgba(0,0,0,.06);}
412
+ .card h2{margin:0 0 2px 0;font-size:18px;} .card .cmeta{color:#555;font-size:13px;margin-bottom:10px;}
413
+ .card .cols{display:grid;grid-template-columns:minmax(280px,360px) 1fr;gap:18px;}
414
+ .kv{font-size:13px;line-height:1.7;} .kv b{color:#444;display:inline-block;min-width:130px;}
415
+ .plots{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:10px;}
416
+ .plots img{width:100%;border:1px solid #eee;border-radius:6px;background:#fff;}
417
+ video{width:100%;max-width:340px;border-radius:6px;background:#000;}
418
+ .warn{background:#fff8e1;border:1px solid #ffe082;border-radius:6px;padding:8px 12px;margin:8px 0;font-size:12.5px;color:#7a5c00;}
419
+ .err{background:#fdecea;border:1px solid #f5c6c0;border-radius:6px;padding:8px 12px;margin:8px 0;font-size:12.5px;color:#7a1f17;font-family:monospace;white-space:pre-wrap;}
420
+ .files a{margin-right:12px;font-size:12.5px;} .files .missing{color:#aaa;text-decoration:line-through;}
421
+ .notes{font-size:12.5px;color:#555;font-style:italic;margin-top:8px;}
422
+ code{background:#f0f0f0;padding:1px 4px;border-radius:3px;font-size:12px;}
423
+ """
424
+
425
+
426
+ def _badge_label(label: str | None) -> str:
427
+ if label == "normal":
428
+ return '<span class="badge b-normal">normal</span>'
429
+ if label == "abnormal_probe":
430
+ return '<span class="badge b-abn">abnormal_probe</span>'
431
+ return f'<span class="badge b-unk">{html.escape(str(label))}</span>'
432
+
433
+
434
+ def _badge_success(s) -> str:
435
+ if s is True:
436
+ return '<span class="badge b-ok">success: true</span>'
437
+ if s is False:
438
+ return '<span class="badge b-fail">success: false</span>'
439
+ return '<span class="badge b-unk">success: unknown</span>'
440
+
441
+
442
+ def render_html(run_dir: Path, summary: dict | None, records: list[dict], table_rows: list[dict],
443
+ args) -> str:
444
+ parts: list[str] = []
445
+ parts.append("<!DOCTYPE html><html><head><meta charset='utf-8'>")
446
+ parts.append(f"<title>LIBERO smoke-test visual report — {html.escape(run_dir.name)}</title>")
447
+ parts.append(f"<style>{_CSS}</style></head><body>")
448
+ parts.append("<h1>LIBERO smoke-test visual review</h1>")
449
+ parts.append(f"<div class='sub'>run dir: <code>{html.escape(str(run_dir))}</code> &nbsp;|&nbsp; "
450
+ f"generated {html.escape(_dt.datetime.now().isoformat(timespec='seconds'))} &nbsp;|&nbsp; "
451
+ f"scenarios: {len(records)}</div>")
452
+
453
+ # run-level metadata
454
+ if summary:
455
+ t = summary.get("totals", {})
456
+ parts.append("<div class='meta-grid'>")
457
+ for k, v in [
458
+ ("model_path", summary.get("model_path")),
459
+ ("policy_server", f"{summary.get('policy_server',{}).get('host')}:{summary.get('policy_server',{}).get('port')}"),
460
+ ("manifest", summary.get("manifest")),
461
+ ("server_command", summary.get("server_command")),
462
+ ("n_scenarios", summary.get("n_scenarios")),
463
+ ("n_normal / n_abnormal_probe", f"{summary.get('n_normal')} / {summary.get('n_abnormal_probe')}"),
464
+ ("rollout_started / actions_produced / video_saved / errors",
465
+ f"{t.get('rollout_started')} / {t.get('actions_produced')} / {t.get('video_saved')} / {t.get('errors')}"),
466
+ ("run timestamp", summary.get("timestamp")),
467
+ ("dry_run", summary.get("dry_run")),
468
+ ]:
469
+ parts.append(f"<div><b>{html.escape(str(k))}:</b> {html.escape(_fmt(v))}</div>")
470
+ parts.append("</div>")
471
+ if not _MPL_OK:
472
+ parts.append(f"<div class='warn'>matplotlib not available ({html.escape(str(_MPL_ERR))}) — plots were skipped.</div>")
473
+ if args.no_plots:
474
+ parts.append("<div class='warn'>--no-plots was set — no PNG plots were generated this run.</div>")
475
+
476
+ # comparison table
477
+ parts.append("<h2>Comparison across all scenarios</h2>")
478
+ cols = ["scenario_id", "label", "seed", "success", "num_policy_calls", "video_saved",
479
+ "mean_action_norm", "max_action_norm", "mean_delta_norm", "gripper_min", "gripper_max", "output_dir"]
480
+ parts.append("<table class='cmp'><thead><tr>" + "".join(f"<th>{html.escape(c)}</th>" for c in cols) + "</tr></thead><tbody>")
481
+ for row in table_rows:
482
+ parts.append("<tr>")
483
+ for c in cols:
484
+ v = row.get(c)
485
+ if c == "label":
486
+ cell = _badge_label(v)
487
+ elif c == "success":
488
+ cell = _badge_success(v)
489
+ elif c == "scenario_id":
490
+ cell = f"<a href='#{html.escape(str(v))}'>{html.escape(str(v))}</a>"
491
+ elif c in ("mean_action_norm", "max_action_norm", "mean_delta_norm", "gripper_min", "gripper_max"):
492
+ cell = "—" if v is None else f"{float(v):.4g}"
493
+ else:
494
+ cell = html.escape(_fmt(v))
495
+ parts.append(f"<td>{cell}</td>")
496
+ parts.append("</tr>")
497
+ parts.append("</tbody></table>")
498
+
499
+ # one card per scenario
500
+ for rec in records:
501
+ sid = rec["scenario_id"]
502
+ parts.append(f"<div class='card' id='{html.escape(sid)}'>")
503
+ parts.append(f"<h2>{html.escape(sid)}</h2>")
504
+ parts.append("<div class='cmeta'>" + _badge_label(rec.get("label")) + " " + _badge_success(rec.get("success"))
505
+ + f" &nbsp; seed={html.escape(_fmt(rec.get('seed')))}"
506
+ + (f" &nbsp; env=<code>{html.escape(str(rec.get('env_name')))}</code>" if rec.get("env_name") else "")
507
+ + "</div>")
508
+ if rec.get("error"):
509
+ parts.append(f"<div class='err'>error: {html.escape(str(rec['error']))}</div>")
510
+ for w in rec.get("warnings", []):
511
+ parts.append(f"<div class='warn'>⚠ {html.escape(str(w))}</div>")
512
+
513
+ parts.append("<div class='cols'>")
514
+
515
+ # left column: key/values + video + file links
516
+ parts.append("<div>")
517
+ st = rec.get("stats") or {}
518
+ kv = [
519
+ ("scenario_id", sid),
520
+ ("label", rec.get("label")),
521
+ ("seed", rec.get("seed")),
522
+ ("instruction", rec.get("instruction")),
523
+ ("success", rec.get("success")),
524
+ ("max_episode_steps", rec.get("max_episode_steps")),
525
+ ("num policy calls", rec.get("num_policy_calls")),
526
+ ("actions.npy shape", rec.get("actions_shape")),
527
+ ("action horizon × DoF", f"{st.get('horizon')} × {st.get('n_dof')}" if st else "—"),
528
+ ("gripper DoF (col)", (f"{st['dof_names'][st['gripper_col']]} (col {st['gripper_col']})" if st else "—")),
529
+ ("mean / max ‖action‖", f"{_fmt(st.get('mean_action_norm'))} / {_fmt(st.get('max_action_norm'))}" if st else "—"),
530
+ ("mean ‖Δ chunk‖", _fmt(st.get("mean_delta_norm")) if st else "—"),
531
+ ("gripper min / max", f"{_fmt(st.get('gripper_min'))} / {_fmt(st.get('gripper_max'))}" if st else "—"),
532
+ ("obs_noise_std", rec.get("obs_noise_std")),
533
+ ("action_repeat", rec.get("action_repeat")),
534
+ ("elapsed_sec", rec.get("elapsed_sec")),
535
+ ("frames", (f"{rec.get('n_frames')} PNGs" if rec.get("n_frames") else "none")),
536
+ ]
537
+ parts.append("<div class='kv'>")
538
+ for k, v in kv:
539
+ parts.append(f"<div><b>{html.escape(str(k))}:</b> {html.escape(_fmt(v))}</div>")
540
+ parts.append("</div>")
541
+
542
+ # video preview
543
+ parts.append("<div style='margin-top:10px'>")
544
+ if rec.get("video_data_uri"):
545
+ parts.append(f"<video controls preload='metadata' src='{rec['video_data_uri']}'></video>")
546
+ elif rec.get("video_present"):
547
+ vrel = _rel(rec["video_path"], run_dir)
548
+ parts.append(f"<video controls preload='metadata' src='{html.escape(vrel)}'></video>"
549
+ f"<div style='font-size:12px'><a href='{html.escape(vrel)}'>{html.escape(vrel)}</a></div>")
550
+ elif rec.get("first_frame_uri"):
551
+ parts.append(f"<img src='{rec['first_frame_uri']}' style='max-width:340px;border-radius:6px'/>"
552
+ f"<div class='warn'>no video.mp4 — showing first frame only</div>")
553
+ else:
554
+ parts.append("<div class='warn'>no video / frames available to preview</div>")
555
+ parts.append("</div>")
556
+
557
+ # file links
558
+ parts.append("<div class='files' style='margin-top:10px'>")
559
+ for name, info in (rec.get("files") or {}).items():
560
+ if info["exists"]:
561
+ parts.append(f"<a href='{html.escape(info['rel'])}'>{html.escape(name)}</a>")
562
+ else:
563
+ parts.append(f"<span class='missing'>{html.escape(name)}</span>")
564
+ if rec.get("frames_dir"):
565
+ parts.append(f"<a href='{html.escape(_rel(rec['frames_dir'], run_dir))}/'>frames/ ({rec.get('n_frames')})</a>")
566
+ parts.append("</div>")
567
+ if rec.get("notes"):
568
+ parts.append(f"<div class='notes'>note: {html.escape(str(rec['notes']))}</div>")
569
+ parts.append("</div>") # end left column
570
+
571
+ # right column: plots
572
+ parts.append("<div>")
573
+ if rec.get("plot_paths"):
574
+ parts.append("<div class='plots'>")
575
+ for logical in ("action_norm", "action_mean_per_dof", "gripper_over_time", "action_delta_norm"):
576
+ p = rec["plot_paths"].get(logical)
577
+ if p and Path(p).exists():
578
+ parts.append(f"<a href='{html.escape(_rel(Path(p), run_dir))}'>"
579
+ f"<img src='{html.escape(_rel(Path(p), run_dir))}' alt='{logical}'/></a>")
580
+ else:
581
+ parts.append(f"<div class='warn'>plot '{logical}.png' not available</div>")
582
+ parts.append("</div>")
583
+ elif rec.get("stats") is None:
584
+ parts.append("<div class='warn'>no actions.npy — no action statistics / plots</div>")
585
+ else:
586
+ parts.append("<div class='warn'>plots not generated (use without --no-plots, and ensure matplotlib is installed)</div>")
587
+ parts.append("</div>") # end right column
588
+
589
+ parts.append("</div>") # end cols
590
+ parts.append("</div>") # end card
591
+
592
+ parts.append("</body></html>")
593
+ return "\n".join(parts)
594
+
595
+
596
+ # --------------------------------------------------------------------------- #
597
+ # main
598
+ # --------------------------------------------------------------------------- #
599
+ def _discover_scenarios(run_dir: Path, summary: dict | None) -> tuple[list[str], dict[str, dict]]:
600
+ rows_by_id: dict[str, dict] = {}
601
+ order: list[str] = []
602
+ if summary and isinstance(summary.get("scenarios"), list):
603
+ for r in summary["scenarios"]:
604
+ sid = r.get("scenario_id")
605
+ if sid:
606
+ rows_by_id[sid] = r
607
+ order.append(sid)
608
+ # add any scenario subdirs not already listed
609
+ skip = {"plots"}
610
+ for p in sorted(run_dir.iterdir()):
611
+ if p.is_dir() and p.name not in skip and p.name not in order:
612
+ order.append(p.name)
613
+ return order, rows_by_id
614
+
615
+
616
+ def write_table_outputs(run_dir: Path, table_rows: list[dict]) -> tuple[Path, Path]:
617
+ cols = ["scenario_id", "label", "seed", "success", "num_policy_calls", "video_saved",
618
+ "mean_action_norm", "max_action_norm", "mean_delta_norm", "gripper_min", "gripper_max", "output_dir"]
619
+ csv_path = run_dir / "visual_summary.csv"
620
+ with open(csv_path, "w", newline="") as f:
621
+ w = csv.DictWriter(f, fieldnames=cols)
622
+ w.writeheader()
623
+ for row in table_rows:
624
+ w.writerow({c: ("" if row.get(c) is None else row.get(c)) for c in cols})
625
+ json_path = run_dir / "visual_summary.json"
626
+ with open(json_path, "w") as f:
627
+ json.dump({"run_dir": str(run_dir), "n_scenarios": len(table_rows),
628
+ "generated": _dt.datetime.now().isoformat(timespec="seconds"),
629
+ "columns": cols, "rows": table_rows}, f, indent=2)
630
+ return csv_path, json_path
631
+
632
+
633
+ def main() -> int:
634
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
635
+ ap.add_argument("--run-dir", required=True, help="Path to the completed run dir (contains summary.json + scenario subdirs).")
636
+ ap.add_argument("--open", action="store_true", help="Open the generated HTML report in a browser.")
637
+ ap.add_argument("--no-plots", action="store_true", help="Do not (re)generate the PNG plots.")
638
+ ap.add_argument("--no-video-embed", action="store_true", help="Link videos by relative path instead of base64-embedding them in the HTML.")
639
+ args = ap.parse_args()
640
+
641
+ run_dir = Path(args.run_dir).resolve()
642
+ if not run_dir.is_dir():
643
+ print(f"ERROR: --run-dir does not exist or is not a directory: {run_dir}", file=sys.stderr)
644
+ return 2
645
+
646
+ summary, serr = _load_json(run_dir / "summary.json")
647
+ if serr:
648
+ print(f"WARNING: {serr} — proceeding by scanning scenario subdirectories.", file=sys.stderr)
649
+ # summary.md is read only to confirm it exists / surface it; we don't parse it.
650
+ summary_md_present = (run_dir / "summary.md").exists()
651
+ if not summary_md_present:
652
+ print("WARNING: summary.md not found (continuing).", file=sys.stderr)
653
+
654
+ order, rows_by_id = _discover_scenarios(run_dir, summary)
655
+ if not order:
656
+ print(f"ERROR: no scenarios found under {run_dir}", file=sys.stderr)
657
+ return 2
658
+ print(f"Found {len(order)} scenario(s) in {run_dir}")
659
+
660
+ make_plots_flag = not args.no_plots
661
+ embed_video = not args.no_video_embed
662
+
663
+ records: list[dict] = []
664
+ table_rows: list[dict] = []
665
+ all_warnings: list[str] = []
666
+ n_videos_embedded = 0
667
+ n_videos_present = 0
668
+ for sid in order:
669
+ try:
670
+ rec = process_scenario(sid, run_dir, rows_by_id.get(sid), make_plots_flag, embed_video)
671
+ except Exception as e: # noqa: BLE001
672
+ tb = traceback.format_exc()
673
+ print(f"WARNING: scenario {sid} raised {e!r}; recording as failed card.", file=sys.stderr)
674
+ rec = {"scenario_id": sid, "warnings": [f"internal error: {e!r}", tb], "stats": None,
675
+ "plot_paths": {}, "files": {}, "label": None, "seed": None, "success": None,
676
+ "video_present": False, "video_data_uri": None, "n_frames": 0}
677
+ records.append(rec)
678
+ all_warnings += [f"[{sid}] {w}" for w in rec.get("warnings", [])]
679
+ if rec.get("video_present"):
680
+ n_videos_present += 1
681
+ if rec.get("video_data_uri"):
682
+ n_videos_embedded += 1
683
+ st = rec.get("stats") or {}
684
+ table_rows.append({
685
+ "scenario_id": sid,
686
+ "label": rec.get("label"),
687
+ "seed": rec.get("seed"),
688
+ "success": rec.get("success"),
689
+ "num_policy_calls": rec.get("num_policy_calls"),
690
+ "video_saved": bool(rec.get("video_present")),
691
+ "mean_action_norm": st.get("mean_action_norm"),
692
+ "max_action_norm": st.get("max_action_norm"),
693
+ "mean_delta_norm": st.get("mean_delta_norm"),
694
+ "gripper_min": st.get("gripper_min"),
695
+ "gripper_max": st.get("gripper_max"),
696
+ "output_dir": rec.get("output_dir") or str(run_dir / sid),
697
+ })
698
+
699
+ csv_path, json_path = write_table_outputs(run_dir, table_rows)
700
+ html_str = render_html(run_dir, summary, records, table_rows, args)
701
+ html_path = run_dir / "visual_report.html"
702
+ html_path.write_text(html_str, encoding="utf-8")
703
+
704
+ # console summary
705
+ print(f"\nWrote: {html_path}")
706
+ print(f"Wrote: {csv_path}")
707
+ print(f"Wrote: {json_path}")
708
+ if make_plots_flag and _MPL_OK:
709
+ print(f"Plots: {run_dir / 'plots'}/<scenario_id>/*.png")
710
+ print(f"Videos: {n_videos_present}/{len(order)} present; "
711
+ f"{'all ' if n_videos_embedded == len(order) and embed_video else ''}"
712
+ f"{n_videos_embedded}/{len(order)} embedded in HTML"
713
+ + ("" if embed_video else " (--no-video-embed: linked by path instead)"))
714
+ if all_warnings:
715
+ print(f"\n{len(all_warnings)} warning(s):")
716
+ for w in all_warnings:
717
+ print(f" - {w}")
718
+ else:
719
+ print("\nNo missing files or warnings.")
720
+
721
+ if args.open:
722
+ import webbrowser
723
+
724
+ url = html_path.as_uri()
725
+ opened = False
726
+ try:
727
+ opened = webbrowser.open(url)
728
+ except Exception:
729
+ opened = False
730
+ if opened:
731
+ print(f"\nOpened {url} in a browser.")
732
+ else:
733
+ print(f"\nCould not auto-open a browser. Open this file manually:\n {html_path}")
734
+
735
+ return 0
736
+
737
+
738
+ if __name__ == "__main__":
739
+ raise SystemExit(main())
outputs/_setup_logs/_dryrun.log ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Loaded 10 scenarios from /home/ubuntu/VLA_SAE/examples/LIBERO/smoke_tests/scenarios_10.yaml
2
+ labels: 8 normal, 2 abnormal_probe
3
+
4
+ Expected GR00T server command (run this in a separate terminal):
5
+ uv run python gr00t/eval/run_gr00t_server.py --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 --embodiment-tag LIBERO_PANDA --use-sim-policy-wrapper
6
+
7
+ NOTE: The GR00T-N1.7 backbone 'nvidia/Cosmos-Reason2-2B' is a GATED HuggingFace repo.
8
+ To start the server you must:
9
+ 1. Request access at https://huggingface.co/nvidia/Cosmos-Reason2-2B (one click, usually instant).
10
+ 2. Authenticate, e.g. export HF_TOKEN=hf_xxx (or: uv run hf auth login)
11
+ 3. Re-run the server / smoke tests.
12
+
13
+ Model checkpoint OK: checkpoints/GR00T-N1.7-LIBERO/libero_10
14
+ Run directory: outputs/libero_smoke_tests/20260512_120217
15
+ (dry-run) skipping server reachability requirement.
16
+ LIBERO rollout interpreter: /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/python
17
+
18
+ [1/10] normal_kitchen3_moka_pot_on_stove (normal) env=libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it seed=1000 max_episode_steps=50
19
+
20
+ [2/10] normal_kitchen4_bowl_in_drawer (normal) env=libero_sim/KITCHEN_SCENE4_put_the_black_bowl_in_the_bottom_drawer_of_the_cabinet_and_close_it seed=1001 max_episode_steps=50
21
+
22
+ [3/10] normal_living1_soup_and_cheese_in_basket (normal) env=libero_sim/LIVING_ROOM_SCENE1_put_both_the_alphabet_soup_and_the_cream_cheese_box_in_the_basket seed=1002 max_episode_steps=50
23
+
24
+ [4/10] normal_living2_soup_and_tomato_in_basket (normal) env=libero_sim/LIVING_ROOM_SCENE2_put_both_the_alphabet_soup_and_the_tomato_sauce_in_the_basket seed=1003 max_episode_steps=50
25
+
26
+ [5/10] normal_study1_book_in_caddy (normal) env=libero_sim/STUDY_SCENE1_pick_up_the_book_and_place_it_in_the_back_compartment_of_the_caddy seed=1004 max_episode_steps=50
27
+
28
+ [6/10] normal_kitchen8_both_moka_pots_on_stove (normal) env=libero_sim/KITCHEN_SCENE8_put_both_moka_pots_on_the_stove seed=1005 max_episode_steps=50
29
+
30
+ [7/10] normal_kitchen6_mug_in_microwave (normal) env=libero_sim/KITCHEN_SCENE6_put_the_yellow_and_white_mug_in_the_microwave_and_close_it seed=1006 max_episode_steps=50
31
+
32
+ [8/10] normal_living5_two_mugs_on_plates (normal) env=libero_sim/LIVING_ROOM_SCENE5_put_the_white_mug_on_the_left_plate_and_put_the_yellow_and_white_mug_on_the_right_plate seed=1007 max_episode_steps=50
33
+
34
+ [9/10] abnormal_probe_obs_noise (abnormal_probe) env=libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it seed=2000 max_episode_steps=50
35
+
36
+ [10/10] abnormal_probe_short_timeout (abnormal_probe) env=libero_sim/LIVING_ROOM_SCENE2_put_both_the_alphabet_soup_and_the_tomato_sauce_in_the_basket seed=2001 max_episode_steps=16
37
+
38
+ ======== SMOKE-TEST SUMMARY ========
39
+ scenario_id | label | seed | rollout_started | actions_produced | video_saved | success | output_dir | error_if_any
40
+ -----------------------------------------+----------------+------+-----------------+------------------+-------------+---------+-------------------------------------------------------------------------------------+------------------------------
41
+ normal_kitchen3_moka_pot_on_stove | normal | 1000 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_kitchen3_moka_pot_on_stove | dry-run (no rollout executed)
42
+ normal_kitchen4_bowl_in_drawer | normal | 1001 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_kitchen4_bowl_in_drawer | dry-run (no rollout executed)
43
+ normal_living1_soup_and_cheese_in_basket | normal | 1002 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_living1_soup_and_cheese_in_basket | dry-run (no rollout executed)
44
+ normal_living2_soup_and_tomato_in_basket | normal | 1003 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_living2_soup_and_tomato_in_basket | dry-run (no rollout executed)
45
+ normal_study1_book_in_caddy | normal | 1004 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_study1_book_in_caddy | dry-run (no rollout executed)
46
+ normal_kitchen8_both_moka_pots_on_stove | normal | 1005 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_kitchen8_both_moka_pots_on_stove | dry-run (no rollout executed)
47
+ normal_kitchen6_mug_in_microwave | normal | 1006 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_kitchen6_mug_in_microwave | dry-run (no rollout executed)
48
+ normal_living5_two_mugs_on_plates | normal | 1007 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/normal_living5_two_mugs_on_plates | dry-run (no rollout executed)
49
+ abnormal_probe_obs_noise | abnormal_probe | 2000 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/abnormal_probe_obs_noise | dry-run (no rollout executed)
50
+ abnormal_probe_short_timeout | abnormal_probe | 2001 | no | no | no | unknown | outputs/libero_smoke_tests/20260512_120217/abnormal_probe_short_timeout | dry-run (no rollout executed)
51
+
52
+ Wrote: outputs/libero_smoke_tests/20260512_120217/summary.json
53
+ Wrote: outputs/libero_smoke_tests/20260512_120217/summary.md
54
+
55
+ Review the videos with:
56
+ python /home/ubuntu/VLA_SAE/examples/LIBERO/smoke_tests/review_smoke_tests.py --run-dir outputs/libero_smoke_tests/20260512_120217
57
+
outputs/_setup_logs/_hf_download.log ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /home/ubuntu/.local/lib/python3.10/site-packages/huggingface_hub/cli/download.py:146: UserWarning: Ignoring `--include` since filenames have being explicitly set.
2
+ warnings.warn("Ignoring `--include` since filenames have being explicitly set.")
3
+ Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
4
+ path=/home/ubuntu/VLA_SAE/checkpoints/GR00T-N1.7-LIBERO
outputs/_setup_logs/_libero_setup.log ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ +++ dirname gr00t/eval/sim/LIBERO/setup_libero.sh
2
+ ++ cd gr00t/eval/sim/LIBERO
3
+ ++ pwd
4
+ + SCRIPT_DIR=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO
5
+ + LIBERO_REPO=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../../external_dependencies/LIBERO
6
+ + PROJECT_REPO=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../..
7
+ + LIBERO_UV_ENV=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv
8
+ + git submodule update --init /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../../external_dependencies/LIBERO
9
+ + rm -rf /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv
10
+ + mkdir -p /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv
11
+ + uv venv /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv --python 3.10
12
+ Using CPython 3.10.12 interpreter at: /usr/bin/python3.10
13
+ Creating virtual environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
14
+ Activate with: source gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/activate
15
+ + source /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/activate
16
+ ++ '[' -z '' ']'
17
+ ++ '[' -n x ']'
18
+ ++ SCRIPT_PATH=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/activate
19
+ ++ '[' /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/activate = gr00t/eval/sim/LIBERO/setup_libero.sh ']'
20
+ ++ deactivate nondestructive
21
+ ++ unset -f pydoc
22
+ ++ '[' -z '' ']'
23
+ ++ '[' -z '' ']'
24
+ ++ hash -r
25
+ ++ '[' -z '' ']'
26
+ ++ unset VIRTUAL_ENV
27
+ ++ unset VIRTUAL_ENV_PROMPT
28
+ ++ '[' '!' nondestructive = nondestructive ']'
29
+ ++ VIRTUAL_ENV=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv
30
+ ++ '[' linux-gnu = cygwin ']'
31
+ ++ '[' linux-gnu = msys ']'
32
+ ++ export VIRTUAL_ENV
33
+ ++ '[' -z '' ']'
34
+ ++ unset SCRIPT_PATH
35
+ ++ _OLD_VIRTUAL_PATH=/home/ubuntu/.local/bin:/home/ubuntu/.vscode-server/cli/servers/Stable-034f571df509819cc10b0c8129f66ef77a542f0e/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
36
+ ++ PATH=/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin:/home/ubuntu/.local/bin:/home/ubuntu/.vscode-server/cli/servers/Stable-034f571df509819cc10b0c8129f66ef77a542f0e/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
37
+ ++ export PATH
38
+ ++ '[' x '!=' x ']'
39
+ +++ basename /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv
40
+ ++ VIRTUAL_ENV_PROMPT=.venv
41
+ ++ export VIRTUAL_ENV_PROMPT
42
+ ++ '[' -z '' ']'
43
+ ++ '[' -z '' ']'
44
+ ++ _OLD_VIRTUAL_PS1=
45
+ ++ PS1='(.venv) '
46
+ ++ export PS1
47
+ ++ alias pydoc
48
+ ++ true
49
+ ++ hash -r
50
+ + uv pip install --requirements /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../../external_dependencies/LIBERO/requirements.txt
51
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
52
+ Resolved 120 packages in 34ms
53
+ Building gym==0.25.2
54
+ Building bddl==1.0.1
55
+ Building promise==2.3
56
+ Building future==0.18.2
57
+ Building egl-probe==1.0.2
58
+ Building robomimic==0.2.0
59
+ Building pathtools==0.1.2
60
+ Building easydict==1.9
61
+ Downloading wandb (1.7MiB)
62
+ Downloading pyopengl (3.0MiB)
63
+ Downloading transformers (4.4MiB)
64
+ Downloading nvidia-cuda-runtime (2.1MiB)
65
+ Downloading cuda-bindings (6.0MiB)
66
+ Downloading tokenizers (6.3MiB)
67
+ Downloading nvidia-cuda-nvrtc (86.0MiB)
68
+ Downloading nvidia-cuda-cupti (10.2MiB)
69
+ Downloading h5py (4.8MiB)
70
+ Downloading imageio-ffmpeg (28.1MiB)
71
+ Downloading triton (179.4MiB)
72
+ Downloading nvidia-cusparse (139.2MiB)
73
+ Downloading numba (3.6MiB)
74
+ Downloading torch (506.0MiB)
75
+ Downloading nvidia-cublas (403.5MiB)
76
+ Downloading numpy (16.0MiB)
77
+ Downloading torchvision (7.2MiB)
78
+ Downloading llvmlite (53.7MiB)
79
+ Downloading nvidia-cufft (204.2MiB)
80
+ Downloading scipy (36.8MiB)
81
+ Downloading setuptools (1.0MiB)
82
+ Downloading nvidia-cusolver (191.6MiB)
83
+ Downloading opencv-python (58.1MiB)
84
+ Downloading mujoco (6.9MiB)
85
+ Downloading tensorboard-data-server (6.3MiB)
86
+ Downloading nvidia-cudnn-cu13 (349.1MiB)
87
+ Downloading nvidia-curand (56.8MiB)
88
+ Downloading nvidia-cusparselt-cu13 (162.0MiB)
89
+ Downloading nvidia-nvjitlink (38.8MiB)
90
+ Downloading nvidia-nccl-cu13 (187.4MiB)
91
+ Downloading robosuite (184.5MiB)
92
+ Downloading matplotlib (11.3MiB)
93
+ Downloading grpcio (6.5MiB)
94
+ Downloading tensorboard (5.3MiB)
95
+ Downloading nvidia-nvshmem-cu13 (57.6MiB)
96
+ Built easydict==1.9
97
+ Built pathtools==0.1.2
98
+ Built promise==2.3
99
+ Built robomimic==0.2.0
100
+ Built bddl==1.0.1
101
+ Built gym==0.25.2
102
+ Built future==0.18.2
103
+ Downloaded nvidia-cuda-runtime
104
+ Downloaded setuptools
105
+ Downloaded wandb
106
+ Built egl-probe==1.0.2
107
+ Downloaded h5py
108
+ Downloaded numba
109
+ Downloaded transformers
110
+ Downloaded tensorboard
111
+ Downloaded cuda-bindings
112
+ Downloaded tensorboard-data-server
113
+ Downloaded tokenizers
114
+ Downloaded grpcio
115
+ Downloaded mujoco
116
+ Downloaded torchvision
117
+ Downloaded nvidia-cuda-cupti
118
+ Downloaded matplotlib
119
+ Downloaded numpy
120
+ Downloaded imageio-ffmpeg
121
+ Downloaded nvidia-nvjitlink
122
+ Downloaded scipy
123
+ Downloaded pyopengl
124
+ Downloaded llvmlite
125
+ Downloaded nvidia-curand
126
+ Downloaded nvidia-nvshmem-cu13
127
+ Downloaded opencv-python
128
+ Downloaded nvidia-cuda-nvrtc
129
+ Downloaded nvidia-cusparse
130
+ Downloaded nvidia-cusparselt-cu13
131
+ Downloaded triton
132
+ Downloaded nvidia-nccl-cu13
133
+ Downloaded nvidia-cusolver
134
+ Downloaded nvidia-cufft
135
+ Downloaded nvidia-cudnn-cu13
136
+ Downloaded nvidia-cublas
137
+ Downloaded robosuite
138
+ Downloaded torch
139
+ Prepared 43 packages in 17.97s
140
+ Installed 120 packages in 192ms
141
+ + absl-py==2.4.0
142
+ + antlr4-python3-runtime==4.9.3
143
+ + attrs==26.1.0
144
+ + bddl==1.0.1
145
+ + certifi==2026.4.22
146
+ + charset-normalizer==3.4.7
147
+ + click==8.3.3
148
+ + cloudpickle==2.1.0
149
+ + cuda-bindings==13.2.0
150
+ + cuda-pathfinder==1.5.4
151
+ + cuda-toolkit==13.0.2
152
+ + cycler==0.12.1
153
+ + docker-pycreds==0.4.0
154
+ + easydict==1.9
155
+ + egl-probe==1.0.2
156
+ + einops==0.4.1
157
+ + etils==1.13.0
158
+ + exceptiongroup==1.3.1
159
+ + fastjsonschema==2.21.2
160
+ + filelock==3.29.0
161
+ + fonttools==4.62.1
162
+ + fsspec==2026.4.0
163
+ + future==0.18.2
164
+ + gitdb==4.0.12
165
+ + gitpython==3.1.50
166
+ + glfw==2.10.0
167
+ + grpcio==1.80.0
168
+ + gym==0.25.2
169
+ + gym-notices==0.1.0
170
+ + h5py==3.16.0
171
+ + hf-xet==1.5.0
172
+ + huggingface-hub==0.36.2
173
+ + hydra-core==1.2.0
174
+ + idna==3.14
175
+ + imageio==2.37.3
176
+ + imageio-ffmpeg==0.6.0
177
+ + importlib-resources==7.1.0
178
+ + iniconfig==2.3.0
179
+ + jinja2==3.1.6
180
+ + jsonschema==4.26.0
181
+ + jsonschema-specifications==2025.9.1
182
+ + jupyter-core==5.9.1
183
+ + jupytext==1.19.2
184
+ + kiwisolver==1.5.0
185
+ + llvmlite==0.47.0
186
+ + markdown==3.10.2
187
+ + markdown-it-py==4.2.0
188
+ + markupsafe==3.0.3
189
+ + matplotlib==3.5.3
190
+ + mdit-py-plugins==0.6.0
191
+ + mdurl==0.1.2
192
+ + mpmath==1.3.0
193
+ + mujoco==3.8.1
194
+ + nbformat==5.10.4
195
+ + networkx==3.4.2
196
+ + numba==0.65.1
197
+ + numpy==1.22.4
198
+ + nvidia-cublas==13.1.0.3
199
+ + nvidia-cuda-cupti==13.0.85
200
+ + nvidia-cuda-nvrtc==13.0.88
201
+ + nvidia-cuda-runtime==13.0.96
202
+ + nvidia-cudnn-cu13==9.19.0.56
203
+ + nvidia-cufft==12.0.0.61
204
+ + nvidia-cufile==1.15.1.6
205
+ + nvidia-curand==10.4.0.35
206
+ + nvidia-cusolver==12.0.4.66
207
+ + nvidia-cusparse==12.6.3.3
208
+ + nvidia-cusparselt-cu13==0.8.0
209
+ + nvidia-nccl-cu13==2.28.9
210
+ + nvidia-nvjitlink==13.0.88
211
+ + nvidia-nvshmem-cu13==3.4.5
212
+ + nvidia-nvtx==13.0.85
213
+ + omegaconf==2.3.0
214
+ + opencv-python==4.6.0.66
215
+ + packaging==26.2
216
+ + pathtools==0.1.2
217
+ + pillow==12.2.0
218
+ + platformdirs==4.9.6
219
+ + pluggy==1.6.0
220
+ + promise==2.3
221
+ + protobuf==3.20.3
222
+ + psutil==7.2.2
223
+ + pygments==2.20.0
224
+ + pyopengl==3.1.10
225
+ + pyparsing==3.3.2
226
+ + pytest==9.0.3
227
+ + python-dateutil==2.9.0.post0
228
+ + pyyaml==6.0.3
229
+ + referencing==0.37.0
230
+ + regex==2026.5.9
231
+ + requests==2.34.0
232
+ + robomimic==0.2.0
233
+ + robosuite==1.4.0
234
+ + rpds-py==0.30.0
235
+ + scipy==1.13.1
236
+ + sentry-sdk==2.59.0
237
+ + setproctitle==1.3.7
238
+ + setuptools==81.0.0
239
+ + shortuuid==1.0.13
240
+ + six==1.17.0
241
+ + smmap==5.0.3
242
+ + sympy==1.14.0
243
+ + tensorboard==2.20.0
244
+ + tensorboard-data-server==0.7.2
245
+ + tensorboardx==2.6.5
246
+ + termcolor==3.3.0
247
+ + thop==0.1.1.post2209072238
248
+ + tokenizers==0.12.1
249
+ + tomli==2.4.1
250
+ + torch==2.11.0
251
+ + torchvision==0.26.0
252
+ + tqdm==4.67.3
253
+ + traitlets==5.15.0
254
+ + transformers==4.21.1
255
+ + triton==3.6.0
256
+ + typing-extensions==4.15.0
257
+ + urllib3==2.7.0
258
+ + wandb==0.13.1
259
+ + werkzeug==3.1.8
260
+ + zipp==3.23.1
261
+ + uv pip install -e /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../../external_dependencies/LIBERO --config-settings editable_mode=compat
262
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
263
+ Resolved 1 package in 440ms
264
+ Building libero @ file:///home/ubuntu/VLA_SAE/external_dependencies/LIBERO
265
+ Built libero @ file:///home/ubuntu/VLA_SAE/external_dependencies/LIBERO
266
+ Prepared 1 package in 211ms
267
+ Installed 1 package in 1ms
268
+ + libero==0.1.0 (from file:///home/ubuntu/VLA_SAE/external_dependencies/LIBERO)
269
+ + uv pip install --editable /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../.. --no-deps
270
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
271
+ Resolved 1 package in 1ms
272
+ Building gr00t @ file:///home/ubuntu/VLA_SAE
273
+ Built gr00t @ file:///home/ubuntu/VLA_SAE
274
+ Prepared 1 package in 569ms
275
+ Installed 1 package in 0.62ms
276
+ + gr00t==0.1.0 (from file:///home/ubuntu/VLA_SAE)
277
+ + uv pip install torch==2.5.1 torchvision==0.20.1 pydantic av tianshou==0.5.1 tyro pandas dm_tree einops==0.8.1 albumentations==1.4.18 zmq
278
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
279
+ Resolved 72 packages in 543ms
280
+ Building zmq==0.0.0
281
+ Downloading pandas (12.2MiB)
282
+ Downloading nvidia-cuda-cupti-cu12 (13.2MiB)
283
+ Downloading opencv-python-headless (57.6MiB)
284
+ Downloading sympy (5.9MiB)
285
+ Downloading av (35.9MiB)
286
+ Downloading numpy (16.0MiB)
287
+ Downloading torchvision (6.9MiB)
288
+ Downloading nvidia-cufft-cu12 (201.7MiB)
289
+ Downloading nvidia-cublas-cu12 (346.6MiB)
290
+ Downloading nvidia-cusparse-cu12 (197.8MiB)
291
+ Downloading nvidia-cuda-nvrtc-cu12 (23.5MiB)
292
+ Downloading nvidia-cusolver-cu12 (122.0MiB)
293
+ Downloading nvidia-nvjitlink-cu12 (20.1MiB)
294
+ Downloading nvidia-nccl-cu12 (179.9MiB)
295
+ Downloading nvidia-curand-cu12 (53.7MiB)
296
+ Downloading nvidia-cudnn-cu12 (634.0MiB)
297
+ Downloading torch (864.5MiB)
298
+ Downloading triton (199.8MiB)
299
+ Built zmq==0.0.0
300
+ Downloaded torchvision
301
+ Downloaded nvidia-cuda-cupti-cu12
302
+ Downloaded numpy
303
+ Downloaded nvidia-nvjitlink-cu12
304
+ Downloaded nvidia-cuda-nvrtc-cu12
305
+ Downloaded sympy
306
+ Downloaded av
307
+ Downloaded nvidia-curand-cu12
308
+ Downloaded opencv-python-headless
309
+ Downloaded nvidia-cusolver-cu12
310
+ Downloaded pandas
311
+ Downloaded nvidia-nccl-cu12
312
+ Downloaded triton
313
+ Downloaded nvidia-cufft-cu12
314
+ Downloaded nvidia-cusparse-cu12
315
+ Downloaded nvidia-cublas-cu12
316
+ Downloaded nvidia-cudnn-cu12
317
+ Downloaded torch
318
+ Prepared 26 packages in 25.57s
319
+ Uninstalled 6 packages in 141ms
320
+ Installed 44 packages in 266ms
321
+ + albucore==0.0.17
322
+ + albumentations==1.4.18
323
+ + annotated-types==0.7.0
324
+ + av==17.0.1
325
+ + dm-tree==0.1.10
326
+ + docstring-parser==0.18.0
327
+ - einops==0.4.1
328
+ + einops==0.8.1
329
+ + eval-type-backport==0.3.1
330
+ + farama-notifications==0.0.6
331
+ + gymnasium==1.3.0
332
+ + lazy-loader==0.5
333
+ - numpy==1.22.4
334
+ + numpy==2.2.6
335
+ + nvidia-cublas-cu12==12.4.5.8
336
+ + nvidia-cuda-cupti-cu12==12.4.127
337
+ + nvidia-cuda-nvrtc-cu12==12.4.127
338
+ + nvidia-cuda-runtime-cu12==12.4.127
339
+ + nvidia-cudnn-cu12==9.1.0.70
340
+ + nvidia-cufft-cu12==11.2.1.3
341
+ + nvidia-curand-cu12==10.3.5.147
342
+ + nvidia-cusolver-cu12==11.6.1.9
343
+ + nvidia-cusparse-cu12==12.3.1.170
344
+ + nvidia-nccl-cu12==2.21.5
345
+ + nvidia-nvjitlink-cu12==12.4.127
346
+ + nvidia-nvtx-cu12==12.4.127
347
+ + opencv-python-headless==4.13.0.92
348
+ + pandas==2.3.3
349
+ + pettingzoo==1.26.1
350
+ + pydantic==2.13.4
351
+ + pydantic-core==2.46.4
352
+ + pytz==2026.2
353
+ + pyzmq==27.1.0
354
+ + scikit-image==0.25.2
355
+ - sympy==1.14.0
356
+ + sympy==1.13.1
357
+ + tianshou==0.5.1
358
+ + tifffile==2025.5.10
359
+ - torch==2.11.0
360
+ + torch==2.5.1
361
+ - torchvision==0.26.0
362
+ + torchvision==0.20.1
363
+ - triton==3.6.0
364
+ + triton==3.1.0
365
+ + typeguard==4.5.1
366
+ + typing-inspection==0.4.2
367
+ + tyro==1.0.13
368
+ + tzdata==2026.2
369
+ + wrapt==2.1.2
370
+ + zmq==0.0.0
371
+ + uv pip install transformers==4.57.3 msgpack==1.1.0 msgpack-numpy==0.4.8 gymnasium==0.29.1
372
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
373
+ Resolved 23 packages in 109ms
374
+ Prepared 1 package in 95ms
375
+ Uninstalled 3 packages in 19ms
376
+ Installed 6 packages in 40ms
377
+ - gymnasium==1.3.0
378
+ + gymnasium==0.29.1
379
+ + msgpack==1.1.0
380
+ + msgpack-numpy==0.4.8
381
+ + safetensors==0.7.0
382
+ - tokenizers==0.12.1
383
+ + tokenizers==0.22.2
384
+ - transformers==4.21.1
385
+ + transformers==4.57.3
386
+ + uv pip install numpy==1.26.4
387
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
388
+ Resolved 1 package in 6ms
389
+ Uninstalled 1 package in 11ms
390
+ Installed 1 package in 16ms
391
+ - numpy==2.2.6
392
+ + numpy==1.26.4
393
+ + uv pip install --editable /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/../../../.. --no-deps
394
+ Using Python 3.10.12 environment at: gr00t/eval/sim/LIBERO/libero_uv/.venv
395
+ Resolved 1 package in 1ms
396
+ Building gr00t @ file:///home/ubuntu/VLA_SAE
397
+ Built gr00t @ file:///home/ubuntu/VLA_SAE
398
+ Prepared 1 package in 682ms
399
+ Uninstalled 1 package in 0.33ms
400
+ Installed 1 package in 0.55ms
401
+ ~ gr00t==0.1.0 (from file:///home/ubuntu/VLA_SAE)
402
+ + rm -rf /home/ubuntu/.libero
403
+ + printf 'n\n'
404
+ + python -c 'from gr00t.eval.sim.LIBERO.libero_env import register_libero_envs'
405
+ Do you want to specify a custom path for the dataset folder? (Y/N): [robosuite WARNING] No private macro file found! (__init__.py:7)
406
+ [robosuite WARNING] It is recommended to use a private macro file (__init__.py:8)
407
+ [robosuite WARNING] To setup, run: python /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/robosuite/scripts/setup_macros.py (__init__.py:9)
408
+ Matplotlib created a temporary config/cache directory at /tmp/matplotlib-igojt7pq because the default path (/home/ubuntu/.config/matplotlib) is not a writable directory; it is highly recommended to set the MPLCONFIGDIR environment variable to a writable directory, in particular to speed up the import of Matplotlib and to better support multiprocessing.
409
+ Gym has been unmaintained since 2022 and does not support NumPy 2.0 amongst other critical functionality.
410
+ Please upgrade to Gymnasium, the maintained drop-in replacement of Gym, or contact the authors of your software and request that they upgrade.
411
+ See the migration guide at https://gymnasium.farama.org/introduction/migration_guide/ for additional information.
412
+ Initializing the default config file...
413
+ The following information is stored in the config file: /home/ubuntu/.libero/config.yaml
414
+ benchmark_root: /home/ubuntu/VLA_SAE/external_dependencies/LIBERO/libero/libero
415
+ bddl_files: /home/ubuntu/VLA_SAE/external_dependencies/LIBERO/libero/libero/./bddl_files
416
+ init_states: /home/ubuntu/VLA_SAE/external_dependencies/LIBERO/libero/libero/./init_files
417
+ datasets: /home/ubuntu/VLA_SAE/external_dependencies/LIBERO/libero/libero/../datasets
418
+ assets: /home/ubuntu/VLA_SAE/external_dependencies/LIBERO/libero/libero/./assets
419
+ + python -
420
+ [robosuite WARNING] No private macro file found! (__init__.py:7)
421
+ [robosuite WARNING] It is recommended to use a private macro file (__init__.py:8)
422
+ [robosuite WARNING] To setup, run: python /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/robosuite/scripts/setup_macros.py (__init__.py:9)
423
+ Matplotlib created a temporary config/cache directory at /tmp/matplotlib-fwatww__ because the default path (/home/ubuntu/.config/matplotlib) is not a writable directory; it is highly recommended to set the MPLCONFIGDIR environment variable to a writable directory, in particular to speed up the import of Matplotlib and to better support multiprocessing.
424
+ Gym has been unmaintained since 2022 and does not support NumPy 2.0 amongst other critical functionality.
425
+ Please upgrade to Gymnasium, the maintained drop-in replacement of Gym, or contact the authors of your software and request that they upgrade.
426
+ See the migration guide at https://gymnasium.farama.org/introduction/migration_guide/ for additional information.
427
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:159: UserWarning: WARN: The obs returned by the `reset()` method is not within the observation space.
428
+ logger.warn(f"{pre} is not within the observation space.")
429
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:135: UserWarning: WARN: The obs returned by the `reset()` method was expecting numpy array dtype to be float32, actual type: float64
430
+ logger.warn(
431
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:131: UserWarning: WARN: The obs returned by the `reset()` method was expecting a numpy array, actual type: <class 'list'>
432
+ logger.warn(
433
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/spaces/box.py:240: UserWarning: WARN: Casting input x to numpy array.
434
+ gym.logger.warn("Casting input x to numpy array.")
435
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
436
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
437
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
438
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
439
+ Env OK: <class 'gymnasium.wrappers.order_enforcing.OrderEnforcing'>
outputs/_setup_logs/_server.log ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/albumentations/__init__.py:13: UserWarning: A new version of Albumentations is available: 2.0.8 (you have 1.4.18). Upgrade using: pip install -U albumentations. To disable automatic update checks, set the environment variable NO_ALBUMENTATIONS_UPDATE to 1.
2
+ check_for_updates()
3
+ flash_attn is not installed. Falling back to sdpa attention. Install flash-attn for better performance: pip install flash-attn
4
+ Starting GR00T inference server...
5
+ Embodiment tag: EmbodimentTag.LIBERO_PANDA
6
+ Model path: checkpoints/GR00T-N1.7-LIBERO/libero_10
7
+ Device: cuda
8
+ Host: 127.0.0.1
9
+ Port: 5555
10
+ Traceback (most recent call last):
11
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_http.py", line 403, in hf_raise_for_status
12
+ response.raise_for_status()
13
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/requests/models.py", line 1167, in raise_for_status
14
+ raise HTTPError(http_error_msg, response=self)
15
+ requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://huggingface.co/nvidia/Cosmos-Reason2-2B/resolve/main/config.json
16
+
17
+ The above exception was the direct cause of the following exception:
18
+
19
+ Traceback (most recent call last):
20
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/utils/hub.py", line 479, in cached_files
21
+ hf_hub_download(
22
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py", line 114, in _inner_fn
23
+ return fn(*args, **kwargs)
24
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1014, in hf_hub_download
25
+ return _hf_hub_download_to_cache_dir(
26
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1121, in _hf_hub_download_to_cache_dir
27
+ _raise_on_head_call_error(head_call_error, force_download, local_files_only)
28
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1662, in _raise_on_head_call_error
29
+ raise head_call_error
30
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1550, in _get_metadata_or_catch_error
31
+ metadata = get_hf_file_metadata(
32
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_validators.py", line 114, in _inner_fn
33
+ return fn(*args, **kwargs)
34
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 1467, in get_hf_file_metadata
35
+ r = _request_wrapper(
36
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 283, in _request_wrapper
37
+ response = _request_wrapper(
38
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/file_download.py", line 307, in _request_wrapper
39
+ hf_raise_for_status(response)
40
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/huggingface_hub/utils/_http.py", line 420, in hf_raise_for_status
41
+ raise _format(GatedRepoError, message, response) from e
42
+ huggingface_hub.errors.GatedRepoError: 401 Client Error. (Request ID: Root=1-6a0314fd-4cda431e47f56d0801840f67;ad3e8e30-c9ce-490a-ba30-5986386d4d7c)
43
+
44
+ Cannot access gated repo for url https://huggingface.co/nvidia/Cosmos-Reason2-2B/resolve/main/config.json.
45
+ Access to model nvidia/Cosmos-Reason2-2B is restricted. You must have access to it and be authenticated to access it. Please log in.
46
+
47
+ The above exception was the direct cause of the following exception:
48
+
49
+ Traceback (most recent call last):
50
+ File "/home/ubuntu/VLA_SAE/gr00t/eval/run_gr00t_server.py", line 167, in <module>
51
+ main(config)
52
+ File "/home/ubuntu/VLA_SAE/gr00t/eval/run_gr00t_server.py", line 86, in main
53
+ policy = Gr00tPolicy(
54
+ File "/home/ubuntu/VLA_SAE/gr00t/policy/gr00t_policy.py", line 100, in __init__
55
+ model = AutoModel.from_pretrained(model_dir)
56
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/models/auto/auto_factory.py", line 604, in from_pretrained
57
+ return model_class.from_pretrained(
58
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/modeling_utils.py", line 277, in _wrapper
59
+ return func(*args, **kwargs)
60
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/modeling_utils.py", line 4971, in from_pretrained
61
+ model = cls(config, *model_args, **model_kwargs)
62
+ File "/home/ubuntu/VLA_SAE/gr00t/model/gr00t_n1d7/gr00t_n1d7.py", line 516, in __init__
63
+ self.backbone = backbone_cls(
64
+ File "/home/ubuntu/VLA_SAE/gr00t/model/modules/qwen3_backbone.py", line 80, in __init__
65
+ self.model = Qwen3VLForConditionalGeneration.from_pretrained(
66
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/modeling_utils.py", line 277, in _wrapper
67
+ return func(*args, **kwargs)
68
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/modeling_utils.py", line 4843, in from_pretrained
69
+ config, model_kwargs = cls.config_class.from_pretrained(
70
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/configuration_utils.py", line 622, in from_pretrained
71
+ config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
72
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/configuration_utils.py", line 662, in get_config_dict
73
+ config_dict, kwargs = cls._get_config_dict(pretrained_model_name_or_path, **kwargs)
74
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/configuration_utils.py", line 721, in _get_config_dict
75
+ resolved_config_file = cached_file(
76
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/utils/hub.py", line 322, in cached_file
77
+ file = cached_files(path_or_repo_id=path_or_repo_id, filenames=[filename], **kwargs)
78
+ File "/home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/transformers/utils/hub.py", line 543, in cached_files
79
+ raise OSError(
80
+ OSError: You are trying to access a gated repo.
81
+ Make sure to have access to it at https://huggingface.co/nvidia/Cosmos-Reason2-2B.
82
+ 401 Client Error. (Request ID: Root=1-6a0314fd-4cda431e47f56d0801840f67;ad3e8e30-c9ce-490a-ba30-5986386d4d7c)
83
+
84
+ Cannot access gated repo for url https://huggingface.co/nvidia/Cosmos-Reason2-2B/resolve/main/config.json.
85
+ Access to model nvidia/Cosmos-Reason2-2B is restricted. You must have access to it and be authenticated to access it. Please log in.
outputs/_setup_logs/_uv_sync.log ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Using CPython 3.10.12 interpreter at: /usr/bin/python3
2
+ Creating virtual environment at: .venv
3
+ error: Failed to generate package metadata for `flash-attn==2.7.4.post1 @ path+scripts/deployment/dgpu/wheels/flash_attn-2.7.4.post1-cp310-cp310-linux_aarch64.whl`
4
+ Caused by: Failed to extract archive: flash_attn-2.7.4.post1-cp310-cp310-linux_aarch64.whl
5
+ Caused by: Invalid zip file structure
6
+ Caused by: Encountered an unexpected header (actual: 0x73726576, expected: 0x4034b50).
outputs/_setup_logs/_venv_install.log ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ + uv pip install --python .venv/bin/python -e . --no-deps
2
+ Resolved 1 package in 1ms
3
+ Building gr00t @ file:///home/ubuntu/VLA_SAE
4
+ Built gr00t @ file:///home/ubuntu/VLA_SAE
5
+ Prepared 1 package in 848ms
6
+ Installed 1 package in 0.80ms
7
+ + gr00t==0.1.0 (from file:///home/ubuntu/VLA_SAE)
8
+ + uv pip install --python .venv/bin/python torch==2.7.1 torchvision==0.22.1 transformers==4.57.3 numpy==1.26.4 albumentations==1.4.18 av==16.1.0 diffusers==0.35.1 dm-tree lmdb==1.7.5 msgpack==1.1.0 msgpack-numpy==0.4.8 pandas==2.2.3 peft==0.17.1 termcolor==3.2.0 tyro==0.9.17 click==8.1.8 datasets==3.6.0 cryptography einops==0.8.1 gitpython==3.1.46 jsonlines==4.0.0 gymnasium==1.2.2 matplotlib==3.10.1 omegaconf==2.3.0 scipy==1.15.3 torchcodec==0.4.0 wandb==0.23.0 pyzmq==27.0.1 'huggingface-hub[cli]' 'opencv-python-headless>=4.5,<4.13' safetensors accelerate sentencepiece protobuf pyyaml tqdm
9
+ Resolved 127 packages in 1.16s
10
+ Downloading aiohttp (1.6MiB)
11
+ Downloading nvidia-cufile-cu12 (1.1MiB)
12
+ Downloading sympy (6.0MiB)
13
+ Downloading hf-xet (4.3MiB)
14
+ Downloading nvidia-curand-cu12 (53.7MiB)
15
+ Downloading scipy (35.9MiB)
16
+ Downloading pyarrow (46.6MiB)
17
+ Downloading scikit-image (14.1MiB)
18
+ Downloading cryptography (4.5MiB)
19
+ Downloading nvidia-nvjitlink-cu12 (18.8MiB)
20
+ Downloading numpy (17.4MiB)
21
+ Downloading diffusers (3.9MiB)
22
+ Downloading tokenizers (3.1MiB)
23
+ Downloading kiwisolver (1.6MiB)
24
+ Downloading opencv-python-headless (47.7MiB)
25
+ Downloading transformers (11.4MiB)
26
+ Downloading nvidia-cusolver-cu12 (150.9MiB)
27
+ Downloading nvidia-cusparse-cu12 (206.5MiB)
28
+ Downloading pandas (12.5MiB)
29
+ Downloading sentencepiece (1.3MiB)
30
+ Downloading nvidia-cuda-cupti-cu12 (8.5MiB)
31
+ Downloading pillow (6.7MiB)
32
+ Downloading nvidia-cufft-cu12 (190.9MiB)
33
+ Downloading networkx (1.6MiB)
34
+ Downloading nvidia-cuda-nvrtc-cu12 (22.6MiB)
35
+ Downloading pydantic-core (2.0MiB)
36
+ Downloading fonttools (4.7MiB)
37
+ Downloading av (38.4MiB)
38
+ Downloading nvidia-cudnn-cu12 (544.5MiB)
39
+ Downloading nvidia-nccl-cu12 (192.0MiB)
40
+ Downloading nvidia-cublas-cu12 (374.9MiB)
41
+ Downloading nvidia-cusparselt-cu12 (149.5MiB)
42
+ Downloading pygments (1.2MiB)
43
+ Downloading torchcodec (1.3MiB)
44
+ Downloading wandb (19.3MiB)
45
+ Downloading matplotlib (8.2MiB)
46
+ Downloading torchvision (7.1MiB)
47
+ Downloading triton (148.4MiB)
48
+ Downloading torch (783.1MiB)
49
+ Downloaded nvidia-cufile-cu12
50
+ Downloaded sentencepiece
51
+ Downloaded kiwisolver
52
+ Downloaded aiohttp
53
+ Downloaded torchcodec
54
+ Downloaded pygments
55
+ Downloaded pydantic-core
56
+ Building antlr4-python3-runtime==4.9.3
57
+ Built antlr4-python3-runtime==4.9.3
58
+ Downloaded networkx
59
+ Downloaded tokenizers
60
+ Downloaded diffusers
61
+ Downloaded hf-xet
62
+ Downloaded fonttools
63
+ Downloaded cryptography
64
+ Downloaded pillow
65
+ Downloaded torchvision
66
+ Downloaded nvidia-cuda-cupti-cu12
67
+ Downloaded matplotlib
68
+ Downloaded sympy
69
+ Downloaded pandas
70
+ Downloaded scikit-image
71
+ Downloaded numpy
72
+ Downloaded nvidia-nvjitlink-cu12
73
+ Downloaded transformers
74
+ Downloaded wandb
75
+ Downloaded nvidia-cuda-nvrtc-cu12
76
+ Downloaded scipy
77
+ Downloaded av
78
+ Downloaded opencv-python-headless
79
+ Downloaded nvidia-curand-cu12
80
+ Downloaded pyarrow
81
+ Downloaded nvidia-cusolver-cu12
82
+ Downloaded nvidia-cusparselt-cu12
83
+ Downloaded nvidia-cufft-cu12
84
+ Downloaded nvidia-cusparse-cu12
85
+ Downloaded nvidia-nccl-cu12
86
+ Downloaded triton
87
+ Downloaded nvidia-cublas-cu12
88
+ Downloaded nvidia-cudnn-cu12
89
+ Downloaded torch
90
+ Prepared 125 packages in 43.34s
91
+ Installed 127 packages in 197ms
92
+ + absl-py==2.4.0
93
+ + accelerate==1.13.0
94
+ + aiohappyeyeballs==2.6.1
95
+ + aiohttp==3.13.5
96
+ + aiosignal==1.4.0
97
+ + albucore==0.0.17
98
+ + albumentations==1.4.18
99
+ + annotated-types==0.7.0
100
+ + antlr4-python3-runtime==4.9.3
101
+ + async-timeout==5.0.1
102
+ + attrs==26.1.0
103
+ + av==16.1.0
104
+ + certifi==2026.4.22
105
+ + cffi==2.0.0
106
+ + charset-normalizer==3.4.7
107
+ + click==8.1.8
108
+ + cloudpickle==3.1.2
109
+ + contourpy==1.3.2
110
+ + cryptography==48.0.0
111
+ + cycler==0.12.1
112
+ + datasets==3.6.0
113
+ + diffusers==0.35.1
114
+ + dill==0.3.8
115
+ + dm-tree==0.1.10
116
+ + docstring-parser==0.18.0
117
+ + einops==0.8.1
118
+ + eval-type-backport==0.3.1
119
+ + farama-notifications==0.0.6
120
+ + filelock==3.29.0
121
+ + fonttools==4.62.1
122
+ + frozenlist==1.8.0
123
+ + fsspec==2025.3.0
124
+ + gitdb==4.0.12
125
+ + gitpython==3.1.46
126
+ + gymnasium==1.2.2
127
+ + hf-xet==1.5.0
128
+ + huggingface-hub==0.36.2
129
+ + idna==3.14
130
+ + imageio==2.37.3
131
+ + importlib-metadata==9.0.0
132
+ + inquirerpy==0.3.4
133
+ + jinja2==3.1.6
134
+ + jsonlines==4.0.0
135
+ + kiwisolver==1.5.0
136
+ + lazy-loader==0.5
137
+ + lmdb==1.7.5
138
+ + markdown-it-py==4.2.0
139
+ + markupsafe==3.0.3
140
+ + matplotlib==3.10.1
141
+ + mdurl==0.1.2
142
+ + mpmath==1.3.0
143
+ + msgpack==1.1.0
144
+ + msgpack-numpy==0.4.8
145
+ + multidict==6.7.1
146
+ + multiprocess==0.70.16
147
+ + networkx==3.4.2
148
+ + numpy==1.26.4
149
+ + nvidia-cublas-cu12==12.6.4.1
150
+ + nvidia-cuda-cupti-cu12==12.6.80
151
+ + nvidia-cuda-nvrtc-cu12==12.6.77
152
+ + nvidia-cuda-runtime-cu12==12.6.77
153
+ + nvidia-cudnn-cu12==9.5.1.17
154
+ + nvidia-cufft-cu12==11.3.0.4
155
+ + nvidia-cufile-cu12==1.11.1.6
156
+ + nvidia-curand-cu12==10.3.7.77
157
+ + nvidia-cusolver-cu12==11.7.1.2
158
+ + nvidia-cusparse-cu12==12.5.4.2
159
+ + nvidia-cusparselt-cu12==0.6.3
160
+ + nvidia-nccl-cu12==2.26.2
161
+ + nvidia-nvjitlink-cu12==12.6.85
162
+ + nvidia-nvtx-cu12==12.6.77
163
+ + omegaconf==2.3.0
164
+ + opencv-python-headless==4.11.0.86
165
+ + packaging==26.2
166
+ + pandas==2.2.3
167
+ + peft==0.17.1
168
+ + pfzy==0.3.4
169
+ + pillow==12.2.0
170
+ + platformdirs==4.9.6
171
+ + prompt-toolkit==3.0.52
172
+ + propcache==0.5.2
173
+ + protobuf==6.33.6
174
+ + psutil==7.2.2
175
+ + pyarrow==24.0.0
176
+ + pycparser==3.0
177
+ + pydantic==2.13.4
178
+ + pydantic-core==2.46.4
179
+ + pygments==2.20.0
180
+ + pyparsing==3.3.2
181
+ + python-dateutil==2.9.0.post0
182
+ + pytz==2026.2
183
+ + pyyaml==6.0.3
184
+ + pyzmq==27.0.1
185
+ + regex==2026.5.9
186
+ + requests==2.34.0
187
+ + rich==15.0.0
188
+ + safetensors==0.7.0
189
+ + scikit-image==0.25.2
190
+ + scipy==1.15.3
191
+ + sentencepiece==0.2.1
192
+ + sentry-sdk==2.59.0
193
+ + setuptools==82.0.1
194
+ + shtab==1.8.0
195
+ + six==1.17.0
196
+ + smmap==5.0.3
197
+ + sympy==1.14.0
198
+ + termcolor==3.2.0
199
+ + tifffile==2025.5.10
200
+ + tokenizers==0.22.2
201
+ + torch==2.7.1
202
+ + torchcodec==0.4.0
203
+ + torchvision==0.22.1
204
+ + tqdm==4.67.3
205
+ + transformers==4.57.3
206
+ + triton==3.3.1
207
+ + typeguard==4.5.1
208
+ + typing-extensions==4.15.0
209
+ + typing-inspection==0.4.2
210
+ + tyro==0.9.17
211
+ + tzdata==2026.2
212
+ + urllib3==2.7.0
213
+ + wandb==0.23.0
214
+ + wcwidth==0.7.0
215
+ + wrapt==2.1.2
216
+ + xxhash==3.7.0
217
+ + yarl==1.23.0
218
+ + zipp==3.23.1
outputs/_setup_logs/server_live.log ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Starting GR00T inference server...
2
+ Embodiment tag: EmbodimentTag.LIBERO_PANDA
3
+ Model path: checkpoints/GR00T-N1.7-LIBERO/libero_10
4
+ Device: cuda
5
+ Host: 127.0.0.1
6
+ Port: 5555
7
+ /home/ubuntu/VLA_SAE/.venv/lib/python3.10/site-packages/albumentations/__init__.py:13: UserWarning: A new version of Albumentations is available: 2.0.8 (you have 1.4.18). Upgrade using: pip install -U albumentations. To disable automatic update checks, set the environment variable NO_ALBUMENTATIONS_UPDATE to 1.
8
+ check_for_updates()
9
+ flash_attn is not installed. Falling back to sdpa attention. Install flash-attn for better performance: pip install flash-attn
10
+ /home/ubuntu/VLA_SAE/gr00t/model/modules/dit.py:255: FutureWarning: Accessing config attribute `compute_dtype` directly via 'AlternateVLDiT' object attribute is deprecated. Please access 'compute_dtype' over 'AlternateVLDiT's config object instead, e.g. 'unet.config.compute_dtype'.
11
+ embedding_dim=self.inner_dim, compute_dtype=self.compute_dtype
12
+ /home/ubuntu/VLA_SAE/gr00t/model/modules/dit.py:286: FutureWarning: Accessing config attribute `output_dim` directly via 'AlternateVLDiT' object attribute is deprecated. Please access 'output_dim' over 'AlternateVLDiT's config object instead, e.g. 'unet.config.output_dim'.
13
+ self.proj_out_2 = nn.Linear(self.inner_dim, self.output_dim)
14
+ Total number of DiT parameters: 1091722240
15
+ Total number of SelfAttentionTransformer parameters: 201433088
16
+
17
+
18
+ ✓ Server ready — listening on 127.0.0.1:5555
19
+
20
+ Server is ready and listening on tcp://127.0.0.1:5555
outputs/_setup_logs/smoke_run.log ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Loaded 10 scenarios from examples/LIBERO/smoke_tests/scenarios_10.yaml
2
+ labels: 8 normal, 2 abnormal_probe
3
+
4
+ Expected GR00T server command (run this in a separate terminal):
5
+ uv run python gr00t/eval/run_gr00t_server.py --model-path checkpoints/GR00T-N1.7-LIBERO/libero_10 --embodiment-tag LIBERO_PANDA --use-sim-policy-wrapper
6
+
7
+ NOTE: The GR00T-N1.7 backbone 'nvidia/Cosmos-Reason2-2B' is a GATED HuggingFace repo.
8
+ To start the server you must:
9
+ 1. Request access at https://huggingface.co/nvidia/Cosmos-Reason2-2B (one click, usually instant).
10
+ 2. Authenticate, e.g. export HF_TOKEN=hf_xxx (or: uv run hf auth login)
11
+ 3. Re-run the server / smoke tests.
12
+
13
+ Model checkpoint OK: checkpoints/GR00T-N1.7-LIBERO/libero_10
14
+ Run directory: outputs/libero_smoke_tests/20260512_122756
15
+ GR00T server reachable at 127.0.0.1:5555.
16
+ LIBERO rollout interpreter: /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/python
17
+
18
+ [1/10] normal_kitchen3_moka_pot_on_stove (normal) env=libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it seed=1000 max_episode_steps=50
19
+ -> ok rc=0 17.3s actions=yes video=yes success=False
20
+
21
+ [2/10] normal_kitchen4_bowl_in_drawer (normal) env=libero_sim/KITCHEN_SCENE4_put_the_black_bowl_in_the_bottom_drawer_of_the_cabinet_and_close_it seed=1001 max_episode_steps=50
22
+ -> ok rc=0 12.1s actions=yes video=yes success=False
23
+
24
+ [3/10] normal_living1_soup_and_cheese_in_basket (normal) env=libero_sim/LIVING_ROOM_SCENE1_put_both_the_alphabet_soup_and_the_cream_cheese_box_in_the_basket seed=1002 max_episode_steps=50
25
+ -> ok rc=0 13.3s actions=yes video=yes success=False
26
+
27
+ [4/10] normal_living2_soup_and_tomato_in_basket (normal) env=libero_sim/LIVING_ROOM_SCENE2_put_both_the_alphabet_soup_and_the_tomato_sauce_in_the_basket seed=1003 max_episode_steps=50
28
+ -> ok rc=0 14.2s actions=yes video=yes success=False
29
+
30
+ [5/10] normal_study1_book_in_caddy (normal) env=libero_sim/STUDY_SCENE1_pick_up_the_book_and_place_it_in_the_back_compartment_of_the_caddy seed=1004 max_episode_steps=50
31
+ -> ok rc=0 12.0s actions=yes video=yes success=False
32
+
33
+ [6/10] normal_kitchen8_both_moka_pots_on_stove (normal) env=libero_sim/KITCHEN_SCENE8_put_both_moka_pots_on_the_stove seed=1005 max_episode_steps=50
34
+ -> ok rc=0 10.8s actions=yes video=yes success=False
35
+
36
+ [7/10] normal_kitchen6_mug_in_microwave (normal) env=libero_sim/KITCHEN_SCENE6_put_the_yellow_and_white_mug_in_the_microwave_and_close_it seed=1006 max_episode_steps=50
37
+ -> ok rc=0 12.1s actions=yes video=yes success=False
38
+
39
+ [8/10] normal_living5_two_mugs_on_plates (normal) env=libero_sim/LIVING_ROOM_SCENE5_put_the_white_mug_on_the_left_plate_and_put_the_yellow_and_white_mug_on_the_right_plate seed=1007 max_episode_steps=50
40
+ -> ok rc=0 15.1s actions=yes video=yes success=False
41
+
42
+ [9/10] abnormal_probe_obs_noise (abnormal_probe) env=libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it seed=2000 max_episode_steps=50
43
+ -> ok rc=0 10.8s actions=yes video=yes success=False
44
+
45
+ [10/10] abnormal_probe_short_timeout (abnormal_probe) env=libero_sim/LIVING_ROOM_SCENE2_put_both_the_alphabet_soup_and_the_tomato_sauce_in_the_basket seed=2001 max_episode_steps=16
46
+ -> ok rc=0 13.1s actions=yes video=yes success=False
47
+
48
+ ======== SMOKE-TEST SUMMARY ========
49
+ scenario_id | label | seed | rollout_started | actions_produced | video_saved | success | output_dir | error_if_any
50
+ -----------------------------------------+----------------+------+-----------------+------------------+-------------+---------+-------------------------------------------------------------------------------------+-------------
51
+ normal_kitchen3_moka_pot_on_stove | normal | 1000 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_kitchen3_moka_pot_on_stove |
52
+ normal_kitchen4_bowl_in_drawer | normal | 1001 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_kitchen4_bowl_in_drawer |
53
+ normal_living1_soup_and_cheese_in_basket | normal | 1002 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_living1_soup_and_cheese_in_basket |
54
+ normal_living2_soup_and_tomato_in_basket | normal | 1003 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_living2_soup_and_tomato_in_basket |
55
+ normal_study1_book_in_caddy | normal | 1004 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_study1_book_in_caddy |
56
+ normal_kitchen8_both_moka_pots_on_stove | normal | 1005 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_kitchen8_both_moka_pots_on_stove |
57
+ normal_kitchen6_mug_in_microwave | normal | 1006 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_kitchen6_mug_in_microwave |
58
+ normal_living5_two_mugs_on_plates | normal | 1007 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/normal_living5_two_mugs_on_plates |
59
+ abnormal_probe_obs_noise | abnormal_probe | 2000 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise |
60
+ abnormal_probe_short_timeout | abnormal_probe | 2001 | yes | yes | yes | False | outputs/libero_smoke_tests/20260512_122756/abnormal_probe_short_timeout |
61
+
62
+ Wrote: outputs/libero_smoke_tests/20260512_122756/summary.json
63
+ Wrote: outputs/libero_smoke_tests/20260512_122756/summary.md
64
+
65
+ Review the videos with:
66
+ python /home/ubuntu/VLA_SAE/examples/LIBERO/smoke_tests/review_smoke_tests.py --run-dir outputs/libero_smoke_tests/20260512_122756
67
+
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/actions.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b7b5e56b63aa6efeeb95fae090d9988add5621839fd3eb16aa95c6b22fad60fe
3
+ size 3264
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00000.png ADDED

Git LFS Details

  • SHA256: b40aed48976c0cf7b45e47d2f288bbb60ef33840d02911c00af36a581c1c2629
  • Pointer size: 131 Bytes
  • Size of remote file: 138 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00001.png ADDED

Git LFS Details

  • SHA256: ce573c1e704b83ff069695b9819986b3869c5b774ce81f62e454ecdcd0278b4a
  • Pointer size: 131 Bytes
  • Size of remote file: 141 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00002.png ADDED

Git LFS Details

  • SHA256: f4640431fc5a1d1f821e9ef1043e88b1492463a29d54bce2526b91d3a52a9cc5
  • Pointer size: 131 Bytes
  • Size of remote file: 142 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00003.png ADDED

Git LFS Details

  • SHA256: 7b669cdf7c04b652be023304c4fbb8c2ae1a6d586c96901a9503a17941e12229
  • Pointer size: 131 Bytes
  • Size of remote file: 142 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00004.png ADDED

Git LFS Details

  • SHA256: 68b9792bca0a20cf3054d266f40902edc3bcdf76c454e3e6bc244c8057814ca0
  • Pointer size: 131 Bytes
  • Size of remote file: 143 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00005.png ADDED

Git LFS Details

  • SHA256: f4609c1f9c8da9d222c73b89d0e23ab916c4a16dc05a3a39bd1aca81bf20ad05
  • Pointer size: 131 Bytes
  • Size of remote file: 144 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00006.png ADDED

Git LFS Details

  • SHA256: e92118fa5ac2ca75fabede03302c2508bf098a58d4b3db02a63ddac7e1ff2791
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00007.png ADDED

Git LFS Details

  • SHA256: 794df451fd713ac97454a6b70a4266f43c26b4cafae78bfbf5813da7da1bf026
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00008.png ADDED

Git LFS Details

  • SHA256: ac199f345db3997d9dd04f6a551a5c86488f98bb965db79115d4c57328629e6d
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00009.png ADDED

Git LFS Details

  • SHA256: cc68141042bfb21d69404625513121651010b09c23c3190cbaf7dd59486dcdf7
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00010.png ADDED

Git LFS Details

  • SHA256: df496dedc1127f1aa25793b55576ddff64007a7ad5c7be265de68e038e77d5c8
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00011.png ADDED

Git LFS Details

  • SHA256: 89c9041c6b6ee805fd553072a51fb90d779c6c53fc376ab837c2051d1593acf3
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00012.png ADDED

Git LFS Details

  • SHA256: b9d3d56e939d7fa688fc2a9f3cfd33b8727ee015dc3ef4059bc036284edf2e6f
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00013.png ADDED

Git LFS Details

  • SHA256: b377dff2fec609f14a97143c43e610d4da9a4f788390f3e30cb58c5f588ad8d6
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00014.png ADDED

Git LFS Details

  • SHA256: 4cd775957fff34c66ed2f6187117e3662532fd12ad4264b3af95561babb9a5fb
  • Pointer size: 131 Bytes
  • Size of remote file: 148 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00015.png ADDED

Git LFS Details

  • SHA256: ca89db5d716b9dcea3956ecba9877f0abba32d173e68ba42438cc8533e939af6
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00016.png ADDED

Git LFS Details

  • SHA256: 89ee52ee90dbb9cda328936c5f3ed6de23c074a71bd065e22404cd172e1859d4
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00017.png ADDED

Git LFS Details

  • SHA256: b7410f809cd5499d2eea8e6c8e7609c379a4aa93f560a476cf8122a2910512d1
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00018.png ADDED

Git LFS Details

  • SHA256: a8d051f4f8ae6a1d89cbe3bcfb4c2a8acc28fa680d58a17fd1157bb743bd2cca
  • Pointer size: 131 Bytes
  • Size of remote file: 152 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00019.png ADDED

Git LFS Details

  • SHA256: 9a144b2fad65f6c38ddcecb93defcecfe012eb68a0d34ff98ab9e4534c170fa4
  • Pointer size: 131 Bytes
  • Size of remote file: 155 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00020.png ADDED

Git LFS Details

  • SHA256: 38fa78262739e48de633ac36efd2000a1bcda45484e5c0b5f97fa205edc93ac4
  • Pointer size: 131 Bytes
  • Size of remote file: 155 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00021.png ADDED

Git LFS Details

  • SHA256: a067f36741f6d6e7ce89dca586e28ff773b48c1f5e2f2d9f836ed8d0ccd41afc
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00022.png ADDED

Git LFS Details

  • SHA256: d2c5c6936e333c0d4a6504795cd4f5c729013ea3beaccaabe922fe97f7e95206
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00023.png ADDED

Git LFS Details

  • SHA256: ca5b6322ef0cd3175f8b11a158544784b4f6c16747a778ec6f1955ea12c06ad2
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames/frame_00024.png ADDED

Git LFS Details

  • SHA256: 617cac24df034e2b9548fa3c44bd13c4a53382434b8dd8a9092d3ece83243b45
  • Pointer size: 131 Bytes
  • Size of remote file: 149 kB
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "scenario_id": "abnormal_probe_obs_noise",
3
+ "label": "abnormal_probe",
4
+ "manifest_entry": {
5
+ "id": "abnormal_probe_obs_noise",
6
+ "label": "abnormal_probe",
7
+ "env_name": "libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it",
8
+ "instruction": "turn on the stove and put the moka pot on it",
9
+ "instruction_override": null,
10
+ "seed": 2000,
11
+ "max_episode_steps": 50,
12
+ "n_action_steps": 8,
13
+ "save_video": true,
14
+ "obs_noise_std": 6.0,
15
+ "action_repeat": 1,
16
+ "notes": "Simulation-only perturbation: mild zero-mean Gaussian noise (sigma=6 in 0-255 pixel units) is added to the image observations *before* they are sent to the policy. Smoke test only - this is a placeholder for future, more principled distribution-shift probes; no complex anomaly logic."
17
+ },
18
+ "resolved": {
19
+ "env_name": "libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it",
20
+ "seed": 2000,
21
+ "max_episode_steps": 50,
22
+ "n_action_steps": 8,
23
+ "save_video": true,
24
+ "save_frames": true,
25
+ "obs_noise_std": 6.0,
26
+ "action_repeat": 1,
27
+ "instruction_override": null
28
+ },
29
+ "model_path": "checkpoints/GR00T-N1.7-LIBERO/libero_10",
30
+ "policy_server": {
31
+ "host": "127.0.0.1",
32
+ "port": 5555
33
+ },
34
+ "libero_python": "/home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/bin/python",
35
+ "timestamp": "2026-05-12T12:29:46"
36
+ }
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/rollout_summary.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "env_name": "libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it",
3
+ "seed": 2000,
4
+ "requested_max_episode_steps": 50,
5
+ "n_action_steps": 8,
6
+ "n_get_action_calls": 7,
7
+ "episode_successes": [
8
+ false
9
+ ],
10
+ "success": false,
11
+ "episode_length": 7,
12
+ "episode_reward": 0.0,
13
+ "elapsed_sec": 6.133596658706665,
14
+ "obs_noise_std": 6.0,
15
+ "action_repeat": 1,
16
+ "video_path": "outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/video.mp4",
17
+ "actions_path": "outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/actions.npy",
18
+ "policy_server": {
19
+ "host": "127.0.0.1",
20
+ "port": 5555
21
+ }
22
+ }
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/stderr.log ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [robosuite WARNING] No private macro file found! (__init__.py:7)
2
+ [robosuite WARNING] It is recommended to use a private macro file (__init__.py:8)
3
+ [robosuite WARNING] To setup, run: python /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/robosuite/scripts/setup_macros.py (__init__.py:9)
4
+ Matplotlib created a temporary config/cache directory at /tmp/matplotlib-syt5xusc because the default path (/home/ubuntu/.config/matplotlib) is not a writable directory; it is highly recommended to set the MPLCONFIGDIR environment variable to a writable directory, in particular to speed up the import of Matplotlib and to better support multiprocessing.
5
+ Gym has been unmaintained since 2022 and does not support NumPy 2.0 amongst other critical functionality.
6
+ Please upgrade to Gymnasium, the maintained drop-in replacement of Gym, or contact the authors of your software and request that they upgrade.
7
+ See the migration guide at https://gymnasium.farama.org/introduction/migration_guide/ for additional information.
8
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:159: UserWarning: WARN: The obs returned by the `reset()` method is not within the observation space.
9
+ logger.warn(f"{pre} is not within the observation space.")
10
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:135: UserWarning: WARN: The obs returned by the `reset()` method was expecting numpy array dtype to be float32, actual type: float64
11
+ logger.warn(
12
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:131: UserWarning: WARN: The obs returned by the `reset()` method was expecting a numpy array, actual type: <class 'list'>
13
+ logger.warn(
14
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/spaces/box.py:240: UserWarning: WARN: Casting input x to numpy array.
15
+ gym.logger.warn("Casting input x to numpy array.")
16
+
17
+ logger.warn(f"{pre} is not within the observation space.")
18
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:135: UserWarning: WARN: The obs returned by the `step()` method was expecting numpy array dtype to be float32, actual type: float64
19
+ logger.warn(
20
+ /home/ubuntu/VLA_SAE/gr00t/eval/sim/LIBERO/libero_uv/.venv/lib/python3.10/site-packages/gymnasium/utils/passive_env_checker.py:131: UserWarning: WARN: The obs returned by the `step()` method was expecting a numpy array, actual type: <class 'list'>
21
+ logger.warn(
22
+
outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/stdout.log ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Running collecting 1 episodes for libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it with 1 vec envs
2
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
3
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
4
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5
+ [info] using task orders [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
6
+ Collecting 1 episodes took 6.133342027664185 seconds
7
+ SMOKE_RESULT_JSON: {"ok": true, "rollout_started": true, "actions_produced": true, "video_saved": true, "success": false, "error": null, "env_name": "libero_sim/KITCHEN_SCENE3_turn_on_the_stove_and_put_the_moka_pot_on_it", "seed": 2000, "n_action_calls": 7, "episode_length": 7, "episode_reward": 0.0, "video_path": "outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/video.mp4", "actions_path": "outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/actions.npy", "video_source_name": "1c2407cd-5353-4895-957f-f8b3986573d2_s0.mp4", "frames_dir": "outputs/libero_smoke_tests/20260512_122756/abnormal_probe_obs_noise/frames", "n_frames": 25}