2525from nemo_rl .distributed .batched_data_dict import BatchedDataDict
2626from nemo_rl .environments .interfaces import EnvironmentInterface
2727from nemo_rl .experience .interfaces import Completion , PromptGroupRecord
28- from nemo_rl .experience .rollouts import (
29- _calculate_single_metric ,
30- _tensorize_by_key ,
31- calculate_rewards ,
32- )
28+ from nemo_rl .experience .metric_utils import calculate_single_metric , pct
29+ from nemo_rl .experience .rollouts import _tensorize_by_key , calculate_rewards
3330from nemo_rl .models .generation .interfaces import (
3431 GenerationConfig ,
3532 GenerationDatumSpec ,
@@ -337,17 +334,29 @@ def _aggregate_rollout_metrics(
337334 terminated = [m ["terminated" ] for m in all_sample_metrics ]
338335 max_turns_reached = [m ["max_turns_reached" ] for m in all_sample_metrics ]
339336
337+ # max_gen_tokens_per_turn: Diagnostic for long single generations
338+ max_gen_tokens_per_turn = [
339+ max (m ["turn_gen_tokens" ]) if m ["turn_gen_tokens" ] else 0
340+ for m in all_sample_metrics
341+ ]
342+
340343 # Aggregate metrics across all samples.
341344 n = len (all_sample_metrics )
342345 rollout_metrics : dict [str , Any ] = {
343- ** _calculate_single_metric (total_reward , n , "total_reward" ),
346+ ** calculate_single_metric (total_reward , n , "total_reward" ),
344347 # turn metrics
345348 "total_turns" : sum (turn_count ),
346- ** _calculate_single_metric (turn_count , n , "turns_per_sample" ),
349+ ** calculate_single_metric (turn_count , n , "turns_per_sample" ),
350+ "turns_per_sample/p95" : pct (turn_count , 95 ),
351+ "turns_per_sample/p99" : pct (turn_count , 99 ),
347352 # token metrics
348- ** _calculate_single_metric (total_tokens , n , "total_tokens_per_sample" ),
349- ** _calculate_single_metric (assistant_tokens , n , "gen_tokens_per_sample" ),
350- ** _calculate_single_metric (env_tokens , n , "env_tokens_per_sample" ),
353+ ** calculate_single_metric (total_tokens , n , "total_tokens_per_sample" ),
354+ ** calculate_single_metric (assistant_tokens , n , "gen_tokens_per_sample" ),
355+ ** calculate_single_metric (env_tokens , n , "env_tokens_per_sample" ),
356+ # max_gen_tokens_per_turn: Diagnostic for long single generations
357+ "max_gen_tokens_per_turn/max" : max (max_gen_tokens_per_turn ),
358+ "max_gen_tokens_per_turn/mean" : sum (max_gen_tokens_per_turn ) / n ,
359+ "max_gen_tokens_per_turn/p95" : pct (max_gen_tokens_per_turn , 95 ),
351360 # truncated metrics
352361 "truncation_rate" : sum (truncated ) / n ,
353362 "natural_termination_rate" : sum (terminated ) / n ,
@@ -362,7 +371,7 @@ def _aggregate_rollout_metrics(
362371 rollout_metrics ["per_worker_token_counts" ] = per_worker_token_counts
363372
364373 # Per-turn token histograms (flat across all turns, distinct from the
365- # per-sample histograms emitted via _calculate_single_metric above).
374+ # per-sample histograms emitted via calculate_single_metric above).
366375 rollout_metrics ["histogram/gen_tokens_length" ] = [
367376 t for m in all_sample_metrics for t in m ["turn_gen_tokens" ]
368377 ]
@@ -542,18 +551,36 @@ def _compute_rollout_metrics(
542551 sum (len (m ["token_ids" ]) for m in c .message_log if m ["role" ] == "assistant" )
543552 for c in completions
544553 ]
554+ # max_gen_tokens_per_turn: Diagnostic for long single generations
555+ max_gen_tokens_per_turn = [
556+ max (
557+ (
558+ len (m ["token_ids" ])
559+ for m in c .message_log
560+ if m ["role" ] == "assistant"
561+ ),
562+ default = 0 ,
563+ )
564+ for c in completions
565+ ]
545566 # truncated metrics
546567 truncated = [c .truncated for c in completions ]
547568
548569 # Aggregate metrics across all samples.
549570 n = len (completions )
550571 rollout_metrics : dict [str , Any ] = {
551- ** _calculate_single_metric (total_reward , n , "total_reward" ),
572+ ** calculate_single_metric (total_reward , n , "total_reward" ),
552573 # turn metrics
553- ** _calculate_single_metric (turn_count , n , "turns_per_sample" ),
574+ ** calculate_single_metric (turn_count , n , "turns_per_sample" ),
575+ "turns_per_sample/p95" : pct (turn_count , 95 ),
576+ "turns_per_sample/p99" : pct (turn_count , 99 ),
554577 # token metrics
555- ** _calculate_single_metric (total_tokens , n , "total_tokens_per_sample" ),
556- ** _calculate_single_metric (assistant_tokens , n , "gen_tokens_per_sample" ),
578+ ** calculate_single_metric (total_tokens , n , "total_tokens_per_sample" ),
579+ ** calculate_single_metric (assistant_tokens , n , "gen_tokens_per_sample" ),
580+ ** calculate_single_metric (
581+ max_gen_tokens_per_turn , n , "max_gen_tokens_per_turn"
582+ ),
583+ "max_gen_tokens_per_turn/p95" : pct (max_gen_tokens_per_turn , 95 ),
557584 # truncated metrics
558585 "natural_termination_rate" : sum (not t for t in truncated ) / n ,
559586 "truncation_rate" : sum (truncated ) / n ,
@@ -569,7 +596,7 @@ def _compute_rollout_metrics(
569596 ]
570597 if values :
571598 rollout_metrics .update (
572- _calculate_single_metric (values , n , f"{ agent_name } /{ key } " )
599+ calculate_single_metric (values , n , f"{ agent_name } /{ key } " )
573600 )
574601 rollout_metrics [f"{ agent_name } /full_result" ] = Table (
575602 data = [[json .dumps (r , separators = ("," , ":" ))] for r in agent_extras ],
0 commit comments