Source code for neural_lam.utils.logging
"""Setup and configuration of training loggers (WandB / MLFlow)."""
# Standard library
import argparse
import os
import warnings
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
# Local
from ..datastore.base import BaseDatastore
# Third-party
import pytorch_lightning as pl
from loguru import logger
from pytorch_lightning.loggers import MLFlowLogger, WandbLogger
from pytorch_lightning.utilities import rank_zero_only
# Local
from ..custom_loggers import CustomMLFlowLogger
[docs]
@rank_zero_only
def log_on_rank_zero(
msg: str, level: str = "info", *args: Any, **kwargs: Any
) -> None:
"""
Log a message only on rank zero using loguru logger.
Parameters
----------
msg : str
The message to log.
level : str, default "info"
The logging level (e.g. "info", "warning", "error").
*args : Any
Positional arguments passed to the logger.
**kwargs : Any
Keyword arguments passed to the logger.
"""
if rank_zero_only.rank == 0: # ty: ignore[unresolved-attribute]
log_fn = getattr(logger, level, logger.info)
log_fn(msg, *args, **kwargs)
[docs]
def init_training_logger_metrics(
training_logger: Any, val_steps: list[int]
) -> None:
"""
Configure validation metric aggregation for the active training logger.
Parameters
----------
training_logger : Any
Logger instance used during training.
val_steps : list of int
Autoregressive rollout lengths to log as separate metrics.
"""
experiment = training_logger.experiment
if isinstance(training_logger, WandbLogger):
experiment.define_metric("val_mean_loss", summary="min")
for step in val_steps:
experiment.define_metric(f"val_loss_unroll{step}", summary="min")
elif isinstance(training_logger, MLFlowLogger):
pass
else:
warnings.warn(
"Only WandbLogger & MLFlowLogger is supported for tracking metrics.\
Experiment results will only go to stdout."
)
[docs]
@rank_zero_only
def setup_training_logger(
datastore: "BaseDatastore",
args: argparse.Namespace,
run_name: str,
run_dir: str,
) -> pl.loggers.Logger:
"""
Set up the training logger (WandB or MLFlow).
Parameters
----------
datastore : BaseDatastore
Datastore providing metadata for logging configuration.
args : argparse.Namespace
Parsed training arguments controlling the logger backend.
run_name : str
Name of the run.
run_dir : str
Directory under which all artifacts for this run are written
(logger ``save_dir``, checkpoints, Lightning ``default_root_dir``).
Typically ``runs/<run_name>``.
Returns
-------
pl.loggers.Logger
The initialized logger object.
Raises
------
ValueError
If ``args.logger`` is not ``'wandb'`` or ``'mlflow'``.
Notes
-----
When ``--wandb_id`` is given, ``resume="allow"`` is set automatically:
W&B resumes the run if it exists, or creates it with that ID otherwise.
This allows the same job script to be safely resubmitted on HPC systems.
The run name is set to ``None`` when resuming to preserve the existing name.
"""
if args.wandb_id and args.logger != "wandb":
logger.warning(
f"--wandb_id is set but logger is {args.logger!r}; "
"the wandb_id will have no effect."
)
if args.logger == "wandb":
wandb_resume = "allow" if args.wandb_id else None
logger.info(
f"Wandb resume mode: {wandb_resume!r} (id: {args.wandb_id!r})"
)
return pl.loggers.WandbLogger(
project=args.logger_project,
name=None if args.wandb_id else run_name,
config=dict(training=vars(args), datastore=datastore.config),
resume=wandb_resume,
id=args.wandb_id,
save_dir=run_dir,
)
elif args.logger == "mlflow":
if args.wandb_id is not None:
warnings.warn(
"--wandb_id is only used with --logger=wandb and will be "
"ignored."
)
url = os.getenv("MLFLOW_TRACKING_URI")
if url is None:
raise ValueError(
"MLFlow logger requires setting MLFLOW_TRACKING_URI in env."
)
training_logger = CustomMLFlowLogger(
experiment_name=args.logger_project,
tracking_uri=url,
run_name=run_name,
save_dir=run_dir,
)
training_logger.log_hyperparams(
dict(training=vars(args), datastore=datastore.config)
)
return training_logger
else:
raise ValueError(
f"Unsupported logger type: {args.logger!r}. "
"Supported loggers are: 'wandb', 'mlflow'."
)