Skip to content

Commit 76ba95d

Browse files
authored
Merge pull request #185 from unilabsim/refactor/issue-150-training-common
refactor: extract shared training entrypoint helpers
2 parents 501083f + 8d019e1 commit 76ba95d

8 files changed

Lines changed: 755 additions & 707 deletions

File tree

‎scripts/train_appo.py‎

Lines changed: 62 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
from __future__ import annotations
44

55
import datetime
6-
import importlib
76
import os
8-
import pkgutil
97
import sys
108
from pathlib import Path
119

@@ -16,27 +14,10 @@
1614
ROOT_DIR = Path(__file__).parent.parent
1715
sys.path.append(str(ROOT_DIR))
1816

17+
from unilab.training import create_env, ensure_registries, get_log_root, render_play_mode
1918
from unilab.utils.experiment_tracking import ExperimentTracker
2019

2120

22-
def ensure_registries():
23-
for pkg_name in (
24-
"unilab.envs.locomotion",
25-
"unilab.envs.manipulation",
26-
"unilab.envs.motion_tracking",
27-
):
28-
try:
29-
package = importlib.import_module(pkg_name)
30-
if hasattr(package, "__path__"):
31-
for _, name, _ in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
32-
try:
33-
importlib.import_module(name)
34-
except Exception:
35-
pass
36-
except ImportError:
37-
pass
38-
39-
4021
def build_appo_runner_kwargs(
4122
cfg: DictConfig,
4223
env_cfg_override: dict | None,
@@ -68,79 +49,52 @@ def run_motrix_play_loop(
6849
play_env_num: int,
6950
num_steps: int | None = None,
7051
) -> None:
71-
import time
72-
7352
import numpy as np
7453
from tensordict import TensorDict
7554

7655
if env.state is None:
7756
env.init_state()
78-
env._backend.init_renderer()
79-
80-
env_indices = np.arange(play_env_num, dtype=np.int32)
81-
obs_out, _ = env.reset(env_indices)
82-
obs_np = np.asarray(obs_out["obs"], dtype=np.float32)
83-
84-
last_render_time = time.perf_counter()
85-
render_dt = 1.0 / 60.0
86-
steps_run = 0
8757

8858
with torch.inference_mode():
89-
while num_steps is None or steps_run < num_steps:
90-
obs_torch = torch.from_numpy(obs_np).to(device)
91-
td = TensorDict({"policy": obs_torch}, batch_size=play_env_num)
92-
actions_torch = actor(td)
93-
actions_np = actions_torch.cpu().numpy().astype(np.float32)
94-
state = env.step(actions_np)
95-
obs_np = np.asarray(state.obs["obs"], dtype=np.float32)
96-
97-
current_time = time.perf_counter()
98-
elapsed = current_time - last_render_time
99-
if elapsed < render_dt:
100-
time.sleep(render_dt - elapsed)
101-
last_render_time = time.perf_counter()
102-
103-
env._backend.render()
104-
steps_run += 1
59+
render_play_mode(
60+
env,
61+
sim_backend="motrix",
62+
num_steps=num_steps,
63+
initialize=lambda: np.asarray(
64+
env.reset(np.arange(play_env_num, dtype=np.int32))[0]["obs"],
65+
dtype=np.float32,
66+
),
67+
step=lambda obs_np: np.asarray(
68+
env.step(
69+
actor(
70+
TensorDict(
71+
{"policy": torch.from_numpy(obs_np).to(device)}, batch_size=play_env_num
72+
)
73+
)
74+
.cpu()
75+
.numpy()
76+
.astype(np.float32)
77+
).obs["obs"],
78+
dtype=np.float32,
79+
),
80+
)
10581

10682

10783
def resolve_appo_checkpoint_path(
10884
base_log_dir: str | Path,
10985
load_run: str,
11086
) -> tuple[str | None, str | None]:
111-
base_log_dir = str(base_log_dir)
87+
from unilab.training import resolve_checkpoint_path
11288

113-
if load_run == "-1":
114-
if not os.path.exists(base_log_dir):
115-
return None, None
116-
all_runs = sorted(
117-
[d for d in os.listdir(base_log_dir) if os.path.isdir(os.path.join(base_log_dir, d))]
118-
)
119-
if not all_runs:
120-
return None, None
121-
candidate_dir = os.path.join(base_log_dir, all_runs[-1])
122-
elif os.path.isdir(load_run):
123-
candidate_dir = load_run
124-
elif os.path.isfile(load_run):
125-
return load_run, os.path.dirname(load_run)
126-
else:
127-
candidate_dir = os.path.join(base_log_dir, load_run)
128-
if not os.path.isdir(candidate_dir):
129-
return None, None
130-
131-
model_files = [
132-
f for f in os.listdir(candidate_dir) if f.startswith("model_") and f.endswith(".pt")
133-
]
134-
if not model_files:
135-
return None, None
136-
137-
model_files.sort(key=lambda x: int(x.split("_")[1].split(".")[0]))
138-
return os.path.join(candidate_dir, model_files[-1]), candidate_dir
89+
checkpoint_path, checkpoint_dir = resolve_checkpoint_path(base_log_dir, load_run, suffix=".pt")
90+
return (
91+
str(checkpoint_path) if checkpoint_path is not None else None,
92+
str(checkpoint_dir) if checkpoint_dir is not None else None,
93+
)
13994

14095

14196
def _get_log_root(cfg: DictConfig) -> str:
142-
"""Get log root directory from algo_log_name config."""
143-
return str(Path(ROOT_DIR) / "logs" / cfg.algo.algo_log_name)
97+
return str(get_log_root(ROOT_DIR, cfg))
14498

14599

146100
def play_appo(cfg: DictConfig, rl_cfg: dict) -> str | None:
@@ -149,7 +103,6 @@ def play_appo(cfg: DictConfig, rl_cfg: dict) -> str | None:
149103
from rsl_rl.utils import resolve_callable
150104
from tensordict import TensorDict
151105

152-
from unilab.base import registry
153106
from unilab.utils.reward_utils import extract_reward_config
154107
from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4, is_rsl_rl_v5
155108

@@ -164,10 +117,9 @@ def play_appo(cfg: DictConfig, rl_cfg: dict) -> str | None:
164117
)
165118
print(f"Using device for play: {device}")
166119

167-
env = registry.make(
168-
cfg.training.task_name,
120+
env = create_env(
121+
cfg,
169122
num_envs=cfg.training.play_env_num,
170-
sim_backend=cfg.training.sim_backend,
171123
env_cfg_override=env_cfg_override,
172124
)
173125
from unilab.utils.obs_utils import get_obs_dims
@@ -231,47 +183,46 @@ def play_appo(cfg: DictConfig, rl_cfg: dict) -> str | None:
231183
raise
232184
return None
233185

234-
import mediapy as media
235-
236-
from unilab.utils import render_many
237-
238186
output_video = os.path.join(load_path_dir, "play_video.mp4")
239187
print(f"Rendering video to {output_video}...")
240188

241189
if env.state is None:
242190
env.init_state()
243-
env_indices = np.arange(cfg.training.play_env_num, dtype=np.int32)
244-
obs_out, _ = env.reset(env_indices)
245-
obs_np = np.asarray(obs_out["obs"], dtype=np.float32)
246-
247-
state_list = []
248191
num_steps = int(getattr(cfg.training, "play_steps", 1000))
249192

250193
print("Collecting physics states...")
251194
with torch.inference_mode():
252-
for _ in range(num_steps):
253-
obs_torch = torch.from_numpy(obs_np).to(device)
254-
td = TensorDict({"policy": obs_torch}, batch_size=cfg.training.play_env_num)
255-
actions_torch = actor(td)
256-
actions_np = actions_torch.cpu().numpy().astype(np.float32)
257-
state = env.step(actions_np)
258-
obs_np = np.asarray(state.obs["obs"], dtype=np.float32)
259-
state_list.append(np.asarray(env._backend.get_physics_state(), dtype=np.float32).copy())
260-
261-
print("Rendering frames...")
262-
frames = render_many.render_states_get_frames(
263-
state_list,
264-
env.cfg.model_file,
265-
width=1280,
266-
height=720,
267-
camera_id=-1,
268-
cam_distance=cfg.training.cam_distance,
269-
cam_elevation=cfg.training.cam_elevation,
270-
cam_azimuth=cfg.training.cam_azimuth,
271-
)
272-
195+
render_play_mode(
196+
env,
197+
sim_backend=cfg.training.sim_backend,
198+
num_steps=num_steps,
199+
output_video=output_video,
200+
initialize=lambda: np.asarray(
201+
env.reset(np.arange(cfg.training.play_env_num, dtype=np.int32))[0]["obs"],
202+
dtype=np.float32,
203+
),
204+
step=lambda obs_np: np.asarray(
205+
env.step(
206+
actor(
207+
TensorDict(
208+
{"policy": torch.from_numpy(obs_np).to(device)},
209+
batch_size=cfg.training.play_env_num,
210+
)
211+
)
212+
.cpu()
213+
.numpy()
214+
.astype(np.float32)
215+
).obs["obs"],
216+
dtype=np.float32,
217+
),
218+
frame_state_getter=lambda: env._backend.get_physics_state(),
219+
camera_kwargs={
220+
"cam_distance": cfg.training.cam_distance,
221+
"cam_elevation": cfg.training.cam_elevation,
222+
"cam_azimuth": cfg.training.cam_azimuth,
223+
},
224+
)
273225
print(f"Saving video to {output_video} with mediapy...")
274-
media.write_video(str(output_video), frames, fps=int(1.0 / env.cfg.ctrl_dt))
275226
print("Done.")
276227
return output_video
277228

0 commit comments

Comments
 (0)