From 25fc6fefba793733b1d8f7bc7ba70de95a29c624 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Apr 2026 22:24:38 -0700 Subject: [PATCH 1/3] Add zero_std/all_zero_ratio and zero_std/all_one_ratio metrics The existing `zero_std/count_{reward}` metrics report the raw number of GRPO groups where every sample shares the same reward (and therefore contributes zero gradient under group-normalized advantages). Comparing these counts across runs with different rollout batch sizes, or understanding them without first looking up `rollout_batch_size` / `n_samples_per_prompt`, is awkward. Add two derived ratios over the total number of groups in the rollout: - `zero_std/all_zero_ratio` - fraction of groups where every sample scored 0.0 (binary reward: "too hard", no attempt succeeded). - `zero_std/all_one_ratio` - fraction of groups where every sample scored 1.0 ("too easy", every attempt succeeded). Both are meaningful only for binary rewards; for non-binary reward schemes the individual `count_{reward}` buckets remain available. --- miles/ray/rollout.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/miles/ray/rollout.py b/miles/ray/rollout.py index 1aa2e91fe6c..8ef7fd63696 100644 --- a/miles/ray/rollout.py +++ b/miles/ray/rollout.py @@ -1281,7 +1281,18 @@ def _is_zero_std(samples: list[Sample]): interesting_rewards = [str(round(g[0].get_reward_value(args), 1)) for g in interesting_sample_groups] - return {f"zero_std/count_{reward}": len(items) for reward, items in group_by(interesting_rewards).items()} + counts = {reward: len(items) for reward, items in group_by(interesting_rewards).items()} + log_dict = {f"zero_std/count_{reward}": count for reward, count in counts.items()} + + # Ratios over total groups, so "too hard" (all-0) and "too easy" (all-1) + # rates are comparable across runs without needing to know the rollout + # batch size. + total_groups = len(all_sample_groups) + if total_groups > 0: + log_dict["zero_std/all_zero_ratio"] = counts.get("0.0", 0) / total_groups + log_dict["zero_std/all_one_ratio"] = counts.get("1.0", 0) / total_groups + + return log_dict def _compute_spec_metrics(args, all_samples: list[Sample]): From b080eb0dc69bea89d119e4cc48c467c77f76b860 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Apr 2026 22:27:18 -0700 Subject: [PATCH 2/3] Rename zero_std ratios to percentages and scale by 100 Per review: the metric names are more intuitive as "percentage" than "ratio", so rename and multiply by 100 so the values match the name (0-100 instead of 0-1). - zero_std/all_zero_ratio -> zero_std/all_zero_percentage - zero_std/all_one_ratio -> zero_std/all_one_percentage --- miles/ray/rollout.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/miles/ray/rollout.py b/miles/ray/rollout.py index 8ef7fd63696..8b9e0340450 100644 --- a/miles/ray/rollout.py +++ b/miles/ray/rollout.py @@ -1284,13 +1284,13 @@ def _is_zero_std(samples: list[Sample]): counts = {reward: len(items) for reward, items in group_by(interesting_rewards).items()} log_dict = {f"zero_std/count_{reward}": count for reward, count in counts.items()} - # Ratios over total groups, so "too hard" (all-0) and "too easy" (all-1) - # rates are comparable across runs without needing to know the rollout - # batch size. + # Percentages over total groups, so "too hard" (all-0) and "too easy" + # (all-1) rates are comparable across runs without needing to know the + # rollout batch size. total_groups = len(all_sample_groups) if total_groups > 0: - log_dict["zero_std/all_zero_ratio"] = counts.get("0.0", 0) / total_groups - log_dict["zero_std/all_one_ratio"] = counts.get("1.0", 0) / total_groups + log_dict["zero_std/all_zero_percentage"] = 100 * counts.get("0.0", 0) / total_groups + log_dict["zero_std/all_one_percentage"] = 100 * counts.get("1.0", 0) / total_groups return log_dict From 13e86da1d8367f48c5ecbf5a0b9498d72d653cc8 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Apr 2026 22:29:11 -0700 Subject: [PATCH 3/3] Keep zero_std percentage values on 0.0-1.0 scale --- miles/ray/rollout.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miles/ray/rollout.py b/miles/ray/rollout.py index 8b9e0340450..ded71b511ea 100644 --- a/miles/ray/rollout.py +++ b/miles/ray/rollout.py @@ -1289,8 +1289,8 @@ def _is_zero_std(samples: list[Sample]): # rollout batch size. total_groups = len(all_sample_groups) if total_groups > 0: - log_dict["zero_std/all_zero_percentage"] = 100 * counts.get("0.0", 0) / total_groups - log_dict["zero_std/all_one_percentage"] = 100 * counts.get("1.0", 0) / total_groups + log_dict["zero_std/all_zero_percentage"] = counts.get("0.0", 0) / total_groups + log_dict["zero_std/all_one_percentage"] = counts.get("1.0", 0) / total_groups return log_dict