Skip to content

Commit 3ce59b5

Browse files
committed
fix(megatron_bridge): save TensorBoard logs by default
Set the default log directory to the saved run output so training reports can consume TensorBoard metrics while preserving user overrides. Signed-off-by: Ben Lugassi <blugassi@nvidia.com>
1 parent 0de6ca8 commit 3ce59b5

5 files changed

Lines changed: 41 additions & 4 deletions

File tree

src/cloudai/report_generator/training/parser.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,23 @@ 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+
return True
8292

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

src/cloudai/workloads/megatron_bridge/slurm_command_gen_strategy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ def _list_or_comma_str(self, val: str | list[str] | None) -> Optional[str]:
430430
def _add_extra_cmd_args(self, extra_cmd_args: dict[str, str]) -> list[str]:
431431
"""Hydra overrides: defaults merged with the user's extra_cmd_args, which take precedence."""
432432
overrides = {
433+
"logger.tensorboard_dir": "/nemo_run/tb_logs",
433434
"logger.log_timers_to_tensorboard": "true",
434435
"logger.log_throughput_to_tensorboard": "true",
435436
"logger.log_memory_to_tensorboard": "true",

tests/ref_data/megatron-bridge.sbatch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if [ "${WANDB_INSTALL_RC}" -ne 0 ]; then
1919
fi
2020

2121
LAUNCH_RC=0
22-
NEMORUN_HOME="__OUTPUT_DIR__/output" __INSTALL_DIR__/Run__main-venv/bin/python __INSTALL_DIR__/Megatron-Bridge__main/scripts/performance/setup_experiment.py -p main -t 00:20:00 -i __OUTPUT_DIR__/output/megatron_bridge_image.sqsh -hf dummy_token -ng 8 -gn 8 -cm __INSTALL_DIR__/Megatron-Bridge__main:/opt/Megatron-Bridge -cb 'export CUDA_VISIBLE_DEVICES=0,1,2,3' -cb 'export NCCL_DEBUG=INFO' -m qwen3 -mr 30b_a3b --detach false --save_config_filepath /nemo_run/configs/ConfigContainer.yaml --additional_slurm_params 'gpus-per-node=8;gres=gpu:8' logger.log_timers_to_tensorboard=true logger.log_throughput_to_tensorboard=true logger.log_memory_to_tensorboard=true >>"$LOG" 2>&1 || LAUNCH_RC=$?
22+
NEMORUN_HOME="__OUTPUT_DIR__/output" __INSTALL_DIR__/Run__main-venv/bin/python __INSTALL_DIR__/Megatron-Bridge__main/scripts/performance/setup_experiment.py -p main -t 00:20:00 -i __OUTPUT_DIR__/output/megatron_bridge_image.sqsh -hf dummy_token -ng 8 -gn 8 -cm __INSTALL_DIR__/Megatron-Bridge__main:/opt/Megatron-Bridge -cb 'export CUDA_VISIBLE_DEVICES=0,1,2,3' -cb 'export NCCL_DEBUG=INFO' -m qwen3 -mr 30b_a3b --detach false --save_config_filepath /nemo_run/configs/ConfigContainer.yaml --additional_slurm_params 'gpus-per-node=8;gres=gpu:8' logger.tensorboard_dir=/nemo_run/tb_logs logger.log_timers_to_tensorboard=true logger.log_throughput_to_tensorboard=true logger.log_memory_to_tensorboard=true >>"$LOG" 2>&1 || LAUNCH_RC=$?
2323

2424

2525
JOB_ID=""

tests/report_generator/training/test_training_parser.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,10 @@ 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

478481
(tmp_path / "nemo_config.json").write_text("{}")

tests/workloads/megatron_bridge/test_command_gen_strategy_slurm.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,29 @@ def test_defaults_not_emitted_when_not_set_in_toml(
185185
assert "--cuda_graph_impl" not in cmd
186186
assert " -ms " not in cmd
187187

188+
def test_tensorboard_dir_defaults_to_persisted_run_path(
189+
self, configured_slurm_system: SlurmSystem, make_test_run: Callable[..., TestRun]
190+
) -> None:
191+
tr = make_test_run(output_subdir="out_default_tensorboard")
192+
cmd_gen = MegatronBridgeSlurmCommandGenStrategy(configured_slurm_system, tr)
193+
194+
wrapper_content = self._wrapper_content(cmd_gen)
195+
196+
assert "logger.tensorboard_dir=/nemo_run/tb_logs" in wrapper_content
197+
198+
def test_tensorboard_dir_can_be_overridden(
199+
self, configured_slurm_system: SlurmSystem, make_test_run: Callable[..., TestRun]
200+
) -> None:
201+
tr = make_test_run(output_subdir="out_custom_tensorboard")
202+
tdef = cast(MegatronBridgeTestDefinition, tr.test)
203+
tdef.extra_cmd_args["logger.tensorboard_dir"] = "/nemo_run/custom_tb"
204+
cmd_gen = MegatronBridgeSlurmCommandGenStrategy(configured_slurm_system, tr)
205+
206+
wrapper_content = self._wrapper_content(cmd_gen)
207+
208+
assert "logger.tensorboard_dir=/nemo_run/custom_tb" in wrapper_content
209+
assert "logger.tensorboard_dir=/nemo_run/tb_logs" not in wrapper_content
210+
188211
def test_container_image_local_path_passed_verbatim(
189212
self, cmd_gen: MegatronBridgeSlurmCommandGenStrategy, test_run: TestRun
190213
) -> None:

0 commit comments

Comments
 (0)