Skip to content

Commit ab02db2

Browse files
committed
fix(training_report): improve can_parse validation and logs
Reject missing or invalid training artifacts before parsing and log why report generation is skipped. Signed-off-by: Ben Lugassi <blugassi@nvidia.com>
1 parent 81fc9af commit ab02db2

2 files changed

Lines changed: 68 additions & 4 deletions

File tree

src/cloudai/report_generator/training/parser.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,34 @@ def get_model_config(self, tr: TestRun) -> dict:
7272
def get_model_name(self, tr: TestRun) -> str:
7373
"""CloudAI-sourced model identity for the run."""
7474

75+
@staticmethod
76+
def _has_tb_event_files(tb_dir: Path) -> bool:
77+
# TensorBoard documents event files as having "tfevents" in the filename.
78+
return any(path.is_file() for path in tb_dir.rglob("*tfevents*"))
79+
7580
def can_parse(self, tr: TestRun) -> bool:
7681
"""Return True when the run produced the TB events and config artifact this parser needs."""
82+
name = type(self).__name__
7783
tb_dir = self.get_tb_dir(tr)
78-
if not (tb_dir.is_dir() and any(tb_dir.iterdir())):
84+
if not (tb_dir.is_dir() and self._has_tb_event_files(tb_dir)):
85+
logging.warning(f"{name}: no TensorBoard events at '{tb_dir}'; skipping training report")
7986
return False
8087
config_path = self.get_config_path(tr)
81-
return config_path is not None and config_path.is_file()
88+
if config_path is None or not config_path.is_file():
89+
logging.warning(f"{name}: config artifact not found under '{tr.output_path}'; skipping training report")
90+
return False
91+
if config_path.suffix.lower() in {".json", ".yaml", ".yml"}:
92+
try:
93+
config = self.get_model_config(tr)
94+
except (json.JSONDecodeError, yaml.YAMLError) as exc:
95+
logging.warning(f"{name}: invalid config artifact at '{config_path}' ({exc}); skipping training report")
96+
return False
97+
if not isinstance(config, dict) or not config:
98+
logging.warning(
99+
f"{name}: empty or invalid config artifact at '{config_path}'; skipping training report"
100+
)
101+
return False
102+
return True
82103

83104
def parse(self, tr: TestRun, system: System) -> TrainingResults:
84105
"""Read TB scalars + the config artifact and assemble TrainingResults."""

tests/report_generator/training/test_training_parser.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,56 @@ def test_can_parse_requires_nonempty_tb_dir(tmp_path):
472472

473473
tb_dir = tmp_path / "tb_logs" / "default"
474474
tb_dir.mkdir(parents=True)
475-
(tb_dir / "events.out.tfevents").write_text("x")
475+
(tb_dir / "placeholder.txt").write_text("x")
476+
assert parser.can_parse(_tr(output_path=tmp_path)) is False # non-empty dir without event files
477+
478+
(tb_dir / "custom.tfevents.log").write_text("x")
476479
assert parser.can_parse(_tr(output_path=tmp_path)) is False # file-backed parsers also need config
477480

478-
(tmp_path / "nemo_config.json").write_text("{}")
481+
(tmp_path / "nemo_config.json").write_text('{"model": {}}')
479482
assert parser.can_parse(_tr(output_path=tmp_path)) is True
480483

481484

485+
@pytest.mark.parametrize(
486+
("parser", "tb_path", "config_path", "config_contents"),
487+
(
488+
pytest.param(NeMoRunParser(), "tb_logs/default", "nemo_config.json", "", id="nemo-empty-file"),
489+
pytest.param(NeMoRunParser(), "tb_logs/default", "nemo_config.json", "{}", id="nemo-empty-object"),
490+
pytest.param(NeMoRunParser(), "tb_logs/default", "nemo_config.json", "{", id="nemo-malformed"),
491+
pytest.param(
492+
MegatronBridgeParser(),
493+
"experiment/tb_logs",
494+
"experiment/configs/ConfigContainer.yaml",
495+
"",
496+
id="bridge-empty-file",
497+
),
498+
pytest.param(
499+
MegatronBridgeParser(),
500+
"experiment/tb_logs",
501+
"experiment/configs/ConfigContainer.yaml",
502+
"{}",
503+
id="bridge-empty-object",
504+
),
505+
pytest.param(
506+
MegatronBridgeParser(),
507+
"experiment/tb_logs",
508+
"experiment/configs/ConfigContainer.yaml",
509+
"key: [",
510+
id="bridge-malformed",
511+
),
512+
),
513+
)
514+
def test_can_parse_rejects_invalid_file_config(tmp_path, parser, tb_path, config_path, config_contents):
515+
tb_dir = tmp_path / tb_path
516+
tb_dir.mkdir(parents=True)
517+
(tb_dir / "events.out.tfevents.1").write_text("x")
518+
artifact = tmp_path / config_path
519+
artifact.parent.mkdir(parents=True, exist_ok=True)
520+
artifact.write_text(config_contents)
521+
522+
assert parser.can_parse(_tr(output_path=tmp_path)) is False
523+
524+
482525
def test_megatron_config_path_points_to_tb_event_file(tmp_path):
483526
tb_dir = tmp_path / "tensorboard"
484527
tb_dir.mkdir()

0 commit comments

Comments
 (0)