Rate this Page

Source code for torchrl.record.loggers.mlflow

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import annotations

import importlib.util

import os
from collections.abc import Mapping, Sequence
from tempfile import TemporaryDirectory
from typing import Any

from tensordict import TensorDictBase
from torch import Tensor

from torchrl.record.loggers.common import (
    _has_torchcodec,
    _make_metrics_safe,
    _write_video,
    Logger,
)

_has_mlflow = importlib.util.find_spec("mlflow") is not None
_has_omegaconf = importlib.util.find_spec("omegaconf") is not None


[docs] class MLFlowLogger(Logger): """Wrapper for the mlflow logger. Args: exp_name (str): The name of the experiment. tracking_uri (str): A tracking URI to a datastore that supports MLFlow. Since MLFlow 3.10, filesystem tracking backends (e.g. ``./mlruns``) are no longer supported and a database backend such as ``sqlite:///path/to/mlflow.db`` must be used. See the `MLflow migration guide <https://mlflow.org/docs/latest/self-hosting/migrate-from-file-store>`_. Keyword Args: artifact_location (str, optional): Location used to store run artifacts (videos, models, ...). When ``None`` (default), MLFlow uses its default artifact location. When ``tracking_uri`` is a filesystem URI, it is also used as ``artifact_location`` for backward compatibility. fps (int, optional): Number of frames per second when recording videos. Defaults to ``30``. """ def __init__( self, exp_name: str, tracking_uri: str, tags: dict[str, Any] | None = None, *, artifact_location: str | None = None, video_fps: int = 30, **kwargs, ) -> None: import mlflow if artifact_location is None and tracking_uri.startswith("file:"): artifact_location = tracking_uri self._mlflow_kwargs = { "name": exp_name, "artifact_location": artifact_location, "tags": tags, } mlflow.set_tracking_uri(tracking_uri) super().__init__(exp_name=exp_name, log_dir=tracking_uri) self.video_log_counter = 0 self.video_fps = video_fps def _checkpoint_state(self) -> dict[str, Any]: return {"id": self.id, "video_log_counter": self.video_log_counter} def _load_checkpoint_state(self, state_dict: Mapping[str, Any]) -> None: if "id" in state_dict: self.id = state_dict["id"] if "video_log_counter" in state_dict: self.video_log_counter = state_dict["video_log_counter"] def _create_experiment(self) -> mlflow.ActiveRun: # noqa import mlflow """Creates an mlflow experiment. Returns: mlflow.ActiveRun: The mlflow experiment object. """ if not _has_mlflow: raise ImportError("MLFlow is not installed") # Only create experiment if it doesn't exist experiment = mlflow.get_experiment_by_name(self._mlflow_kwargs["name"]) if experiment is None: self.id = mlflow.create_experiment(**self._mlflow_kwargs) else: self.id = experiment.experiment_id return mlflow.start_run(experiment_id=self.id) def log_scalar(self, name: str, value: float, step: int | None = None) -> None: """Logs a scalar value to mlflow. Args: name (str): The name of the scalar. value (float): The value of the scalar. step (int, optional): The step at which the scalar is logged. Defaults to None. """ import mlflow mlflow.set_experiment(experiment_id=self.id) mlflow.log_metric(key=name, value=value, step=step) def log_video(self, name: str, video: Tensor, **kwargs) -> None: """Log video inputs to mlflow. Args: name (str): The name of the video. video (Tensor): The video to be logged, expected to be in (T, C, H, W) format for consistency with other loggers. **kwargs: Other keyword arguments. By construction, log_video supports 'step' (integer indicating the step index) and 'fps' (defaults to ``self.video_fps``). """ import mlflow if not _has_torchcodec: raise ImportError( "Logging a video with MLFlow requires torchcodec >= 0.10.0 to " "be installed." ) mlflow.set_experiment(experiment_id=self.id) if video.ndim == 5: video = video[-1] # N T C H W -> T C H W video = video.permute(0, 2, 3, 1) # T C H W -> T H W C if video.size(dim=-1) != 3: raise ValueError( "The MLFlow logger only supports videos with 3 color channels." ) self.video_log_counter += 1 fps = kwargs.pop("fps", self.video_fps) step = kwargs.pop("step", None) with TemporaryDirectory() as temp_dir: video_name = f"{name}_step_{step:04}.mp4" if step else f"{name}.mp4" with open(os.path.join(temp_dir, video_name), "wb") as f: _write_video(f.name, video, fps=fps) mlflow.log_artifact(f.name, "videos") def log_hparams(self, cfg: DictConfig | dict) -> None: # noqa: F821 """Logs the hyperparameters of the experiment. Args: cfg (DictConfig or dict): The configuration of the experiment. """ import mlflow from omegaconf import OmegaConf mlflow.set_experiment(experiment_id=self.id) if type(cfg) is not dict and _has_omegaconf: cfg = OmegaConf.to_container(cfg, resolve=True) mlflow.log_params(cfg) def __repr__(self) -> str: return f"MLFlowLogger(experiment={self.experiment.__repr__()})" def log_histogram(self, name: str, data: Sequence, **kwargs): raise NotImplementedError("Logging histograms in mlflow is not permitted.") def log_metrics( self, metrics: dict[str, Any] | TensorDictBase, step: int | None = None, *, keys_sep: str = "/", ) -> dict[str, Any]: """Log multiple scalar metrics at once to mlflow. This method efficiently handles tensor values by batching CUDA->CPU transfers and performing a single synchronization, then logs all metrics in a single mlflow API call. Args: metrics: Dictionary or TensorDict mapping metric names to values. Tensor values are automatically converted to Python scalars/lists. For TensorDict inputs, nested keys are flattened using ``keys_sep``. step: Optional step value for all metrics. keys_sep: Separator used to flatten nested TensorDict keys into strings. Defaults to "/". Only used for TensorDict inputs. Returns: The converted metrics dictionary (with tensors converted to Python types). """ import mlflow safe_metrics = _make_metrics_safe(metrics, keys_sep=keys_sep) mlflow.set_experiment(experiment_id=self.id) mlflow.log_metrics(safe_metrics, step=step) return safe_metrics