Download src/mrna/utils/callbacks.py from atombio/mrnautilus: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/atombio/mrnautilus/resolve/main/src/mrna/utils/callbacks.py
- Command line
-
hf download hf://atombio/mrnautilus/src/mrna/utils/callbacks.py
-
curl -L -o callbacks.py https://huggingface.co/atombio/mrnautilus/resolve/main/src/mrna/utils/callbacks.py
11.1 kB
| # Copyright (c) 2024 Bytedance Ltd. and/or its affiliates | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # Modifications copyright (C) 2024 Atom Bioworks | |
| import os | |
| from typing import Dict | |
| import pytorch_lightning as pl | |
| import torch | |
| from pytorch_lightning import callbacks | |
| #from pytorch_lightning.utilities.imports import _RICH_AVAILABLE | |
| from importlib.util import find_spec | |
| from pytorch_lightning.utilities.rank_zero import ( | |
| rank_zero_deprecation, | |
| rank_zero_info, | |
| rank_zero_warn, | |
| ) | |
| from rich import reconfigure | |
| from torch import Tensor | |
| from omegaconf import OmegaConf | |
| from packaging.version import Version | |
| import importlib | |
| from typing import Callable | |
| from pkg_resources import DistributionNotFound | |
| import pkg_resources | |
| import operator | |
| def _package_available(package_name: str) -> bool: | |
| """Check if a package is available in your environment. | |
| >>> _package_available('os') | |
| True | |
| >>> _package_available('bla') | |
| False | |
| """ | |
| try: | |
| return find_spec(package_name) is not None | |
| except ModuleNotFoundError: | |
| return False | |
| def _compare_version(package: str, op: Callable, version: str, use_base_version: bool = False) -> bool: | |
| """Compare package version with some requirements. | |
| >>> _compare_version("torch", operator.ge, "0.1") | |
| True | |
| >>> _compare_version("does_not_exist", operator.ge, "0.0") | |
| False | |
| """ | |
| try: | |
| pkg = importlib.import_module(package) | |
| except (ImportError, DistributionNotFound): | |
| return False | |
| try: | |
| if hasattr(pkg, "__version__"): | |
| pkg_version = Version(pkg.__version__) | |
| else: | |
| # try pkg_resources to infer version | |
| pkg_version = Version(pkg_resources.get_distribution(package).version) | |
| except TypeError: | |
| # this is mocked by Sphinx, so it should return True to generate all summaries | |
| return True | |
| if use_base_version: | |
| pkg_version = Version(pkg_version.base_version) | |
| return op(pkg_version, Version(version)) | |
| _RICH_AVAILABLE = _package_available("rich") and _compare_version("rich", operator.ge, "10.2.2") | |
| if _RICH_AVAILABLE: | |
| from pytorch_lightning.callbacks.progress.rich_progress import ( | |
| CustomProgress, | |
| MetricsTextColumn, | |
| RichProgressBar, | |
| ) | |
| from rich import get_console, reconfigure | |
| from rich.text import Text | |
| # NOTE[zzx]: modify here to display float in e-format when lower than 1e-3 | |
| def float_fmt(float_value): | |
| if float_value.is_integer(): | |
| return round(float_value) | |
| elif float_value < 1e-3: | |
| return f'{float_value:.2e}' | |
| else: | |
| return round(float_value, 3) | |
| class BetterMetricsTextColumn(MetricsTextColumn): | |
| """A column containing text.""" | |
| def render(self, task) -> Text: | |
| if ( | |
| self._trainer.state.fn != "fit" | |
| or self._trainer.sanity_checking | |
| or self._trainer.progress_bar_callback.train_progress_bar_id != task.id | |
| ): | |
| return Text() | |
| if self._trainer.training and task.id not in self._tasks: | |
| self._tasks[task.id] = "None" | |
| if self._renderable_cache: | |
| self._tasks[self._current_task_id] = self._renderable_cache[self._current_task_id][1] | |
| self._current_task_id = task.id | |
| if self._trainer.training and task.id != self._current_task_id: | |
| return self._tasks[task.id] | |
| text = "" | |
| for k, v in self._metrics.items(): | |
| text += f"{k}: {float_fmt(v) if isinstance(v, float) else v} " | |
| return Text(text, justify="left", style=self._style) | |
| class BetterRichProgressBar(RichProgressBar): | |
| def _init_progress(self, trainer): | |
| if self.is_enabled and (self.progress is None or self._progress_stopped): | |
| self._reset_progress_bar_ids() | |
| reconfigure(**self._console_kwargs) | |
| self._console = get_console() | |
| self._console.clear_live() | |
| self._metric_component = BetterMetricsTextColumn(trainer, self.theme.metrics, text_delimiter=',', metrics_format='.2f') | |
| self.progress = CustomProgress( | |
| *self.configure_columns(trainer), | |
| self._metric_component, | |
| auto_refresh=False, | |
| disable=self.is_disabled, | |
| console=self._console, | |
| ) | |
| self.progress.start() | |
| # progress has started | |
| self._progress_stopped = False | |
| class ValEveryNSteps(pl.Callback): | |
| def __init__(self, every_n_step): | |
| self.every_n_step = every_n_step | |
| def on_batch_end(self, trainer, pl_module): | |
| if trainer.global_step % self.every_n_step == 0 and trainer.global_step != 0: | |
| trainer.validate() | |
| class CheckpointEveryNSteps(pl.Callback): | |
| """ | |
| Save a checkpoint every N steps, instead of Lightning's default that checkpoints | |
| based on validation loss. | |
| """ | |
| def __init__( | |
| self, | |
| save_step_frequency, | |
| prefix="N-Step-Checkpoint", | |
| use_modelcheckpoint_filename=False, | |
| ): | |
| """ | |
| Args: | |
| save_step_frequency: how often to save in steps | |
| prefix: add a prefix to the name, only used if | |
| use_modelcheckpoint_filename=False | |
| use_modelcheckpoint_filename: just use the ModelCheckpoint callback's | |
| default filename, don't use ours. | |
| """ | |
| self.save_step_frequency = save_step_frequency | |
| self.prefix = prefix | |
| self.use_modelcheckpoint_filename = use_modelcheckpoint_filename | |
| def on_batch_end(self, trainer: pl.Trainer, _): | |
| """ Check if we should save a checkpoint after every train batch """ | |
| epoch = trainer.current_epoch | |
| global_step = trainer.global_step | |
| if global_step % self.save_step_frequency == 0: | |
| if self.use_modelcheckpoint_filename: | |
| filename = trainer.checkpoint_callback.filename | |
| else: | |
| filename = f"{self.prefix}_epoch={epoch}_step={global_step}.ckpt" | |
| ckpt_path = os.path.join(trainer.checkpoint_callback.dirpath, filename) | |
| trainer.save_checkpoint(ckpt_path) | |
| class ModelCheckpoint(callbacks.ModelCheckpoint): | |
| CHECKPOINT_NAME_BEST = "best" | |
| # @classmethod | |
| def _format_checkpoint_name( | |
| self, | |
| filename, | |
| metrics: Dict[str, Tensor], | |
| prefix: str = "", | |
| auto_insert_metric_name: bool = True, | |
| ) -> str: | |
| filename = super()._format_checkpoint_name(filename, metrics, prefix, auto_insert_metric_name) | |
| filename = filename.replace('/', '_') # avoid '/' in filename unexpectedly creates folder | |
| return filename | |
| def on_train_start(self, trainer: "pl.Trainer", pl_module: "pl.LightningModule") -> None: | |
| super().on_train_start(trainer, pl_module) | |
| trainer.callback_metrics[self.monitor] = self.best_model_score | |
| def _update_best_and_save( | |
| self, current: Tensor, trainer: "pl.Trainer", monitor_candidates: Dict[str, Tensor] | |
| ) -> None: | |
| k = len(self.best_k_models) + 1 if self.save_top_k == -1 else self.save_top_k | |
| del_filepath = None | |
| if len(self.best_k_models) == k and k > 0: | |
| del_filepath = self.kth_best_model_path | |
| self.best_k_models.pop(del_filepath) | |
| # do not save nan, replace with +/- inf | |
| if isinstance(current, Tensor) and torch.isnan(current): | |
| current = torch.tensor(float("inf" if self.mode == "min" else "-inf"), device=current.device) | |
| filepath = self._get_metric_interpolated_filepath_name(monitor_candidates, trainer, del_filepath) | |
| # save the current score | |
| self.current_score = current | |
| self.best_k_models[filepath] = current | |
| if len(self.best_k_models) == k: | |
| # monitor dict has reached k elements | |
| _op = max if self.mode == "min" else min | |
| self.kth_best_model_path = _op(self.best_k_models, key=self.best_k_models.get) # type: ignore[arg-type] | |
| self.kth_value = self.best_k_models[self.kth_best_model_path] | |
| _op = min if self.mode == "min" else max | |
| self.best_model_path = _op(self.best_k_models, key=self.best_k_models.get) # type: ignore[arg-type] | |
| self.best_model_score = self.best_k_models[self.best_model_path] | |
| if self.verbose: | |
| epoch = monitor_candidates["epoch"] | |
| step = monitor_candidates["step"] | |
| rank_zero_info( | |
| f"Epoch {epoch:d}, global step {step:d}: {self.monitor!r} reached {current:0.5f}" | |
| f" (best {self.best_model_score:0.5f}), saving model to {filepath!r} as top {k}" | |
| ) | |
| self._save_checkpoint(trainer, filepath) | |
| # update best checkpoint | |
| if self.best_model_path == filepath: | |
| self._save_checkpoint( | |
| trainer, | |
| self.format_checkpoint_name(monitor_candidates, self.CHECKPOINT_NAME_BEST) | |
| ) | |
| if del_filepath is not None and filepath != del_filepath: | |
| trainer.strategy.remove_checkpoint(del_filepath) | |
| def _save_last_checkpoint(self, trainer: "pl.Trainer", monitor_candidates: Dict[str, Tensor]) -> None: | |
| if not self.save_last: | |
| return | |
| filepath = self.format_checkpoint_name(monitor_candidates, self.CHECKPOINT_NAME_LAST) | |
| # set the last model path before saving because it will be part of the state. | |
| previous, self.last_model_path = self.last_model_path, filepath | |
| self._save_checkpoint(trainer, filepath) | |
| if previous and previous != filepath: | |
| trainer.strategy.remove_checkpoint(previous) | |
| class TrackNorms(pl.Callback): | |
| # TODO do callbacks happen before or after the method in the main LightningModule? | |
| # @rank_zero_only # needed? | |
| def on_after_training_step(self, batch, batch_idx, trainer: pl.Trainer, pl_module: pl.LightningModule): | |
| # Log extra metrics | |
| metrics = {} | |
| if hasattr(pl_module, "_grad_norms"): | |
| metrics.update(pl_module._grad_norms) | |
| self.log_dict( | |
| metrics, | |
| on_step=True, | |
| on_epoch=False, | |
| prog_bar=False, | |
| add_dataloader_idx=False, | |
| sync_dist=True, | |
| ) | |
| def on_after_backward(self, trainer: pl.Trainer, pl_module: pl.LightningModule): | |
| # example to inspect gradient information in tensorboard | |
| if OmegaConf.select(trainer.hparams, 'trainer.track_grad_norms'): # TODO dot notation should work with omegaconf? | |
| norms = {} | |
| for name, p in pl_module.named_parameters(): | |
| if p.grad is None: | |
| continue | |
| # param_norm = float(p.grad.data.norm(norm_type)) | |
| param_norm = torch.mean(p.grad.data ** 2) | |
| norms[f"grad_norm.{name}"] = param_norm | |
| pl_module._grad_norms = norms |