1616
1717"""Data models for training parsers."""
1818
19+ import statistics
1920from collections .abc import Hashable
2021from dataclasses import MISSING , dataclass , fields
2122from typing import Any , List , Optional
2223
2324
25+ @dataclass
26+ class MetricStats :
27+ """Aggregated statistics for one metric over the filtered steps."""
28+
29+ mean : float
30+ min : float
31+ max : float
32+ std : float
33+ t99 : float
34+ t95 : float
35+
36+ @classmethod
37+ def from_values (cls , values : list [float ]) -> "MetricStats" :
38+ """Build stats from a non-empty list of values (population std; inclusive percentiles)."""
39+ return cls (
40+ mean = statistics .mean (values ),
41+ min = min (values ),
42+ max = max (values ),
43+ std = statistics .pstdev (values ),
44+ t99 = cls ._percentile (values , 99 ),
45+ t95 = cls ._percentile (values , 95 ),
46+ )
47+
48+ @staticmethod
49+ def _percentile (values : list [float ], p : int ) -> float :
50+ """Inclusive, linearly-interpolated p-th percentile; returns the sole value for a single sample."""
51+ if len (values ) == 1 :
52+ return float (values [0 ])
53+ return statistics .quantiles (values , n = 100 , method = "inclusive" )[p - 1 ]
54+
55+
2456@dataclass (frozen = True )
2557class Scalar :
2658 """A single scalar event from a training run (source-agnostic: TensorBoard today, others later)."""
@@ -51,6 +83,29 @@ class TrainingStep:
5183OPTIONAL_STEP_FIELDS = {f .name for f in fields (TrainingStep ) if f .default is not MISSING }
5284
5385
86+ @dataclass (kw_only = True )
87+ class StepAggregation :
88+ """Per-metric aggregated statistics over the filtered steps."""
89+
90+ step_time_sec : MetricStats
91+ loss : MetricStats
92+ memory_reserved_bytes : MetricStats
93+ memory_allocated_bytes : MetricStats
94+ tflops_per_gpu : Optional [MetricStats ] = None
95+
96+ @classmethod
97+ def from_steps (cls , steps : list ["TrainingStep" ]) -> "StepAggregation" :
98+ """Build per-metric stats from a non-empty list of already-filtered steps."""
99+ tflops = [s .tflops_per_gpu for s in steps if s .tflops_per_gpu is not None ]
100+ return cls (
101+ step_time_sec = MetricStats .from_values ([s .step_time_sec for s in steps ]),
102+ loss = MetricStats .from_values ([s .loss for s in steps ]),
103+ memory_reserved_bytes = MetricStats .from_values ([s .memory_reserved_bytes for s in steps ]),
104+ memory_allocated_bytes = MetricStats .from_values ([s .memory_allocated_bytes for s in steps ]),
105+ tflops_per_gpu = MetricStats .from_values (tflops ) if tflops else None ,
106+ )
107+
108+
54109@dataclass (kw_only = True )
55110class TrainingConfig :
56111 """
@@ -95,6 +150,15 @@ class TrainingConfig:
95150 world_size : Optional [int ] = None # CloudAI-computed (None when gpus_per_node is unavailable)
96151 num_nodes : int = 0 # CloudAI-computed
97152
153+ # Profiling (CloudAI-computed from the run's nsys/profiler settings)
154+ profiling_enabled : bool = False
155+ profiling_start_step : Optional [int ] = None
156+ profiling_stop_step : Optional [int ] = None
157+
158+ # Aggregation window (steps dropped before computing the top-level aggregation)
159+ exclude_start_steps : int = 5
160+ exclude_post_profiling_steps : int = 2
161+
98162 # Identity
99163 test_template_name : str = "" # CloudAI-computed
100164
@@ -105,3 +169,4 @@ class TrainingResults:
105169
106170 config : TrainingConfig
107171 steps : List [TrainingStep ]
172+ aggregation : Optional [StepAggregation ] = None # None when no steps remain after exclusions
0 commit comments