Source code for cloudai.report_generator.training.models

# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Data models for training parsers."""

import statistics
from collections.abc import Hashable
from dataclasses import MISSING, dataclass, fields
from typing import Any, List, Optional

SCHEMA_VERSION = "1.0"  # training report schema; bump on breaking changes to TrainingConfig/TrainingResults


[docs] @dataclass class MetricStats: """Aggregated statistics for one metric over the filtered steps.""" mean: float min: float max: float std: float t99: float t95: float @classmethod def from_values(cls, values: list[float]) -> "MetricStats": """Build stats from a non-empty list of values (population std; inclusive percentiles).""" return cls( mean=statistics.mean(values), min=min(values), max=max(values), std=statistics.pstdev(values), t99=cls._percentile(values, 99), t95=cls._percentile(values, 95), ) @staticmethod def _percentile(values: list[float], p: int) -> float: """Inclusive, linearly-interpolated p-th percentile; returns the sole value for a single sample.""" if len(values) == 1: return float(values[0]) return statistics.quantiles(values, n=100, method="inclusive")[p - 1]
@dataclass(frozen=True) class Scalar: """A single scalar event from a training run (source-agnostic: TensorBoard today, others later).""" tag: str step: int value: float wall_time: float @classmethod def from_record(cls, record: dict[Hashable, Any]) -> "Scalar": """Build from a {column: value} record (e.g. a tbparse DataFrame row).""" return cls(tag=record["tag"], step=record["step"], value=record["value"], wall_time=record["wall_time"])
[docs] @dataclass(kw_only=True) class TrainingStep: """Results for a single training iteration.""" iteration: int step_time_sec: float loss: float memory_reserved_bytes: float memory_allocated_bytes: float tflops_per_gpu: Optional[float] = None # NeMo FLOPs could be missing for some models
OPTIONAL_STEP_FIELDS = {f.name for f in fields(TrainingStep) if f.default is not MISSING}
[docs] @dataclass(kw_only=True) class StepAggregation: """Per-metric aggregated statistics over the filtered steps.""" step_time_sec: MetricStats loss: MetricStats memory_reserved_bytes: MetricStats memory_allocated_bytes: MetricStats tflops_per_gpu: Optional[MetricStats] = None @classmethod def from_steps(cls, steps: list["TrainingStep"]) -> "StepAggregation": """Build per-metric stats from a non-empty list of already-filtered steps.""" tflops = [s.tflops_per_gpu for s in steps if s.tflops_per_gpu is not None] return cls( step_time_sec=MetricStats.from_values([s.step_time_sec for s in steps]), loss=MetricStats.from_values([s.loss for s in steps]), memory_reserved_bytes=MetricStats.from_values([s.memory_reserved_bytes for s in steps]), memory_allocated_bytes=MetricStats.from_values([s.memory_allocated_bytes for s in steps]), tflops_per_gpu=MetricStats.from_values(tflops) if tflops else None, )
[docs] @dataclass(kw_only=True) class TrainingConfig: """ Resolved training configuration from the framework artifact + CloudAI. CloudAI-computed fields are supplied by the parser during construction. """ # Test identity test_id: str # scenario section id test_name: str # test definition name description: str test_scenario_name: str test_template_name: str # Configuration sources system_path: str tests_dir_path: str test_scenario_path: str # Environment container_image: str = "" cloudai_execution_node: str env_vars: dict[str, Any] # system global env + test extra env # Hardware # Depends on: num_nodes, gpus_per_node world_size: Optional[int] = None # Populated from TestRun.nnodes. num_nodes: int gpus_per_node: Optional[int] = None nodes: list[str] # compressed nodelist from the scenario # Depends on: env_vars["CLIQUE_SIZE"] clique_size: Optional[int] = None # Precision fp8: Optional[str] = None # Depends on: fp8 fp8_recipe: Optional[str] = None # Batch micro_batch_size: int global_batch_size: int seq_length: int # Parallelism tensor_parallel_size: int pipeline_parallel_size: int context_parallel_size: Optional[int] virtual_pipeline_parallel_size: Optional[int] sequence_parallel: bool expert_parallel_size: int expert_tensor_parallel_size: int # Depends on: world_size / (tensor_parallel_size * pipeline_parallel_size * context_parallel_size) data_parallel_size: Optional[int] = None # Model architecture num_layers: int hidden_size: int num_attention_heads: int num_query_groups: int ffn_hidden_size: int kv_channels: int normalization: str position_embedding_type: str model_name: str = "" # CloudAI-computed # MoE num_experts: Optional[int] moe_router_topk: Optional[int] moe_ffn_hidden_size: Optional[int] moe_grouped_gemm: Optional[bool] # Profiling profiling_enabled: bool = False # Depends on: profiling_enabled profiling_start_step: Optional[int] = None # Depends on: profiling_enabled profiling_stop_step: Optional[int] = None # Aggregation window (steps dropped before computing the top-level aggregation) exclude_start_steps: int = 5 # Depends on: profiling_enabled, profiling_stop_step exclude_post_profiling_steps: int = 2
[docs] @dataclass(kw_only=True) class TrainingResults: """Container for parsed training output.""" schema_version: str = SCHEMA_VERSION config: TrainingConfig steps: List[TrainingStep] aggregation: Optional[StepAggregation] = None # None when no steps remain after exclusions