Source code for rsl_rl.utils.neptune_log_writer

# Copyright (c) 2021-2026, ETH Zurich and NVIDIA CORPORATION
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause


from __future__ import annotations

import os
from dataclasses import asdict
from torch.utils.tensorboard import SummaryWriter

from rsl_rl.utils.log_writer import LogWriter

try:
    import neptune  # type: ignore
except ModuleNotFoundError:
    neptune = None


[docs] class NeptuneLogWriter(SummaryWriter, LogWriter): """Summary writer for Neptune."""
[docs] def __init__(self, log_dir: str, project_name: str) -> None: """Initialize a Neptune run for logging.""" if neptune is None: raise ModuleNotFoundError("neptune-client is required to log to Neptune.") super().__init__(log_dir, flush_secs=10) # Get the run name run_name = os.path.split(log_dir)[-1] try: token = os.environ["NEPTUNE_API_TOKEN"] except KeyError: raise KeyError( "Neptune api token not found. Please run or add to ~/.bashrc: export NEPTUNE_API_TOKEN=YOUR_API_TOKEN" ) from None try: entity = os.environ["NEPTUNE_USERNAME"] except KeyError: raise KeyError( "Neptune username not found. Please run or add to ~/.bashrc: export NEPTUNE_USERNAME=YOUR_USERNAME" ) from None # Initialize neptune neptune_project = entity + "/" + project_name self.run = neptune.init_run(project=neptune_project, api_token=token) self.run["log_dir"].log(run_name) # Name mapping for incompatible characters self.name_map = { "Train/mean_reward/time": "Train/mean_reward_time", "Train/mean_episode_length/time": "Train/mean_episode_length_time", }
[docs] def add_scalar( self, tag: str, scalar_value: float, global_step: int | None = None, walltime: float | None = None, new_style: bool = False, ) -> None: """Log a scalar to both TensorBoard and Neptune.""" super().add_scalar(tag, scalar_value, global_step=global_step, walltime=walltime, new_style=new_style) self.run[self._map_path(tag)].log(scalar_value, step=global_step)
[docs] def store_config(self, env_cfg: dict | object, train_cfg: dict) -> None: """Upload environment and training configuration to Neptune.""" self.run["train_cfg"] = train_cfg try: self.run["env_cfg"] = env_cfg.to_dict() # type: ignore except Exception: self.run["env_cfg"] = asdict(env_cfg) # type: ignore
[docs] def save_model(self, model_path: str, it: int) -> None: """Upload a model checkpoint artifact to Neptune.""" self.run["model/saved_model_" + str(it)].upload(model_path)
[docs] def save_file(self, path: str) -> None: """Upload an arbitrary file artifact to Neptune.""" name = path.rsplit("/", 1)[-1].split(".")[0] self.run["git_diff/" + name].upload(path)
[docs] def stop(self) -> None: """Finish the active Neptune run.""" self.run.stop()
def _map_path(self, path: str) -> str: """Map metric names to Neptune-compatible keys.""" if path in self.name_map: return self.name_map[path] else: return path