[Task Clean-up][Benchmark] Dexterous Part 5/11: Add success-rate support to the benchmark utilities#6415
Conversation
The environment training benchmark utilities now record the Metrics/success_rate signal emitted by the dexterous tasks alongside reward and episode length, and benchmark discovery excludes inference-only camera benchmark registrations from training runs.
| def _extract_sustained_feature(log_data, feature, uses_lower_threshold, consecutive_samples): | ||
| """Extract the best threshold-facing value sustained over a sample window.""" | ||
| values = np.asarray(log_data[feature], dtype=float)[:, 1] | ||
| if len(values) < consecutive_samples: | ||
| return None | ||
| if not np.all(np.isfinite(values)): | ||
| return math.nan |
There was a problem hiding this comment.
Global NaN check rejects series with any early non-finite value
np.all(np.isfinite(values)) is applied to the full historical series before any windowing, so a single NaN anywhere — including in early warm-up steps before the metric stabilises — causes the function to return math.nan and fail the threshold even when a valid consecutive_samples-length window exists entirely within the finite portion of the data. For success_rate in particular, early episodes may not record any successes and could produce NaN values before converging; those early samples would silently disqualify an otherwise passing run. Restricting the finiteness check to each candidate window (or filtering non-finite entries before slicing) would make the "best sustained window" semantics consistent with the function's name.
| @@ -230,7 +271,7 @@ def _extract_log_val(name, log_data, uses_lower_threshold, workflow): | |||
| "skrl": "Reward / Total reward (mean)", | |||
| } | |||
| tag = reward_tags.get(workflow) | |||
| if tag: | |||
| if tag and consecutive_samples is None: | |||
| return _extract_reward(log_data, tag) | |||
|
|
|||
| elif name == "episode_length": | |||
| @@ -241,8 +282,20 @@ def _extract_log_val(name, log_data, uses_lower_threshold, workflow): | |||
| "skrl": "Episode / Total timesteps (mean)", | |||
| } | |||
| tag = episode_tags.get(workflow) | |||
| if tag: | |||
| return _extract_feature(log_data, tag, uses_lower_threshold) | |||
| elif name == "success_rate": | |||
| success_rate_tags = { | |||
| "rl_games": "Episode/Metrics/success_rate", | |||
| "rsl_rl": "Metrics/success_rate", | |||
| "skrl": "Metrics/success_rate", | |||
| } | |||
| tag = success_rate_tags.get(workflow) | |||
|
|
|||
| if tag: | |||
| if consecutive_samples is not None: | |||
| return _extract_sustained_feature(log_data, tag, uses_lower_threshold, consecutive_samples) | |||
| return _extract_feature(log_data, tag, uses_lower_threshold) | |||
There was a problem hiding this comment.
reward bypasses _extract_reward when consecutive_samples is set
When name == "reward" and consecutive_samples is not None, the early return _extract_reward(...) branch is skipped (lines 274–275) and the code falls through to the generic if tag: block, where _extract_sustained_feature is called instead. _extract_reward uses an "average of the top-k" aggregation that is quite different from the sliding-window min/max in _extract_sustained_feature. Any future YAML config that specifies a structured threshold for the reward metric would silently receive a semantically different aggregation than the scalar-threshold path. There are no tests covering this combined path, and the behaviour change is not documented.
Summary
Metrics/success_ratesignal emitted by the dexterous tasks alongside reward and episode length, with unit tests.Dependencies
Metrics/success_ratekey it consumes is introduced by [Task Clean-up] Dexterous Part 3/11: Add success-rate metrics to the reorientation Direct tasks #6413 / [Task Clean-up] Dexterous Part 4/11: Enable RSL-RL training for the handover Direct task #6414; recording is a no-op for tasks that do not emit it.)