Skip to content

Commit 1e384fd

Browse files
committed
[BugFix] Stabilize LIBERO VLA GRPO training
Add reference rollout comparison and parity diagnostics for OpenVLA on LIBERO. Align preprocessing, CPU action decoding, inference microbatching, collector lifecycle, replay grouping, evaluator metrics, and policy synchronization with the validated runtime. Add paper-shaped configuration controls and focused regression coverage for the end-to-end VLA GRPO path. ghstack-source-id: a88d47e Pull-Request: #3940
1 parent a309c43 commit 1e384fd

30 files changed

Lines changed: 4724 additions & 2216 deletions

sota-implementations/vla_grpo/README.md

Lines changed: 150 additions & 87 deletions
Large diffs are not rendered by default.

sota-implementations/vla_grpo/compare_simplevla_rollouts.py

Lines changed: 885 additions & 0 deletions
Large diffs are not rendered by default.

sota-implementations/vla_grpo/config/vla_grpo_libero.yaml

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
# SimpleVLA-RL (arXiv:2509.09674) on LIBERO: OpenVLA-OFT token variant (7B),
2-
# the paper's hyper-parameters. Parallel MuJoCo workers (one process each)
3-
# feed a single training device; see the README for the multi-GPU notes and
2+
# the paper's hyper-parameters. H100 fast mode uses shared policy-server
3+
# MultiCollector rollout workers; see the README for the multi-GPU notes and
44
# the LoRA fallback. Requires LIBERO (and its deps) plus transformers/timm.
55
#
6-
# Per-iteration accounting: groups_per_iter x group_size = 512 trajectories,
6+
# Per-target-wave accounting: groups_per_iter x group_size = 320 trajectories,
77
# each up to max_outer_steps = 64 chunk decisions (512 env steps / chunk 8).
88

99
env:
1010
backend: libero
1111
task_suite: libero_spatial # libero_spatial | libero_object | libero_goal | libero_10
1212
task_ids: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # one worker per task (num_envs must cover them)
13-
# Parallel MuJoCo processes (training); >= len(task_ids). For GRPO, each
14-
# worker owns serial group ids, so keep num_envs <= groups_per_iter (ideally
15-
# a divisor, often equal) or no worker may finish a full group per iteration.
16-
num_envs: 10
13+
num_envs: 80
1714
# false: each worker replays group_size rollouts serially for one group.
1815
# true: group_size workers share the same task/init state/group id and
1916
# collect a GRPO group in parallel. This improves group-completion throughput
@@ -26,24 +23,28 @@ env:
2623
# batches can overcollect under one policy, but without a group barrier they
2724
# may also drift; the LIBERO wrapper groups parallel repeats by cycled
2825
# init-state id to reduce this waste.
29-
parallel_group_repeats: false
26+
parallel_group_repeats: true
3027
eval_num_envs: 10 # parallel MuJoCo processes (evaluation); >= len(task_ids)
3128
camera_height: 256
3229
camera_width: 256
3330
render_backend: egl # egl (GPU/headless) | osmesa (CPU fallback)
34-
render_gpu_ids: [0] # EGL-visible ids; override to [0,1,...] to spread workers
35-
eval_render_gpu_ids: null # null = reuse render_gpu_ids for eval workers
31+
render_gpu_ids: [2, 3, 4, 5] # H100 fast mode render GPUs for rollout workers
32+
eval_render_gpu_ids: [7] # reserve GPU 7 for eval/video rendering
33+
render_gpu_device_zero_fallback: true
34+
env_kwargs: null
3635
chunk_size: 8 # NUM_ACTIONS_CHUNK of the checkpoint
3736
max_env_steps: 512 # base env steps per episode (the paper's cap)
3837
max_outer_steps: 64 # = max_env_steps / chunk_size, in chunk decisions
39-
train_init_state_mode: random # random | cycle | fixed; eval always cycles
38+
train_init_state_mode: cycle # random | cycle | fixed; eval always cycles
39+
train_init_state_id: 0 # selected LIBERO init state when train_init_state_mode=fixed
4040
seed: 0
4141

4242
tokenizer:
4343
vocab_size: 256 # unused for the openvla backend (codec comes from the checkpoint)
4444

4545
policy:
4646
backend: openvla
47+
mode: tokens # tokens: SimpleVLA-RL GRPO path; l1: official continuous OFT reference/eval path
4748
# the checkpoint MUST match task_suite (it fixes the SFT policy AND the
4849
# action statistics unnorm_key resolves into)
4950
checkpoint: Haozhan72/Openvla-oft-SFT-libero-spatial-traj1
@@ -56,58 +57,54 @@ policy:
5657
temperature: 1.6 # rollout sampling temperature (greedy eval)
5758
top_k: null # null = full categorical; small values keep token exploration local
5859
use_wrist_image: false
60+
# L1 reference path: set mode=l1, checkpoint=moojink/openvla-7b-oft-finetuned-libero-spatial,
61+
# use_wrist_image=true, use_proprio=true, num_images_in_input=2.
62+
action_head_file: action_head--150000_checkpoint.pt
63+
proprio_projector_file: proprio_projector--150000_checkpoint.pt
64+
use_proprio: false
65+
num_images_in_input: 1
5966
center_crop: true # the SimpleVLA-RL checkpoints are trained with augmentations
60-
image_backend: torchvision # OpenVLA image preprocessing backend: torchvision | pil
67+
image_backend: torchvision # OpenVLA preprocessing: torchvision | pil | tensorflow (reference)
6168
# the model emits a continuous gripper; LIBERO/robosuite needs a firm +/-1
6269
# open/close (demo gripper is binary, -1 open / +1 close). SimpleVLA-RL
63-
# applies sign(2*g - 1) then inverts, i.e. a 0.5 binarization threshold
64-
# before the open/close sign flip.
70+
# applies sign(2 * g - 1) then inverts, so threshold 0.0 here is equivalent
71+
# to threshold 0.5 on the raw decoded gripper scalar.
6572
gripper_binarize: true
66-
gripper_binarize_threshold: 0.5
73+
gripper_binarize_threshold: 0.0
6774
gripper_invert: true
6875
lora_rank: 32 # de-risk fallback; set to 0/null for full fine-tuning (needs FSDP)
6976
lora_target_modules: [q_proj, k_proj, v_proj, o_proj]
70-
device: null # null = auto (cuda if available)
77+
device: cuda:0 # training device
7178

7279
collector:
7380
group_size: 8 # n: rollouts per initial state (GRPO group)
7481
candidate_group_size: null # null = group_size; e.g. 16 samples 16 and selects 8
75-
# Initial states per iteration (512 trajectories). Must be >= env.num_envs;
76-
# for best throughput, use a multiple of env.num_envs so no partial groups
77-
# are discarded at the synchronous update boundary. If
78-
# env.parallel_group_repeats=true, use env.num_envs / group_size instead.
79-
groups_per_iter: 64
80-
# Safety cap in target group waves. The collector writes complete
81-
# trajectories directly to the replay buffer every one-outer-step internal
82-
# poll; one wave is approximately max_outer_steps *
83-
# ceil(groups_per_iter * group_size / num_envs) polls. Keeping MCAdvantage
84-
# queues across these same-policy polls lets partial GRPO groups finish
85-
# without crossing a policy update.
86-
max_collect_batches_per_iter: 1
87-
# If a capped wave produces too few useful replay decisions, keep the same
88-
# rollout policy and queued partial groups for another capped wave instead
89-
# of updating on a tiny replay batch and clearing those queues immediately.
90-
max_same_policy_collect_attempts: 2
91-
min_replay_decisions: null # null/0 = collect one target group wave
82+
# Initial states per iteration (320 trajectories). With shared fast mode and
83+
# env.parallel_group_repeats=true, use
84+
# num_collectors * envs_per_collector / group_size.
85+
groups_per_iter: 40
86+
# null/0 = one full selected rollout set:
87+
# groups_per_iter * group_size * max_outer_steps decisions.
88+
min_replay_decisions: null
9289
total_iters: 100 # the paper's total_epochs
93-
policy_device: null # null = policy.device; set e.g. cuda:1 for rollout inference
94-
# Execution-mode switches for throughput ablations:
95-
# - false/false: regular synchronous TorchRL Collector.
96-
# - true/false: async env slots with one request per policy forward.
97-
# - true/true: async env slots plus auto-batched policy inference.
98-
# - false/true: sync env stepping through the policy server path.
99-
async_env: false
100-
async_policy: false
101-
env_backend: threading # AsyncBatchedCollector env backend: threading | multiprocessing
102-
policy_backend: threading # inference transport: threading | multiprocessing | ray | monarch
103-
server_backend: thread # process server needs a policy_factory and is not used here
104-
server_max_batch_size: null # null = env.num_envs when async_policy=true
90+
policy_device: cuda:1 # H100 default: rollout/eval inference server device
91+
# null = one model forward for the complete request batch. Set to 1 for
92+
# single-environment numerical parity while keeping batched env collection.
93+
policy_micro_batch_size: null
94+
replay_wait_s: 0.5
95+
replay_log_s: 30.0
96+
num_collectors: 4
97+
envs_per_collector: 80
98+
num_threads: 1
99+
server_max_batch_size: 1 # one batched ParallelEnv request; avoids mixed eval/rollout modes
105100
server_min_batch_size: 1
106-
server_timeout: 0.01
101+
server_timeout: 0.001
107102
server_collect_stats: true
108103
server_stats_window_size: 1024
109104
max_inflight_per_env: 1
110-
storing_device: null
105+
env_sub_threads: 1
106+
storing_device: cpu
107+
use_buffers: null
111108

112109
advantage:
113110
trajectory_return: sum # binary success return per trajectory
@@ -120,6 +117,9 @@ advantage:
120117

121118
buffer:
122119
device: cpu # decisions hold raw uint8 images; keep the buffer off-GPU
120+
shared_init: true
121+
capacity_group_waves: 7 # replay capacity as multiples of one target group wave
122+
consume_after_n_samples: 1 # sampled decisions leave the buffer after one update pass
123123

124124
loss:
125125
clip_epsilon: [0.2, 0.28] # asymmetric DAPO Clip-Higher (per-token, see ratio_level)
@@ -142,12 +142,11 @@ logger:
142142
mode: online
143143
eval_iter: 4 # evaluate every N iterations (the paper's cadence)
144144
eval_episodes: 50 # greedy episodes per evaluation (cycled init states)
145-
eval_process: false # true = run eval in a dedicated process
146-
eval_async: false # with eval_process, submit eval and log result later
147-
eval_device: null # null = policy.device/auto; set e.g. cuda:7 with eval_process
148-
eval_state_dir: null # null = tempfile dir for trainable-weight snapshots
149-
record_video: true # render eval rollouts via VideoRecorder (needs a logger)
150-
video_episodes: 2 # greedy episodes per recorded video (one camera stream)
145+
eval_backend: process # TorchRL Evaluator backend: thread | process | ray
146+
eval_async: false # submit eval and log result later
147+
eval_busy_policy: skip # skip | error | queue
148+
eval_timeout_s: 1800.0
149+
eval_device: null # null = rollout device; set e.g. cuda:7 for eval
151150
video_fps: 8 # frames per second of the logged video (one frame per decision)
152151

153152
checkpoint:

sota-implementations/vla_grpo/config/vla_grpo_toy.yaml

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ env:
1111
success_tol: 0.35 # sized so a random policy succeeds sometimes (cold-start signal)
1212
max_outer_steps: 6 # episode truncation, in chunk decisions
1313
render_size: 64 # side length of the from_pixels eval-video frame
14-
num_envs: 1 # async-env workers; ToyVLAEnv grouped rollouts are per worker
14+
num_envs: 1
15+
eval_num_envs: 1
16+
parallel_group_repeats: false
17+
train_init_state_mode: random
18+
render_backend: null
19+
render_gpu_ids: null
20+
eval_render_gpu_ids: null
21+
render_gpu_device_zero_fallback: true
22+
env_kwargs: null
1523
seed: 0
1624

1725
tokenizer:
@@ -26,33 +34,24 @@ collector:
2634
group_size: 4 # n: rollouts per initial state (GRPO group)
2735
candidate_group_size: null # null = group_size; larger values oversample then select group_size
2836
groups_per_iter: 8 # initial states per iteration
29-
# Safety cap in target group waves. The collector writes complete
30-
# trajectories directly to the replay buffer every one-outer-step internal
31-
# poll; one wave is approximately max_outer_steps *
32-
# ceil(groups_per_iter * group_size / num_envs) polls.
33-
max_collect_batches_per_iter: 1
34-
# Retry a capped wave once under the same policy if too few useful replay
35-
# decisions have reached the replay buffer.
36-
max_same_policy_collect_attempts: 2
3737
min_replay_decisions: null # null/0 = collect one target group wave
3838
total_iters: 200
39-
# Execution-mode switches for throughput ablations:
40-
# - false/false: regular synchronous TorchRL Collector.
41-
# - true/false: async env slots with one request per policy forward.
42-
# - true/true: async env slots plus auto-batched policy inference.
43-
# - false/true: sync env stepping through the policy server path.
44-
async_env: false
45-
async_policy: false
46-
env_backend: threading # AsyncBatchedCollector env backend: threading | multiprocessing
47-
policy_backend: threading # inference transport: threading | multiprocessing | ray | monarch
48-
server_backend: thread # process server needs a policy_factory and is not used here
49-
server_max_batch_size: null # null = number of async envs when async_policy=true
39+
replay_wait_s: 0.05
40+
replay_log_s: 30.0
41+
policy_device: null
42+
policy_micro_batch_size: null
43+
num_collectors: 1
44+
envs_per_collector: 1
45+
num_threads: 1
46+
env_sub_threads: 1
47+
server_max_batch_size: 1
5048
server_min_batch_size: 1
5149
server_timeout: 0.01
5250
server_collect_stats: true
5351
server_stats_window_size: 1024
5452
max_inflight_per_env: 1
55-
storing_device: null
53+
storing_device: cpu
54+
use_buffers: null
5655

5756
advantage:
5857
trajectory_return: sum # binary success return per trajectory
@@ -63,6 +62,9 @@ advantage:
6362

6463
buffer:
6564
device: null # null = training device
65+
shared_init: false
66+
capacity_group_waves: 1 # replay capacity as multiples of one target group wave
67+
consume_after_n_samples: 1 # sampled decisions leave the buffer after one update pass
6668

6769
loss:
6870
clip_epsilon: [0.2, 0.28] # asymmetric DAPO clip; a float gives symmetric clipping
@@ -85,14 +87,13 @@ logger:
8587
mode: online
8688
eval_iter: 10 # evaluate every N iterations
8789
eval_episodes: 50 # greedy episodes per evaluation
88-
eval_process: false # true = run eval in a dedicated process
89-
eval_async: false # with eval_process, submit eval and log result later
90-
eval_device: null # null = policy.device/auto; set e.g. cuda:7 with eval_process
91-
eval_state_dir: null # null = tempfile dir for trainable-weight snapshots
92-
record_video: true # render eval rollouts via VideoRecorder (needs a logger)
93-
video_episodes: 2 # greedy episodes per recorded video
90+
eval_backend: thread # TorchRL Evaluator backend: thread | process | ray
91+
eval_async: false # submit eval and log result later
92+
eval_busy_policy: skip # skip | error | queue
93+
eval_timeout_s: 300.0
94+
eval_device: null # null = rollout device; set e.g. cuda:7 for eval
9495
video_fps: 4 # frames per second of the logged video (one frame per decision)
9596

9697
checkpoint:
9798
save_iter: 25 # save every N iterations (0 disables)
98-
resume: null # path to a checkpoint_latest.pt to resume from
99+
resume: null # path to a checkpoint_latest TensorDict directory to resume from

0 commit comments

Comments
 (0)