Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/sphinx/source/zh_CN/5-reference/5-support_matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ uv run scripts/generate_support_matrix.py --write
| APPO (torch) | `g1_climb_tracking` (g1 climb tracking) | Tested | - | Tested |
| SAC (torch) | `g1_walk_flat` (G1 walk flat) | Tested | Tested | Tested |
| SAC (torch) | `g1_walk_rough` (G1 walk rough) | Tested | - | Tested |
| SAC (torch) | `g1_motion_tracking` (G1 motion tracking) | Tested | - | Tested |
| SAC (torch) | `g1_motion_tracking` (G1 motion tracking) | Tested | Configured | Tested |
| SAC (torch) | `g1_flip_tracking` (G1 flip tracking) | Tested | - | Registered |
| SAC (torch) | `g1_wall_flip_tracking` (G1 wall flip tracking) | Tested | - | Registered |
| SAC (torch) | `g1_23dof_flip_tracking` (g1 23dof flip tracking) | Tested | - | Registered |
Expand Down
6 changes: 6 additions & 0 deletions src/unilab/envs/manager_based_rl_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
TerminationManager,
TerminationTermCfg,
)
from unilab.utils.term_profiling import ( # PROFILING_TEMP (#1293, TODO: remove after #1292)
TERM_PROFILER,
)


def _manager_terms_field() -> Any:
Expand Down Expand Up @@ -315,6 +318,9 @@ def unwrapped(self) -> ManagerBasedRlEnv:

def _load_managers(self) -> None:
"""Construct managers in the pinned community dependency order."""
# PROFILING_TEMP (#1293, TODO: remove after #1292): a new env means a new
# benchmark case — dump the previous case's per-term stats and reset.
TERM_PROFILER.reset()
self.event_manager = EventManager(self._cfg.events, self)
self.command_manager = (
CommandManager(self._cfg.commands, self) if self._cfg.commands else NullCommandManager()
Expand Down
13 changes: 10 additions & 3 deletions src/unilab/managers/curriculum_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from prettytable import PrettyTable

from unilab.managers.manager_base import ManagerBase, ManagerTermBaseCfg
from unilab.utils.term_profiling import (
profile_term, # PROFILING_TEMP (#1293, TODO: remove after #1292)
)

if TYPE_CHECKING:
from unilab.managers._types import ManagerBasedRlEnv
Expand Down Expand Up @@ -111,12 +114,16 @@ def reset(self, env_ids: np.ndarray | slice | None = None) -> dict[str, float]:
return extras

def compute(self, env_ids: np.ndarray | slice | None = None) -> None:
# PROFILING_TEMP (#1293, TODO: remove after #1292)
phase = "reset" if env_ids is not None else "step"
if env_ids is None:
env_ids = slice(None)
for name, term_cfg in zip(self._term_names, self._term_cfgs, strict=False):
state = term_cfg.func(self._env, env_ids, **term_cfg.params)
self._validate_state(name, state)
self._curriculum_state[name] = state
# PROFILING_TEMP (#1293, TODO: remove after #1292)
with profile_term(f"curriculum/{name}|{phase}"):
state = term_cfg.func(self._env, env_ids, **term_cfg.params)
self._validate_state(name, state)
self._curriculum_state[name] = state

def _validate_state(self, term_name: str, state: Any) -> None:
values = state.values() if isinstance(state, dict) else (state,)
Expand Down
23 changes: 17 additions & 6 deletions src/unilab/managers/event_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from prettytable import PrettyTable

from unilab.managers.manager_base import ManagerBase, ManagerTermBaseCfg
from unilab.utils.term_profiling import (
profile_term, # PROFILING_TEMP (#1293, TODO: remove after #1292)
)

if TYPE_CHECKING:
from unilab.managers._types import ManagerBasedRlEnv
Expand Down Expand Up @@ -170,6 +173,8 @@ def apply(
raise ValueError(f"Event mode '{mode}' requires the time-step of the environment.")

for index, term_cfg in enumerate(self._mode_term_cfgs[mode]):
# PROFILING_TEMP (#1293, TODO: remove after #1292)
pkey = f"event/{mode}/{self._mode_term_names[mode][index]}"
if mode == "interval":
time_left = self._interval_term_time_left[index]
assert dt is not None
Expand All @@ -180,17 +185,20 @@ def apply(
lower, upper = term_cfg.interval_range_s
sampled_interval = self._env.rng.uniform(lower, upper, 1)
self._interval_term_time_left[index][:] = sampled_interval
term_cfg.func(self._env, None, **term_cfg.params)
with profile_term(pkey): # PROFILING_TEMP (#1293)
term_cfg.func(self._env, None, **term_cfg.params)
else:
valid_env_ids = np.flatnonzero(time_left < 1e-6)
if len(valid_env_ids) > 0:
assert term_cfg.interval_range_s is not None
lower, upper = term_cfg.interval_range_s
sampled_time = self._env.rng.uniform(lower, upper, len(valid_env_ids))
self._interval_term_time_left[index][valid_env_ids] = sampled_time
term_cfg.func(self._env, valid_env_ids, **term_cfg.params)
with profile_term(pkey): # PROFILING_TEMP (#1293)
term_cfg.func(self._env, valid_env_ids, **term_cfg.params)
elif mode == "step":
term_cfg.func(self._env, None, **term_cfg.params)
with profile_term(pkey): # PROFILING_TEMP (#1293)
term_cfg.func(self._env, None, **term_cfg.params)
elif mode == "reset":
assert global_env_step_count is not None
# Reset events require concrete indices: callers (e.g. ManagerBasedRlEnv)
Expand All @@ -203,7 +211,8 @@ def apply(
if min_step_count == 0:
self._reset_term_last_triggered_step_id[index][env_ids] = global_env_step_count
self._reset_term_last_triggered_once[index][env_ids] = True
term_cfg.func(self._env, env_ids, **term_cfg.params)
with profile_term(pkey): # PROFILING_TEMP (#1293)
term_cfg.func(self._env, env_ids, **term_cfg.params)
else:
last_triggered_step = self._reset_term_last_triggered_step_id[index][env_ids]
triggered_at_least_once = self._reset_term_last_triggered_once[index][env_ids]
Expand All @@ -219,9 +228,11 @@ def apply(
self._reset_term_last_triggered_step_id[index][valid_env_ids] = (
global_env_step_count
)
term_cfg.func(self._env, valid_env_ids, **term_cfg.params)
with profile_term(pkey): # PROFILING_TEMP (#1293)
term_cfg.func(self._env, valid_env_ids, **term_cfg.params)
else:
term_cfg.func(self._env, env_ids, **term_cfg.params)
with profile_term(pkey): # PROFILING_TEMP (#1293)
term_cfg.func(self._env, env_ids, **term_cfg.params)

def _prepare_terms(self) -> None:
self._interval_term_time_left: list[np.ndarray] = list()
Expand Down
11 changes: 8 additions & 3 deletions src/unilab/managers/metrics_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from prettytable import PrettyTable

from unilab.managers.manager_base import ManagerBase, ManagerTermBaseCfg
from unilab.utils.term_profiling import (
profile_term, # PROFILING_TEMP (#1293, TODO: remove after #1292)
)

if TYPE_CHECKING:
from unilab.managers._types import ManagerBasedRlEnv
Expand Down Expand Up @@ -210,9 +213,11 @@ def _prepare_terms(self) -> None:
def _compute_term(self, idx: int) -> np.ndarray:
name = self._term_names[idx]
term_cfg = self._term_cfgs[idx]
value = term_cfg.func(self._env, **term_cfg.params)
self._check_term_shape(name, value)
self._check_term_finite(name, value)
# PROFILING_TEMP (#1293, TODO: remove after #1292)
with profile_term(f"metrics/{name}"):
value = term_cfg.func(self._env, **term_cfg.params)
self._check_term_shape(name, value)
self._check_term_finite(name, value)
return value


Expand Down
127 changes: 70 additions & 57 deletions src/unilab/managers/observation_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
from unilab.managers._noise import noise_cfg, noise_model
from unilab.managers._noise.noise_cfg import NoiseCfg, NoiseModelCfg
from unilab.managers.manager_base import ManagerBase, ManagerTermBaseCfg
from unilab.utils.term_profiling import (
profile_term, # PROFILING_TEMP (#1293, TODO: remove after #1292)
)

if TYPE_CHECKING:
from unilab.managers._types import ManagerBasedRlEnv
Expand Down Expand Up @@ -388,8 +391,12 @@ def compute_group(
# (num_envs, ...) and full-shape noise draws keep the shared RNG stream
# and per-row noise values identical to the full-batch path.
row_scoped = env_ids is not None and not self._group_obs_temporal[group_name]
# PROFILING_TEMP (#1293, TODO: remove after #1292)
phase = "reset" if env_ids is not None else "step"
for term_name, term_cfg in obs_terms:
obs = term_cfg.func(self._env, **term_cfg.params)
# PROFILING_TEMP (#1293, TODO: remove after #1292)
with profile_term(f"obs/{group_name}/{term_name}|{phase}"):
obs = term_cfg.func(self._env, **term_cfg.params)
if not isinstance(obs, np.ndarray):
raise TypeError(
f"ObservationManager term '{group_name}/{term_name}' returned "
Expand All @@ -400,30 +407,33 @@ def compute_group(
f"ObservationManager term '{group_name}/{term_name}' returned shape "
f"{obs.shape}, expected (num_envs, ...) with num_envs={self.num_envs}."
)
if not row_scoped:
obs = obs.copy()
if isinstance(term_cfg.noise, noise_cfg.NoiseCfg):
obs = term_cfg.noise.apply(obs, rng=self._env.rng)
elif isinstance(term_cfg.noise, noise_cfg.NoiseModelCfg):
obs = self._group_obs_class_instances[group_name][term_name](obs)
if row_scoped:
# Fresh row copy; safe for the in-place clip/scale below.
obs = obs[env_ids]
if term_cfg.clip:
np.clip(obs, term_cfg.clip[0], term_cfg.clip[1], out=obs)
if term_cfg.scale is not None:
scale = term_cfg.scale
assert isinstance(scale, np.ndarray)
np.multiply(obs, scale, out=obs)

# Check for NaN/Inf before delay/history buffers (per-term checking).
if group_cfg.nan_check_per_term and group_cfg.nan_policy != "disabled":
obs = self._check_and_handle_nans(
obs,
context=f"{group_name}/{term_name}",
policy=group_cfg.nan_policy,
env_ids=env_ids if row_scoped else None,
)
# PROFILING_TEMP (#1293, TODO: remove after #1292): manager-level
# per-term post-processing (copy/noise/clip/scale/nan check).
with profile_term(f"obs_post/{group_name}/{term_name}|{phase}"):
if not row_scoped:
obs = obs.copy()
if isinstance(term_cfg.noise, noise_cfg.NoiseCfg):
obs = term_cfg.noise.apply(obs, rng=self._env.rng)
elif isinstance(term_cfg.noise, noise_cfg.NoiseModelCfg):
obs = self._group_obs_class_instances[group_name][term_name](obs)
if row_scoped:
# Fresh row copy; safe for the in-place clip/scale below.
obs = obs[env_ids]
if term_cfg.clip:
np.clip(obs, term_cfg.clip[0], term_cfg.clip[1], out=obs)
if term_cfg.scale is not None:
scale = term_cfg.scale
assert isinstance(scale, np.ndarray)
np.multiply(obs, scale, out=obs)

# Check for NaN/Inf before delay/history buffers (per-term checking).
if group_cfg.nan_check_per_term and group_cfg.nan_policy != "disabled":
obs = self._check_and_handle_nans(
obs,
context=f"{group_name}/{term_name}",
policy=group_cfg.nan_policy,
env_ids=env_ids if row_scoped else None,
)

if term_cfg.delay_max_lag > 0:
delay_buffer = self._group_obs_term_delay_buffer[group_name][term_name]
Expand All @@ -448,43 +458,46 @@ def compute_group(
else:
group_obs[term_name] = obs

# Final NaN check for non-per-term checking.
if not group_cfg.nan_check_per_term and group_cfg.nan_policy != "disabled":
# PROFILING_TEMP (#1293, TODO: remove after #1292): group-level
# post-processing (group nan check / concatenate / reset row slice).
with profile_term(f"obs_group_post/{group_name}|{phase}"):
# Final NaN check for non-per-term checking.
if not group_cfg.nan_check_per_term and group_cfg.nan_policy != "disabled":
if self._group_obs_concatenate[group_name]:
# Will check after concatenation below.
pass
else:
for term_name in group_obs:
group_obs[term_name] = self._check_and_handle_nans(
group_obs[term_name],
context=f"{group_name}/{term_name}",
policy=group_cfg.nan_policy,
env_ids=env_ids if row_scoped else None,
)

if self._group_obs_concatenate[group_name]:
# Will check after concatenation below.
pass
else:
for term_name in group_obs:
group_obs[term_name] = self._check_and_handle_nans(
group_obs[term_name],
context=f"{group_name}/{term_name}",
result = np.concatenate(
list(group_obs.values()), axis=self._group_obs_concatenate_dim[group_name]
)
# Final check for concatenated result (non-per-term checking).
if not group_cfg.nan_check_per_term and group_cfg.nan_policy != "disabled":
result = self._check_and_handle_nans(
result,
context=group_name,
policy=group_cfg.nan_policy,
env_ids=env_ids if row_scoped else None,
)

if self._group_obs_concatenate[group_name]:
result = np.concatenate(
list(group_obs.values()), axis=self._group_obs_concatenate_dim[group_name]
)
# Final check for concatenated result (non-per-term checking).
if not group_cfg.nan_check_per_term and group_cfg.nan_policy != "disabled":
result = self._check_and_handle_nans(
result,
context=group_name,
policy=group_cfg.nan_policy,
env_ids=env_ids if row_scoped else None,
)
else:
result = group_obs

if env_ids is not None and not row_scoped:
# Groups with delay/history terms ran the full-batch pipeline above
# (buffer readout stays full-batch); slice the reset rows to match
# the reset-path return contract.
if isinstance(result, dict):
result = {name: values[env_ids] for name, values in result.items()}
else:
result = result[env_ids]
result = group_obs

if env_ids is not None and not row_scoped:
# Groups with delay/history terms ran the full-batch pipeline above
# (buffer readout stays full-batch); slice the reset rows to match
# the reset-path return contract.
if isinstance(result, dict):
result = {name: values[env_ids] for name, values in result.items()}
else:
result = result[env_ids]

return result

Expand Down
19 changes: 12 additions & 7 deletions src/unilab/managers/reward_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from prettytable import PrettyTable

from unilab.managers.manager_base import ManagerBase, ManagerTermBaseCfg
from unilab.utils.term_profiling import (
profile_term, # PROFILING_TEMP (#1293, TODO: remove after #1292)
)

if TYPE_CHECKING:
from unilab.managers._types import DebugVisualizer, ManagerBasedRlEnv
Expand Down Expand Up @@ -123,13 +126,15 @@ def compute(self, dt: float) -> np.ndarray:
if term_cfg.weight == 0.0:
self._step_reward[:, term_idx] = 0.0
continue
value = term_cfg.func(self._env, **term_cfg.params)
self._check_term_shape(name, value)
self._check_term_finite(name, value)
value = value * term_cfg.weight * scale
self._reward_buf += value
self._episode_sums[name] += value
self._step_reward[:, term_idx] = value / scale
# PROFILING_TEMP (#1293, TODO: remove after #1292)
with profile_term(f"reward/{name}"):
value = term_cfg.func(self._env, **term_cfg.params)
self._check_term_shape(name, value)
self._check_term_finite(name, value)
value = value * term_cfg.weight * scale
self._reward_buf += value
self._episode_sums[name] += value
self._step_reward[:, term_idx] = value / scale
return self._reward_buf

def step_reward_extras(self) -> dict[str, float]:
Expand Down
27 changes: 16 additions & 11 deletions src/unilab/managers/termination_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from prettytable import PrettyTable

from unilab.managers.manager_base import ManagerBase, ManagerTermBaseCfg
from unilab.utils.term_profiling import (
profile_term, # PROFILING_TEMP (#1293, TODO: remove after #1292)
)

if TYPE_CHECKING:
from unilab.managers._types import ManagerBasedRlEnv
Expand Down Expand Up @@ -100,17 +103,19 @@ def compute(self) -> np.ndarray:
self._truncated_buf[:] = False
self._terminated_buf[:] = False
for name, term_cfg in zip(self._term_names, self._term_cfgs, strict=False):
value = term_cfg.func(self._env, **term_cfg.params)
self._check_term_shape(name, value)
if value.dtype != np.bool_:
raise TypeError(
f"TerminationManager term '{name}' returned dtype {value.dtype}, expected bool."
)
if term_cfg.time_out:
self._truncated_buf |= value
else:
self._terminated_buf |= value
self._term_dones[name][:] = value
# PROFILING_TEMP (#1293, TODO: remove after #1292)
with profile_term(f"termination/{name}"):
value = term_cfg.func(self._env, **term_cfg.params)
self._check_term_shape(name, value)
if value.dtype != np.bool_:
raise TypeError(
f"TerminationManager term '{name}' returned dtype {value.dtype}, expected bool."
)
if term_cfg.time_out:
self._truncated_buf |= value
else:
self._terminated_buf |= value
self._term_dones[name][:] = value
return self._truncated_buf | self._terminated_buf

def get_term(self, name: str) -> np.ndarray:
Expand Down
Loading