Skip to content

Commit 4c665fa

Browse files
committed
Fix vectorized env state persistence and empty episode stats handling
1 parent b6613d1 commit 4c665fa

2 files changed

Lines changed: 50 additions & 21 deletions

File tree

rl/algos/ppo.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ def evaluate(self, env_fn, nets, itr, num_batches=5):
428428

429429
# calculate average evaluation reward
430430
eval_ep_rewards = [float(i) for batch in eval_batches for i in batch.ep_rewards]
431-
avg_eval_ep_rewards = np.mean(eval_ep_rewards)
431+
avg_eval_ep_rewards = np.mean(eval_ep_rewards) if eval_ep_rewards else float("nan")
432432

433433
# save checkpoint - saves with suffix and as best if improved
434434
self.checkpointer.save_if_best(nets, avg_eval_ep_rewards, itr)
@@ -441,6 +441,10 @@ def train(self, env_fn, n_itr):
441441

442442
train_start_time = time.time()
443443

444+
# Track last known episode stats for iterations where no episodes complete
445+
last_mean_ep_reward = float("nan")
446+
last_mean_ep_len = float("nan")
447+
444448
obs_mirr, act_mirr = None, None
445449
if hasattr(env_fn(), "mirror_observation"):
446450
obs_mirr = env_fn().mirror_clock_observation
@@ -580,9 +584,19 @@ def train(self, env_fn, n_itr):
580584

581585
action_noise = self.policy.stds.data.tolist()
582586

587+
# Handle empty episode stats (no episodes completed this iteration)
588+
if len(batch.ep_rewards) > 0:
589+
mean_ep_reward = float(torch.mean(batch.ep_rewards))
590+
mean_ep_len = float(torch.mean(batch.ep_lens.float()))
591+
last_mean_ep_reward = mean_ep_reward
592+
last_mean_ep_len = mean_ep_len
593+
else:
594+
mean_ep_reward = last_mean_ep_reward
595+
mean_ep_len = last_mean_ep_len
596+
583597
sys.stdout.write("-" * 37 + "\n")
584-
sys.stdout.write(f"| {'Mean Eprew':>15} | {torch.mean(batch.ep_rewards):>15.5g} |\n")
585-
sys.stdout.write(f"| {'Mean Eplen':>15} | {torch.mean(batch.ep_lens.float()):>15.5g} |\n")
598+
sys.stdout.write(f"| {'Mean Eprew':>15} | {mean_ep_reward:>15.5g} |\n")
599+
sys.stdout.write(f"| {'Mean Eplen':>15} | {mean_ep_len:>15.5g} |\n")
586600
sys.stdout.write(f"| {'Actor loss':>15} | {np.mean(actor_losses):>15.3g} |\n")
587601
sys.stdout.write(f"| {'Critic loss':>15} | {np.mean(critic_losses):>15.3g} |\n")
588602
sys.stdout.write(f"| {'Mirror loss':>15} | {np.mean(mirror_losses):>15.3g} |\n")
@@ -614,8 +628,8 @@ def train(self, env_fn, n_itr):
614628

615629
eval_ep_lens = [float(i) for b in eval_batches for i in b.ep_lens]
616630
eval_ep_rewards = [float(i) for b in eval_batches for i in b.ep_rewards]
617-
avg_eval_ep_lens = np.mean(eval_ep_lens)
618-
avg_eval_ep_rewards = np.mean(eval_ep_rewards)
631+
avg_eval_ep_lens = np.mean(eval_ep_lens) if eval_ep_lens else float("nan")
632+
avg_eval_ep_rewards = np.mean(eval_ep_rewards) if eval_ep_rewards else float("nan")
619633
print("====EVALUATE EPISODE====")
620634
print(
621635
f"(Episode length:{avg_eval_ep_lens:.3f}. Reward:{avg_eval_ep_rewards:.3f}. "
@@ -631,8 +645,8 @@ def train(self, env_fn, n_itr):
631645
critic_loss=np.mean(critic_losses),
632646
mirror_loss=np.mean(mirror_losses),
633647
imitation_loss=np.mean(imitation_losses),
634-
mean_reward=float(torch.mean(batch.ep_rewards)),
635-
mean_ep_len=float(torch.mean(batch.ep_lens.float())),
648+
mean_reward=mean_ep_reward,
649+
mean_ep_len=mean_ep_len,
636650
mean_noise_std=np.mean(action_noise),
637651
step=itr,
638652
)

rl/workers/rollout_worker.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ def __init__(self, env_fn, policy_template, critic_template, num_envs_per_worker
8080
self.current_ep_reward = 0.0
8181
self.current_ep_len = 0
8282

83+
# Vectorized env state persistence
84+
if self.is_vectorized:
85+
self.vec_states = None
86+
self.vec_traj_lens = None
87+
self.vec_ep_reward_accum = None
88+
self.vec_ep_len_accum = None
89+
8390
def sync_state(self, policy_state_dict, critic_state_dict, obs_mean, obs_std, iteration_count):
8491
"""Sync all worker state from main process in a single call.
8592
@@ -231,22 +238,24 @@ def _sample_vectorized(self, gamma, max_steps, max_traj_len, deterministic):
231238
PPOBuffer(self.state_dim, self.action_dim, gamma=gamma, size=max_traj_len * 2) for _ in range(num_envs)
232239
]
233240

234-
# Reset all environments
235-
states = torch.as_tensor(vec_env.reset_all(), dtype=torch.float) # [num_envs, obs_dim]
236-
traj_lens = torch.zeros(num_envs, dtype=torch.int32)
241+
# Initialize or continue from previous state
242+
if self.vec_states is None:
243+
states = torch.as_tensor(vec_env.reset_all(), dtype=torch.float)
244+
traj_lens = torch.zeros(num_envs, dtype=torch.int32)
245+
ep_reward_accum = [0.0] * num_envs
246+
ep_len_accum = [0] * num_envs
247+
if hasattr(policy, "init_hidden_state"):
248+
policy.init_hidden_state()
249+
if hasattr(critic, "init_hidden_state"):
250+
critic.init_hidden_state()
251+
else:
252+
states = self.vec_states
253+
traj_lens = self.vec_traj_lens
254+
ep_reward_accum = self.vec_ep_reward_accum
255+
ep_len_accum = self.vec_ep_len_accum
256+
237257
ep_lens_per_env = [[] for _ in range(num_envs)]
238258
ep_rewards_per_env = [[] for _ in range(num_envs)]
239-
ep_reward_accum = [0.0] * num_envs
240-
ep_len_accum = [0] * num_envs
241-
242-
# For recurrent policies
243-
if hasattr(policy, "init_hidden_state"):
244-
# Note: This might need modification for vectorized recurrent policies
245-
# Currently resets to same initial state for all envs
246-
policy.init_hidden_state()
247-
248-
if hasattr(critic, "init_hidden_state"):
249-
critic.init_hidden_state()
250259

251260
total_steps = 0
252261
while total_steps < max_steps:
@@ -315,6 +324,12 @@ def _sample_vectorized(self, gamma, max_steps, max_traj_len, deterministic):
315324
# Bootstrap with final value (trajectory was truncated, not done)
316325
buffers[i].finish_path(last_val=final_value)
317326

327+
# Persist state for next sample() call
328+
self.vec_states = states
329+
self.vec_traj_lens = traj_lens
330+
self.vec_ep_reward_accum = ep_reward_accum
331+
self.vec_ep_len_accum = ep_len_accum
332+
318333
# Flatten episode stats across all envs
319334
all_ep_lens = [ep_len for per_env in ep_lens_per_env for ep_len in per_env]
320335
all_ep_rewards = [r for per_env in ep_rewards_per_env for r in per_env]

0 commit comments

Comments
 (0)