NVIDIA-NeMo/Speech · #16504

Update training dependency compatibility

pzelasko · merged Nov 9, 202628 files · 1145 + / 445 −
docs/source/core/exp_manager.rst24 + / 14 −
@@ -15,6 +15,13 @@ To use the Experiment Manager, call :class:`~nemo.utils.exp_manager.exp_manager`  The Experiment Manager is configurable using YAML with Hydra. +NeMo's Lightning integration preserves the previous callback defaults: ``TQDMProgressBar``+and a plain ``ModelSummary``. Explicit callbacks remain unchanged; selecting a+``RichProgressBar`` also selects a Rich model summary unless a summary callback is+already provided. ``trainer.enable_progress_bar: false``,+``trainer.enable_model_summary: false``, and barebones mode retain their usual behavior.+This applies to direct Lightning Trainer construction as well as NeMo's Trainer configuration helper.+ .. code-block:: bash      exp_manager:@@ -33,6 +40,9 @@ Optionally, launch TensorBoard to view the training results in ``exp_dir``, whic  If ``create_checkpoint_callback`` is set to ``True``, then NeMo automatically creates checkpoints during training using PyTorch Lightning's `ModelCheckpoint <https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.callbacks.ModelCheckpoint.html>`_.+With ``save_last: true``, NeMo refreshes the last checkpoint at each eligible validation or+training-epoch boundary even when the monitored metric does not improve. Async saves update+scheduling state immediately; unfinished markers remain until writing completes. We can configure the ``ModelCheckpoint`` via YAML or CLI:  .. code-block:: yaml@@ -51,6 +61,14 @@ We can configure the ``ModelCheckpoint`` via YAML or CLI: Resume Training --------------- +New NeMo checkpoints store resolved configuration values without dataclass schema metadata;+the live model configuration and hparams retain their schemas. Previously saved built-in decoding and+adapter configurations remain supported. For an older checkpoint containing an application-defined+schema, import that trusted schema in application code and call+``nemo.utils.serialization.register_checkpoint_schema_types(MyConfig)`` before loading it.+Checkpoint data does not select which schema classes to register.++ To auto-resume training, configure the ``exp_manager``. This feature is important for long training runs that might be interrupted or shut down before the procedure has completed. To auto-resume training, set the following parameters via YAML or CLI: @@ -177,26 +195,18 @@ Neptune  .. _exp_manager_neptune-label: -.. code-block:: yaml--    exp_manager:-        ...-        create_checkpoint_callback: True-        create_neptune_logger: false-        neptune_logger_kwargs:-            project: ${project}-            name: ${name}-            prefix: train-            log_model_checkpoints: false # set to True if checkpoints need to be pushed to Neptune-            tags: null # can specify as an array of strings in yaml array format-            description: null-            <Add any other arguments supported by Neptune logger here>+NeptuneLogger is no longer supported by Lightning 2.6. Keep ``create_neptune_logger: false``.+Setting it to ``true`` raises a configuration error before experiment files are created.+Use another supported Experiment Manager logger, or pass a supported logger directly to the Trainer.  Exponential Moving Average --------------------------  .. _exp_manager_ema-label: +EMA sidecar and optimizer state are restored when resuming training. Validation, testing,+and prediction with a checkpoint use its model weights without requiring an EMA sidecar.+ NeMo supports using exponential moving average (EMA) for model parameters. This can be useful for improving model generalization and stability. To use EMA, set the following parameters via YAML or :class:`~nemo.utils.exp_manager.ExpManagerConfig`. 
nemo/collections/asr/parts/submodules/adapters/__init__.py10 + / 0 −
@@ -31,5 +31,15 @@     TransformerMultiHeadAttentionAdapter,     TransformerMultiHeadAttentionAdapterConfig, )+from nemo.utils.serialization import register_checkpoint_schema_types  # fmt: on+++register_checkpoint_schema_types(+    MultiHeadAttentionAdapterConfig,+    PositionalEncodingAdapterConfig,+    RelPositionalEncodingAdapterConfig,+    RelPositionMultiHeadAttentionAdapterConfig,+    TransformerMultiHeadAttentionAdapterConfig,+)
nemo/collections/asr/parts/submodules/ctc_decoding.py4 + / 0 −
@@ -31,6 +31,7 @@ from nemo.collections.common.tokenizers.aggregate_tokenizer import DummyTokenizer from nemo.collections.common.tokenizers.tokenizer_spec import TokenizerSpec from nemo.utils import logging, logging_mode+from nemo.utils.serialization import register_checkpoint_schema_types   def move_dimension_to_the_front(tensor, dim_index):@@ -1389,3 +1390,6 @@ class CTCDecodingConfig: @dataclass class CTCBPEDecodingConfig(CTCDecodingConfig):     pass+++register_checkpoint_schema_types(CTCDecodingConfig, CTCBPEDecodingConfig)
nemo/collections/asr/parts/submodules/multitask_decoding.py4 + / 0 −
@@ -34,6 +34,7 @@ from nemo.collections.common.tokenizers.aggregate_tokenizer import AggregateTokenizer from nemo.collections.common.tokenizers.tokenizer_spec import TokenizerSpec from nemo.utils import logging+from nemo.utils.serialization import register_checkpoint_schema_types   class AbstractMultiTaskDecoding(ConfidenceMixin):@@ -673,3 +674,6 @@ class MultiTaskDecodingConfig:      # if set to true, return attention scores; ignore them to save memory otherwise     return_xattn_scores: bool = False+++register_checkpoint_schema_types(MultiTaskDecodingConfig)
nemo/collections/asr/parts/submodules/rnnt_decoding.py4 + / 0 −
@@ -34,6 +34,7 @@ from nemo.collections.common.tokenizers.tokenizer_spec import TokenizerSpec from nemo.utils import logging from nemo.utils.enum import PrettyStrEnum+from nemo.utils.serialization import register_checkpoint_schema_types  try:     import kenlm@@ -1927,3 +1928,6 @@ class RNNTBPEDecodingConfig(RNNTDecodingConfig):     """      pass+++register_checkpoint_schema_types(RNNTDecodingConfig, RNNTBPEDecodingConfig)
nemo/collections/common/callbacks/ema.py8 + / 1 −
@@ -22,6 +22,7 @@ import lightning.pytorch as pl import torch from lightning.pytorch import Callback+from lightning.pytorch.trainer.states import TrainerFn from lightning.pytorch.utilities.exceptions import MisconfigurationException from lightning.pytorch.utilities.rank_zero import rank_zero_info @@ -121,6 +122,9 @@ def save_original_optimizer_state(self, trainer: "pl.Trainer"):     def on_load_checkpoint(         self, trainer: "pl.Trainer", pl_module: "pl.LightningModule", checkpoint: Dict[str, Any]     ) -> None:+        # Evaluation restores callback state too, but never resumes optimizer/EMA state.+        if getattr(getattr(trainer, 'state', None), 'fn', TrainerFn.FITTING) != TrainerFn.FITTING:+            return         checkpoint_callback = trainer.checkpoint_callback          # Replace connector._ckpt_path with below to avoid calling into lightning's protected API@@ -137,7 +141,10 @@ def on_load_checkpoint(                 return             ema_path = ckpt_path.replace(ext, f'-EMA{ext}')             if os.path.exists(ema_path):-                ema_state_dict = torch.load(ema_path, map_location=torch.device('cpu'), weights_only=False)+                from nemo.utils.serialization import checkpoint_config_loading++                with checkpoint_config_loading():+                    ema_state_dict = torch.load(ema_path, map_location=torch.device('cpu'), weights_only=True)                  checkpoint['optimizer_states'] = ema_state_dict['optimizer_states']                 del ema_state_dict
nemo/collections/common/parts/adapter_modules.py4 + / 0 −
@@ -22,6 +22,7 @@ from nemo.collections.common.parts.utils import activation_registry from nemo.core.classes.common import safe_instantiate from nemo.core.classes.mixins import access_mixins, adapter_mixin_strategies+from nemo.utils.serialization import register_checkpoint_schema_types   class AdapterModuleUtil(access_mixins.AccessMixin):@@ -170,3 +171,6 @@ class LinearAdapterConfig:         default_factory=lambda: adapter_mixin_strategies.ResidualAddAdapterStrategyConfig()     )     _target_: str = "{0}.{1}".format(LinearAdapter.__module__, LinearAdapter.__name__)+++register_checkpoint_schema_types(LinearAdapterConfig)
nemo/collections/speechlm2/parts/parallel.py3 + / 3 −
@@ -278,7 +278,7 @@ class AutomodelParallelStrategy(ModelParallelStrategy):     """      @override-    def load_checkpoint(self, checkpoint_path):+    def load_checkpoint(self, checkpoint_path, weights_only: Optional[bool] = None):         """Load DCP optimizer state while preserving lazy-state semantics.          Model tensors remain strict. Optimizer loading alone permits a complete@@ -290,7 +290,7 @@ def load_checkpoint(self, checkpoint_path):          path = Path(self.broadcast(checkpoint_path))         if not _is_sharded_checkpoint(path):-            return super().load_checkpoint(path)+            return super().load_checkpoint(path, weights_only=weights_only)          from torch.distributed.checkpoint import FileSystemReader, load         from torch.distributed.checkpoint.state_dict import (@@ -331,7 +331,7 @@ def load_checkpoint(self, checkpoint_path):         # of resume. Keep this metadata-only payload alive for consumers whose         # restored state can outlive that connector reference; model and         # optimizer tensors were loaded separately through DCP above.-        checkpoint = torch.load(path / _METADATA_FILENAME)+        checkpoint = torch.load(path / _METADATA_FILENAME, weights_only=weights_only)         self._checkpoint_keepalive = checkpoint         return checkpoint 
nemo/core/classes/mixins/adapter_mixin_strategies.py4 + / 0 −
@@ -20,6 +20,7 @@ import torch  from nemo.core.classes.mixins import AccessMixin+from nemo.utils.serialization import register_checkpoint_schema_types  if TYPE_CHECKING:     from nemo.core.classes.mixins.adapter_mixins import AdapterModuleMixin@@ -263,3 +264,6 @@ class ResidualAddAdapterStrategyConfig:     _target_: str = "{0}.{1}".format(         ResidualAddAdapterStrategy.__module__, ResidualAddAdapterStrategy.__name__     )  # mandatory field+++register_checkpoint_schema_types(ResidualAddAdapterStrategyConfig)
nemo/core/classes/modelPT.py10 + / 0 −
@@ -1718,6 +1718,16 @@ def hparams(self):          return super().hparams +    def on_save_checkpoint(self, checkpoint: Dict[str, Any]) -> None:+        """Store a plain configuration snapshot without changing live hparams metadata."""+        super().on_save_checkpoint(checkpoint)+        key = self.CHECKPOINT_HYPER_PARAMS_KEY+        hyper_parameters = checkpoint.get(key)+        if isinstance(hyper_parameters, (dict, DictConfig)) and 'cfg' in hyper_parameters:+            hyper_parameters = copy.deepcopy(hyper_parameters)+            hyper_parameters['cfg'] = OmegaConf.create(OmegaConf.to_container(self.cfg, resolve=True))+            checkpoint[key] = hyper_parameters+     @property     def validation_step_outputs(self):         """
nemo/core/config/pytorch_lightning.py2 + / 0 −
@@ -75,6 +75,8 @@ class TrainerConfig:     enable_model_summary: bool = True     inference_mode: bool = True     barebones: bool = False+    model_registry: Optional[str] = None+    enable_autolog_hparams: bool = True   # Register the trainer config.
nemo/lightning/callback_group.py2 + / 2 −
@@ -19,7 +19,6 @@  from lightning.pytorch.callbacks import Callback as PTLCallback from nemo.lightning.base_callback import BaseCallback-from nemo.lightning.one_logger_callback import OneLoggerNeMoCallback   class CallbackGroup:@@ -44,7 +43,8 @@ def get_instance(cls) -> 'CallbackGroup':         return cls._instance      def __init__(self) -> None:-        self._callbacks: List[BaseCallback] = [OneLoggerNeMoCallback()]+        # Leave telemetry detached until its callback integration is updated.+        self._callbacks: List[BaseCallback] = []         # Ensure application-end is emitted at most once per process         self._app_end_emitted: bool = False 
nemo/utils/__init__.py10 + / 1 −
@@ -28,11 +28,20 @@ from nemo.utils.nemo_logging import Logger as _Logger from nemo.utils.nemo_logging import LogMode as logging_mode +try:+    from nemo.utils.serialization import register_checkpoint_config_types+except ModuleNotFoundError as error:+    if error.name != 'omegaconf':+        raise+else:+    register_checkpoint_config_types()+ logging = _Logger() try:-    from nemo.utils.lightning_logger_patch import add_memory_handlers_to_pl_logger+    from nemo.utils.lightning_logger_patch import add_memory_handlers_to_pl_logger, preserve_trainer_callback_defaults      add_memory_handlers_to_pl_logger()+    preserve_trainer_callback_defaults() except ModuleNotFoundError:     pass 
nemo/utils/callbacks/dist_ckpt_io.py2 + / 310 −
@@ -13,51 +13,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -import shutil from abc import ABC, abstractmethod from contextlib import contextmanager from time import time-from typing import Any, Dict, Optional, Union+from typing import Any, Dict, Optional  import lightning.pytorch as pl-import torch from lightning.fabric.plugins import CheckpointIO-from lightning.fabric.utilities.cloud_io import get_filesystem from lightning.fabric.utilities.types import _PATH from lightning.pytorch import Callback from lightning.pytorch.plugins.io.wrapper import _WrappingCheckpointIO  from nemo.utils import logging  try:-    from megatron.core import dist_checkpointing-    from megatron.core.dist_checkpointing.dict_utils import extract_matching_values-    from megatron.core.dist_checkpointing.mapping import ShardedBase-    from megatron.core.dist_checkpointing.serialization import (-        get_default_load_sharded_strategy,-        get_default_save_sharded_strategy,-    )-    from megatron.core.dist_checkpointing.strategies import tensorstore     from megatron.core.dist_checkpointing.strategies.async_utils import AsyncCallsQueue, AsyncRequest-    from megatron.core.dist_checkpointing.strategies.base import SaveShardedStrategy-    from megatron.core.dist_checkpointing.strategies.fully_parallel import (-        FullyParallelLoadStrategyWrapper,-        FullyParallelSaveStrategyWrapper,-    )-    from megatron.core.dist_checkpointing.strategies.torch import TorchDistSaveShardedStrategy-    from megatron.core.dist_checkpointing.validation import StrictHandling-    from megatron.core.parallel_state import get_data_parallel_group      HAVE_MEGATRON_CORE = True  except (ImportError, ModuleNotFoundError) as e:      HAVE_MEGATRON_CORE = False-    IMPORT_ERROR = (-        "megatron-core was not found. "-        "Please see the NeMo README for installation instructions: https://github.com/NVIDIA-NeMo/Speech#megatron-gpt."-        f" Exact error: {e}"-    )+    IMPORT_ERROR = f"Async checkpoint saving requires the optional megatron-core async utilities. Exact error: {e}"   @contextmanager@@ -198,288 +175,3 @@ def _get_checkpoint_io(self, trainer) -> AsyncFinalizableCheckpointIO:                 f'Async finalizer requires an async compatible CheckpointIO, got: {checkpoint_io.__class__}'             )         return checkpoint_io---class DistributedCheckpointIO(AsyncCompatibleCheckpointIO):-    """CheckpointIO for a distributed checkpoint format.--    Args:-        save_ckpt_format (str): Distributed checkpoint format to use for checkpoint saving.-        load_directly_on_device (bool, optional): if True, loads the weights directly-            on GPU. Has effect only for `zarr` based checkpoints (PyT Distributed-            always loads on device). Defaults to True.-        load_strictness (StrictHandling, optional): defines loading strictness.-            If not None, overwrites the `strict` flag passed to `load_checkpoint`.-            Defaults to None.-        async_save (bool): whether to save asynchronously. Should be set to True if-            this class will be wrapped with AsyncFinalizableCheckpointIO.-        torch_dist_multiproc (int, optional): number of extra processes per rank-            used during ckpt save with PyTorch distributed format. Defaults, to None-            which means using an MCore default (2).-        parallel_save (bool): parallelizes the save across ranks. Defaults to True-        parallel_load (bool): parallelizes the load across ranks (followed by params all gather).-            Defaults to False due to some extra memory usage requirement.-    """--    def __init__(-        self,-        save_ckpt_format: str,-        load_directly_on_device: bool = True,-        load_strictness: Optional['StrictHandling'] = None,-        async_save: bool = False,-        torch_dist_multiproc: Optional[int] = None,-        assume_constant_structure: bool = False,-        parallel_save: bool = False,-        parallel_save_within_dp: bool = False,-        parallel_load: bool = False,-    ):-        super().__init__()-        if not HAVE_MEGATRON_CORE:-            raise ImportError(IMPORT_ERROR)--        self.save_ckpt_format = save_ckpt_format-        self.load_directly_on_device = load_directly_on_device-        self.load_strictness = load_strictness-        self.async_save = async_save-        self.torch_dist_multiproc = torch_dist_multiproc-        self.assume_constant_structure = assume_constant_structure-        self.parallel_save = parallel_save-        self.parallel_save_within_dp = parallel_save_within_dp-        self.parallel_load = parallel_load--        self._save_sharded_strategy = None-        self.validated_consistency = False--    @classmethod-    def from_config(cls, model_cfg: dict, async_save: bool = False):-        """Instantiates a DistributedCheckpointIO from a config dict.--        Args:-            model_cfg (dict): model config dict. Most of the configuration-                is extracted from this config.-            async_save (bool, optional): async_save flag is not part of the model config,-                it should be provided separately. Defaults to False.-        """-        return cls(-            save_ckpt_format=model_cfg.get('dist_ckpt_format', 'torch_dist'),-            load_directly_on_device=model_cfg.get('dist_ckpt_load_on_device', True),-            load_strictness=model_cfg.get('dist_ckpt_load_strictness', None),-            async_save=async_save,-            torch_dist_multiproc=model_cfg.get('dist_ckpt_torch_dist_multiproc', None),-            parallel_save=model_cfg.get('dist_ckpt_parallel_save', False),-            parallel_save_within_dp=model_cfg.get('dist_ckpt_parallel_save_within_dp', False),-            parallel_load=model_cfg.get('dist_ckpt_parallel_load', False),-        )--    @_debug_time('DistributedCheckpointIO.save_checkpoint')-    def save_checkpoint(-        self, checkpoint: Dict[str, Any], path: _PATH, storage_options: Optional[Any] = None-    ) -> Optional['AsyncRequest']:-        """Saves a distributed checkpoint. Creates the checkpoint root directory if doesn't exist.--        Args:-            checkpoint (Dict[str, Any]): sharded state dict to save-            path (_PATH): checkpoint directory-            storage_options (Any, optional): Optional parameters when saving the checkpoint-        """-        fs = get_filesystem(path)-        fs.makedirs(path, exist_ok=True)--        validate_sharding_integrity = not (self.validated_consistency and self.assume_constant_structure)-        self.validated_consistency = True--        rank = torch.distributed.get_rank()-        iteration = _get_iteration_from_checkpoint(checkpoint)-        start_time = time()-        async_save_request = dist_checkpointing.save(-            sharded_state_dict=checkpoint,-            checkpoint_dir=path,-            sharded_strategy=self.save_sharded_strategy,-            validate_access_integrity=validate_sharding_integrity,-            async_sharded_save=self.async_save,-        )-        end_time = time()-        log_parts = (-            "Global Checkpoint Save",-            f"Rank: {rank}",-            f"Iteration: {iteration}" if iteration is not None else None,-            f"Start time: {start_time:.3f}s",-            f"Save duration: {end_time - start_time:.3f}s",-        )-        log_message = " : ".join(part for part in log_parts if part is not None)-        logging.info(log_message)--        def iter_finalize_fn():-            logging.info(f'Successfully saved checkpoint from iteration {int(iteration):7d} to {path}')--        if self.async_save:-            assert async_save_request is not None-            async_save_request.add_finalize_fn(iter_finalize_fn)--        return async_save_request--    @_debug_time('DistributedCheckpointIO.load_checkpoint')-    def load_checkpoint(-        self,-        path: _PATH,-        map_location: Optional[Any] = None,-        sharded_state_dict: Dict[str, Any] = None,-        strict: Union[None, bool, 'StrictHandling'] = None,-        validate_access_integrity: Optional[bool] = True,-    ) -> Dict[str, Any]:-        """Loads a distributed checkpoint.--        Args:-            path (_PATH): checkpoint directory-            map_location (Any, optional): required to be None in this implementation-            sharded_state_dict (Dict[str, Any], optional): state dict which-                defines the loading procedure for the distributed checkpoint.-                Defaults to None to comply with the CheckpointIO interface,-                but it's a required argument.-            strict (bool, StrictHandling, optional): adjust load strictness. bool value-                is translated to StrictHandling instance. Gets overwritten by-                `self.load_strictness`. Defaults to None. If `self.load_strictness`-                is also None, strict becomes StrictHandling.ASSUME_OK_UNEXPECTED.--        Returns:-            Dist[str, Any]: loaded checkpoint.-        """-        if sharded_state_dict is None:-            raise ValueError('DistributedCheckpointIO requires passing sharded_state_dict argument to load_checkpoint')-        if map_location is not None:-            raise ValueError('DistributedCheckpointIO doesnt handle map_location argument')--        if self.save_ckpt_format == 'zarr' and self.load_directly_on_device:-            sharded_strategy = tensorstore.TensorStoreLoadShardedStrategy(load_directly_on_device=True)-        else:-            sharded_strategy = None--        if self.parallel_load:-            if sharded_strategy is None:-                sharded_strategy = get_default_load_sharded_strategy(path)-            sharded_strategy = FullyParallelLoadStrategyWrapper(-                sharded_strategy, get_data_parallel_group(with_context_parallel=True)-            )--        if sharded_strategy is not None:-            logging.info(f'Using {sharded_strategy} dist-ckpt load strategy.')--        if isinstance(strict, bool):-            # For backward-compatibility reasons and a bug in MCore (strict check not applied to factories)-            # we must apply a simple strict check here.-            if not strict:-                sharded_state_dict = self.adjust_non_strict_load(path, sharded_state_dict)-            strict = StrictHandling.ASSUME_OK_UNEXPECTED if strict else StrictHandling.LOG_ALL-        if self.load_strictness is not None:-            # Overwrites function argument-            strict = self.load_strictness-        if strict is None:-            # Default behavior-            strict = StrictHandling.ASSUME_OK_UNEXPECTED--        logging.debug(f'Dist ckpt load strictness: {strict}')--        start_time = time()-        ret = dist_checkpointing.load(-            sharded_state_dict=sharded_state_dict,-            checkpoint_dir=path,-            sharded_strategy=sharded_strategy,-            validate_access_integrity=validate_access_integrity,-            strict=strict,-        )-        end_time = time()-        duration = end_time - start_time-        logging.info(-            "Global Checkpoint Load : "-            f"Rank : {torch.distributed.get_rank()} : "-            f"Start time : {start_time:.3f}s : "-            f"Time spent in load_checkpoint: {duration:.3f}s"-        )-        return ret--    def adjust_non_strict_load(self, path: _PATH, sharded_state_dict: Dict[str, Any]):-        """Remove unexpected keys from being loaded into the state dict."""-        ckpt_sharded_metadata = dist_checkpointing.load_tensors_metadata(path)-        loaded_keys = []-        unexpected_keys = []--        def should_remove_missing_sharded_base(x: Any):-            if isinstance(x, ShardedBase):-                if x.key in ckpt_sharded_metadata:-                    loaded_keys.append(x.key)-                    return False-                else:-                    unexpected_keys.append(x.key)-                    return True-            return False--        _, sharded_state_dict = extract_matching_values(sharded_state_dict, should_remove_missing_sharded_base)-        logging.info(f'The following keys are not in the checkpoint and will not be loaded: {unexpected_keys}')--        # TODO: compute missing_keys by:-        #  1. all_gather_object of loaded_keys-        #  2. missing_keys = ckpt_sharded_metadata.keys() - loaded_keys-        return sharded_state_dict--    @_debug_time('DistributedCheckpointIO.remove_checkpoint')-    def remove_checkpoint(self, path: _PATH) -> None:-        """Remove a distributed checkpoint.--        Due to potentially large number of files, the implementation remove the whole directory at once.-        """-        shutil.rmtree(path, ignore_errors=True)--    @property-    def save_sharded_strategy(self) -> 'SaveShardedStrategy':-        """Conditionally initialize and get the sharded strategy to use for saving."""-        if self._save_sharded_strategy is None:-            self._save_sharded_strategy = self._determine_dist_ckpt_save_strategy()-        return self._save_sharded_strategy--    def _determine_dist_ckpt_save_strategy(self):-        """Determine the saving strategy based on constructor args.--        Relies on the default MCore strategy unless extra PyT Distributed format arguments-        are passed in config or in case of a fully parallel save in which case-        a parallelization wrapper is applied.-        """-        if self.save_ckpt_format == 'zarr':-            logging.warning(-                '`zarr` distributed checkpoint backend is deprecated.'-                ' Distributed optimizer checkpoint saving might be extremely slow.'-                ' Please switch to PyTorch Distributed format (model.dist_ckpt_format=torch_dist).'-            )--        if self.async_save and self.save_ckpt_format != 'torch_dist':-            raise ValueError('Async dist-ckpt save supported only for torch_dist format')--        torch_dist_kwargs = {} if self.torch_dist_multiproc is None else dict(thread_count=self.torch_dist_multiproc)-        if self.save_ckpt_format == 'torch_dist' and torch_dist_kwargs:-            save_strategy = TorchDistSaveShardedStrategy(self.save_ckpt_format, 1, **torch_dist_kwargs)-        else:-            save_strategy = get_default_save_sharded_strategy(self.save_ckpt_format, 1)--        # MCore v0.8 introduces `use_cached_ckpt_structure` attribute-        if hasattr(save_strategy, 'use_cached_ckpt_structure'):-            save_strategy.use_cached_ckpt_structure = self.assume_constant_structure--        if self.parallel_save:-            parallelization_group = (-                get_data_parallel_group(with_context_parallel=True) if self.parallel_save_within_dp else None-            )-            save_strategy = FullyParallelSaveStrategyWrapper(-                save_strategy, parallelization_group, self.assume_constant_structure-            )--        logging.info(f'Using {save_strategy} dist-ckpt save strategy.')-        return save_strategy---def _get_iteration_from_checkpoint(checkpoint: Dict[str, Any]) -> Optional[int]:-    return (-        checkpoint.get("loops", {})-        .get("fit_loop", {})-        .get("epoch_loop.batch_progress", {})-        .get("total", {})-        .get("completed", None)-    )
nemo/utils/callbacks/nemo_model_checkpoint.py26 + / 3 −
@@ -252,6 +252,28 @@ def on_save_checkpoint(self, trainer, pl_module, checkpoint):             get_filesystem(backup_path).rm(backup_path)         return output +    def on_validation_end(self, trainer, pl_module):+        # Lightning 2.6.0 stopped refreshing last when top-k does not improve; preserve NeMo's prior behavior.+        refresh_last = (+            self.save_last is True+            and not self._should_skip_saving_checkpoint(trainer)+            and not self._should_save_on_train_epoch_end(trainer)+        )+        super().on_validation_end(trainer, pl_module)+        if refresh_last and self._last_global_step_saved != trainer.global_step:+            self._save_last_checkpoint(trainer, self._monitor_candidates(trainer))++    def on_train_epoch_end(self, trainer, pl_module):+        # Lightning 2.6.0 stopped refreshing last when top-k does not improve; preserve NeMo's prior behavior.+        refresh_last = (+            self.save_last is True+            and not self._should_skip_saving_checkpoint(trainer)+            and self._should_save_on_train_epoch_end(trainer)+        )+        super().on_train_epoch_end(trainer, pl_module)+        if refresh_last and self._last_global_step_saved != trainer.global_step:+            self._save_last_checkpoint(trainer, self._monitor_candidates(trainer))+     def on_train_end(self, trainer, pl_module):         """         Save the checkpoint on train end.@@ -573,6 +595,10 @@ def _save_checkpoint(self, trainer: 'lightning.pytorch.Trainer', filepath: str)                 storage_options = None             logging.info(f'Checkpoint save for step {trainer.global_step} started at {time.time()}.')             trainer.save_checkpoint(filepath, self.save_weights_only, storage_options=storage_options)+            # Lightning uses these to schedule subsequent saves in this hook.+            # Async completion may happen several steps later and must not rewind them.+            self._last_global_step_saved = trainer.global_step+            self._last_checkpoint_saved = filepath             if self.async_save:                 logging.info(f'Scheduled async checkpoint save for {filepath}')             else:@@ -588,9 +614,6 @@ def _get_finalize_save_checkpoint_callback(          def _cb():             logging.debug(f'Finalize callback called for step {global_step}, filepath {filepath}')-            self._last_global_step_saved = global_step-            self._last_checkpoint_saved = filepath-             # notify loggers             if trainer.is_global_zero:                 for logger in trainer.loggers:
nemo/utils/callbacks/s3_checkpoint_io.py11 + / 8 −
@@ -175,6 +175,7 @@ def _check_uploading_results_so_far(self):     def save_checkpoint(         self, checkpoint: Dict[str, Any], path: Union[str, Path], storage_options: Optional[Any] = None     ) -> None:+        """Serialize a checkpoint and upload it to the configured S3 path."""         # if we have a shared memory directory, we can serialize as a file to shared memory instead of as bytes.         if os.path.exists(SHARED_MEM_DIR):             localfile = self._serialize_checkpoint_to_shm(checkpoint, path)@@ -206,8 +207,12 @@ def save_checkpoint(                 _upload_bytes_to_s3(bytes, path, self.chunk_size_MB, self.max_write_concurrency)      def load_checkpoint(-        self, path: Union[str, Path], map_location: Optional[Callable] = lambda storage, loc: storage+        self,+        path: Union[str, Path],+        map_location: Optional[Callable] = lambda storage, loc: storage,+        weights_only: Optional[bool] = None,     ) -> Dict[str, Any]:+        """Download a checkpoint and forward device mapping and unpickling policy to PyTorch."""         if os.path.exists(SHARED_MEM_DIR):             with NamedTemporaryFile(dir=SHARED_MEM_DIR, delete=True) as tempfile:                 logging.info(@@ -220,33 +225,31 @@ def load_checkpoint(                     chunk_size_MB=self.chunk_size_MB,                     max_concurrency=self.max_read_concurrency,                 )-                checkpoint = torch.load(tempfile.name)+                checkpoint = torch.load(tempfile.name, map_location=map_location, weights_only=weights_only)         else:             file_stream: BytesIO = S3Utils.download_s3_file_to_stream(                 s3_path=path, chunk_size_MB=self.chunk_size_MB, max_concurrency=self.max_read_concurrency             )-            checkpoint = torch.load(file_stream)+            checkpoint = torch.load(file_stream, map_location=map_location, weights_only=weights_only)         return checkpoint      def remove_checkpoint(self, path: Union[str, Path]) -> None:+        """Remove an S3 checkpoint or delegate local removal to the base plugin."""         if S3Utils.is_s3_url(path):             S3Utils.remove_object(path)         else:             super().remove_checkpoint(path)      def teardown(self) -> None:-        # this ensure we wait for final checkpoint to finish uploading at train end.+        """Wait for uploads to finish and remove remaining temporary checkpoint files."""         rank = torch.distributed.get_rank()         if self.async_checkpointing:             logging.info(f'Entering teardown, waiting for all jobs to finish, rank {rank}')             start_time = time.perf_counter()             self._executor.shutdown(wait=True)             logging.info(f'executor shut down after {(time.perf_counter() - start_time):.2f} seconds, rank {rank}') -        '''-        This will be non-empty at the end of training with asynchronous uploading because the futures are not-        processed with _check_uploading_results_so_far. Therefore, check that the path exists before deleting.-        '''+        # Async upload futures may leave temporary files until teardown.         if self._temp_files:             for tfile in self._temp_files:                 if os.path.exists(tfile):
nemo/utils/exp_manager.py9 + / 16 −
@@ -36,7 +36,7 @@ from lightning.pytorch.callbacks import Callback, ModelCheckpoint from lightning.pytorch.callbacks.early_stopping import EarlyStopping from lightning.pytorch.callbacks.timer import Interval, Timer-from lightning.pytorch.loggers import MLFlowLogger, NeptuneLogger, TensorBoardLogger, WandbLogger+from lightning.pytorch.loggers import MLFlowLogger, TensorBoardLogger, WandbLogger from lightning.pytorch.loops import _TrainingEpochLoop from lightning.pytorch.strategies.ddp import DDPStrategy from lightning.pytorch.trainer.connectors.checkpoint_connector import _CheckpointConnector@@ -841,6 +841,11 @@ def error_checks(trainer: 'lightning.pytorch.Trainer', cfg: Optional[Union[DictC         - Prints error messages when 1) run on multi-node and not Slurm, and             2) run on multi-gpu without DDP     """+    if cfg.create_neptune_logger:+        raise ValueError(+            'create_neptune_logger is unsupported by the required Lightning version. '+            'Use another exp_manager logger or pass a supported logger to the Trainer.'+        )     if HydraConfig.initialized() and get_original_cwd() != os.getcwd():         raise ValueError(             "Hydra changed the working directory. This interferes with ExpManger's functionality."@@ -1317,19 +1322,7 @@ def configure_loggers(         logging.info("ClearMLLogger has been set up")      if create_neptune_logger:-        if neptune_kwargs is None:-            neptune_kwargs = {}-        if "name" not in neptune_kwargs and "project" not in neptune_kwargs:-            raise ValueError("name and project are required for neptune_logger")-        if "api_key" not in neptune_kwargs and not os.getenv("NEPTUNE_API_TOKEN", None):-            raise ValueError(-                "either api_key should be set in neptune_kwargs or NEPTUNE_API_TOKEN should "-                "be set in environment variable for neptune_logger"-            )-        neptune_logger = NeptuneLogger(**neptune_kwargs)--        logger_list.append(neptune_logger)-        logging.info("NeptuneLogger has been set up")+        raise ValueError('create_neptune_logger is unsupported by the required Lightning version.')      trainer._logger_connector.configure_logger(logger_list) @@ -1340,13 +1333,13 @@ class NeMoCheckpointConnector(_CheckpointConnector):     distributed training settings to pre-load checkpoint.     """ -    def resume_start(self, checkpoint_path=None) -> None:+    def resume_start(self, checkpoint_path=None, weights_only: Optional[bool] = None) -> None:         """resume_start"""         checkpoint_path = self.trainer.ckpt_path         if checkpoint_path is not None:             logging.info(f'Resuming from checkpoint {checkpoint_path}, rank {torch.distributed.get_rank()}')         start_time = time.perf_counter()-        super().resume_start(checkpoint_path)+        super().resume_start(checkpoint_path, weights_only=weights_only)         if checkpoint_path is not None:             logging.info(                 'Time elapsed loading checkpoint/optimizer states: '
nemo/utils/lightning_logger_patch.py54 + / 0 −
@@ -14,6 +14,7 @@ # limitations under the License.  import logging as _logging+from functools import wraps from logging.handlers import MemoryHandler  import lightning.pytorch as pl@@ -57,3 +58,56 @@ def add_filehandlers_to_pl_logger(all_log_file, err_log_file):         HANDLERS["memory_err"].setTarget(HANDLERS["file_err"])         HANDLERS["memory_err"].close()         del HANDLERS["memory_err"]+++def preserve_trainer_callback_defaults():+    """Keep pre-2.6 callback selection for Trainers in a NeMo process.++    NeMo entry points also construct Lightning Trainers directly, so configure+    defaults at the existing Lightning integration boundary. Explicit callbacks,+    disable flags and barebones mode retain Lightning's validation and behavior.+    """+    from importlib import import_module++    for namespace in ('lightning.pytorch', 'pytorch_lightning'):+        try:+            connector = import_module(f'{namespace}.trainer.connectors.callback_connector')+            callbacks = import_module(f'{namespace}.callbacks')+        except ModuleNotFoundError as error:+            if error.name != namespace.split('.')[0]:+                raise+            continue+        _preserve_callback_selection(connector._CallbackConnector, callbacks)+++def _preserve_callback_selection(connector, callbacks):+    """Install an idempotent adapter around one Lightning callback connector."""+    if getattr(connector, '_nemo_callback_defaults_preserved', False):+        return++    configure_progress = connector._configure_progress_bar+    configure_summary = connector._configure_model_summary_callback++    @wraps(configure_progress)+    def progress_bar(self, enable_progress_bar=True):+        """Supply the previous default while retaining upstream flag validation."""+        if enable_progress_bar and not any(isinstance(cb, callbacks.ProgressBar) for cb in self.trainer.callbacks):+            self.trainer.callbacks.append(callbacks.TQDMProgressBar())+        return configure_progress(self, enable_progress_bar)++    @wraps(configure_summary)+    def model_summary(self, enable_model_summary):+        """Match the old summary selection, including explicit Rich progress bars."""+        if enable_model_summary and not any(isinstance(cb, callbacks.ModelSummary) for cb in self.trainer.callbacks):+            summary_type = (+                callbacks.RichModelSummary+                if isinstance(self.trainer.progress_bar_callback, callbacks.RichProgressBar)+                else callbacks.ModelSummary+            )+            self.trainer.callbacks.append(summary_type())+            return None+        return configure_summary(self, enable_model_summary)++    connector._configure_progress_bar = progress_bar+    connector._configure_model_summary_callback = model_summary+    connector._nemo_callback_defaults_preserved = True
nemo/utils/serialization.pyadded125 + / 0 −
@@ -0,0 +1,125 @@+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.+# SPDX-License-Identifier: Apache-2.0+"""Checkpoint configuration types supported by the weights-only loader."""++from collections import defaultdict+from contextlib import contextmanager+from dataclasses import is_dataclass+from enum import Enum+from pathlib import Path, PosixPath, WindowsPath+from pickle import UnpicklingError+from typing import Any, Dict, List, Set, Tuple, Union, get_args, get_origin, get_type_hints++import torch+from omegaconf import DictConfig, ListConfig+from omegaconf.base import ContainerMetadata, Metadata, UnionNode+from omegaconf.nodes import (+    AnyNode,+    BooleanNode,+    BytesNode,+    EnumNode,+    FloatNode,+    IntegerNode,+    InterpolationResultNode,+    PathNode,+    StringNode,+    ValueNode,+)+++def checkpoint_config_types() -> list:+    """Return the fixed data types and restricted annotation loader used by checkpoints."""+    return [+        DictConfig,+        ListConfig,+        ContainerMetadata,+        Metadata,+        UnionNode,+        AnyNode,+        BooleanNode,+        BytesNode,+        EnumNode,+        FloatNode,+        IntegerNode,+        InterpolationResultNode,+        PathNode,+        StringNode,+        ValueNode,+        Path,+        PosixPath,+        WindowsPath,+        Any,+        Dict,+        List,+        Set,+        Tuple,+        Union,+        # Typing aliases pickle through _operator.getitem. The replacement below+        # accepts only fixed typing origins and type arguments, never arbitrary+        # object subscription (including OmegaConf interpolation resolution).+        (_restore_checkpoint_annotation, "_operator.getitem"),+        defaultdict,+        dict,+        list,+        tuple,+        int,+        float,+        str,+        bool,+        bytes,+    ]+++@contextmanager+def checkpoint_config_loading():+    """Add scoped configuration types while retaining existing process registrations."""+    existing = set(torch.serialization.get_safe_globals())+    additions = [entry for entry in checkpoint_config_types() if entry not in existing]+    with torch.serialization.safe_globals(additions):+        yield+++def register_checkpoint_config_types() -> None:+    """Register the fixed configuration container types used by Lightning checkpoints."""+    torch.serialization.add_safe_globals(checkpoint_config_types())+++def register_checkpoint_schema_types(*schemas: type) -> None:+    """Register source-defined dataclass schemas, nested schemas, and annotated enums.++    Call from the schema's module after its definitions. Only trusted source types+    are traversed; checkpoint contents never select imports or additional globals.+    """+    pending = list(schemas)+    registered = set()+    while pending:+        schema = pending.pop()+        arguments = get_args(schema)+        if arguments:+            pending.extend(arguments)+        elif isinstance(schema, type) and schema not in registered:+            if is_dataclass(schema):+                registered.add(schema)+                pending.extend(get_type_hints(schema).values())+            elif issubclass(schema, Enum):+                registered.add(schema)+    torch.serialization.add_safe_globals(list(registered))+++def _restore_checkpoint_annotation(origin, arguments):+    """Rebuild typing annotations without permitting general object item access."""+    if not any(origin is candidate for candidate in (Dict, List, Set, Tuple, Union)):+        raise UnpicklingError('Checkpoint item access is restricted to typing annotations.')+    items = arguments if isinstance(arguments, tuple) else (arguments,)+    if not all(_is_checkpoint_annotation(item) for item in items):+        raise UnpicklingError('Checkpoint annotation arguments must be types.')+    return origin[arguments]+++def _is_checkpoint_annotation(value):+    if value is None or value is Ellipsis or isinstance(value, type):+        return True+    origin = get_origin(value)+    return any(origin is candidate for candidate in (dict, list, set, tuple, Union)) and all(+        _is_checkpoint_annotation(argument) for argument in get_args(value)+    )
pyproject.toml32 + / 24 −
@@ -79,9 +79,10 @@ classifiers = [  [project.optional-dependencies] core = [-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -92,9 +93,10 @@ core = [ ]  lightning = [-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -147,9 +149,10 @@ tts = [     "whisper_normalizer",     "datasets>=3.2.0",     "sentencepiece<1.0.0",-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -171,9 +174,10 @@ audio = [     "datasets>=3.2.0",     "pandas",     "sentencepiece<1.0.0",-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -197,9 +201,10 @@ vllm = [ ]  all = [-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -281,9 +286,10 @@ common = [     "einops",     "pandas",     "sentencepiece<1.0.0",-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -307,9 +313,10 @@ asr = [     "datasets>=3.2.0",     "pandas",     "sentencepiece<1.0.0",-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",@@ -336,9 +343,10 @@ speechlm2 = [     "datasets>=3.2.0",     "pandas",     "sentencepiece<1.0.0",-    "hydra-core>1.3,<=1.3.2",-    "lightning>2.2.1,<=2.4.0",-    "omegaconf<=2.3",+    "hydra-core>=1.3.8,<1.4",+    "lightning>=2.6.6,<2.7",+    "pytorch-lightning>=2.6.6,<2.7",+    "omegaconf>=2.3,<2.4",     "torchmetrics>=0.11.0",     "transformers",     "wandb",
tests/collections/speechlm2/test_parallel.py10 + / 3 −
@@ -52,7 +52,8 @@ def test_default_init(self):         assert strategy.activation_checkpointing_llm is False         assert strategy.activation_checkpointing_perception is False -    def test_sharded_checkpoint_metadata_is_retained_after_return(self, monkeypatch, tmp_path):+    @pytest.mark.parametrize('weights_only', [None, False, True])+    def test_sharded_checkpoint_metadata_is_retained_after_return(self, monkeypatch, tmp_path, weights_only):         class CheckpointMetadata:             pass @@ -77,9 +78,15 @@ def read_metadata(self):         monkeypatch.setattr(dcp, "FileSystemReader", Reader)         monkeypatch.setattr(dcp, "load", lambda state, checkpoint_id: None)         monkeypatch.setattr(dcp_state_dict, "get_model_state_dict", lambda model: {})-        monkeypatch.setattr("torch.load", lambda path: CheckpointMetadata()) -        checkpoint = strategy.load_checkpoint(tmp_path)+        def load_metadata(path, *, weights_only=None):+            assert weights_only is policy+            return CheckpointMetadata()++        policy = weights_only+        monkeypatch.setattr("torch.load", load_metadata)++        checkpoint = strategy.load_checkpoint(tmp_path, weights_only=weights_only)         checkpoint_ref = weakref.ref(checkpoint)         del checkpoint         gc.collect()
tests/collections/speechlm2/test_salm_dflash.py4 + / 3 −
@@ -271,11 +271,12 @@ def test_expand_ids_with_audio_matches_unpad_behavior_for_all_padding_row():     assert expanded.tolist() == [[0, 0], [11, 12]]  -def test_device_falls_back_before_draft_configuration():-    module = salm_dflash.SALMDFlashModule(nn.Linear(1, 1), {"dflash": {"mask_token_id": 18}})+@pytest.mark.parametrize("device", ["cpu", "meta"])+def test_device_falls_back_before_draft_configuration(device):+    module = salm_dflash.SALMDFlashModule(nn.Linear(1, 1, device=device), {"dflash": {"mask_token_id": 18}}).to(device)      assert module.draft_model is None-    assert module.device == torch.device("cpu")+    assert module.device == next(module.target.parameters()).device   class _BatchTarget(nn.Module):
tests/core/test_dependency_compatibility.pyadded336 + / 0 −
@@ -0,0 +1,336 @@+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.+# SPDX-License-Identifier: Apache-2.0+"""Compatibility coverage for dependency imports and checkpoint configuration."""++import io+import operator+import pickle+import subprocess+import sys+from dataclasses import dataclass+from importlib import import_module+from unittest.mock import patch++import pytest+import torch+from omegaconf import OmegaConf+++@pytest.mark.unit+@pytest.mark.parametrize('module', ['nemo.core', 'nemo.utils.exp_manager'])+def test_dependency_imports(module):+    result = subprocess.run([sys.executable, '-c', f'import {module}'], capture_output=True, text=True, timeout=60)+    assert result.returncode == 0, result.stderr+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+@pytest.mark.parametrize('schema', ['untyped', 'ctc', 'ctc_bpe', 'rnnt', 'rnnt_bpe'])+def test_checkpoint_configuration_loads_with_weights_only(tmp_path, namespace, schema):+    lightning = import_module(namespace)++    class Minimal(lightning.LightningModule):+        def __init__(self, cfg):+            super().__init__()+            self.cfg = cfg+            self.weight = torch.nn.Parameter(torch.zeros(2))++    config = OmegaConf.create({'name': 'model', 'layers': [2, 4], 'enabled': True, 'dropout': 0.1, 'optional': None})+    if schema != 'untyped':+        from nemo.collections.asr.parts.submodules.ctc_decoding import CTCBPEDecodingConfig, CTCDecodingConfig+        from nemo.collections.asr.parts.submodules.rnnt_decoding import RNNTBPEDecodingConfig, RNNTDecodingConfig++        schemas = dict(+            ctc=CTCDecodingConfig,+            ctc_bpe=CTCBPEDecodingConfig,+            rnnt=RNNTDecodingConfig,+            rnnt_bpe=RNNTBPEDecodingConfig,+        )+        config.decoding = OmegaConf.structured(schemas[schema])+    path = tmp_path / 'model.ckpt'+    torch.save(+        {+            'pytorch-lightning_version': lightning.__version__,+            'hyper_parameters': {'cfg': config},+            'state_dict': {'weight': torch.ones(2)},+        },+        path,+    )+    import nemo.utils  # noqa: F401++    restored = Minimal.load_from_checkpoint(path, weights_only=True)+    assert OmegaConf.to_container(restored.cfg) == OmegaConf.to_container(config)+    assert torch.equal(restored.weight, torch.ones(2))+++@pytest.mark.unit+def test_weights_only_still_rejects_unregistered_objects():+    stream = io.BytesIO()+    torch.save(_UnregisteredObject(), stream)+    stream.seek(0)+    import nemo.utils  # noqa: F401++    with pytest.raises(pickle.UnpicklingError):+        torch.load(stream, weights_only=True)+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+def test_checkpoint_instantiators_keep_the_lightning_policy(tmp_path, namespace):+    lightning = import_module(namespace)+    cli = import_module(f'{namespace}.cli')++    class Minimal(lightning.LightningModule):+        pass++    checkpoint = {+        'state_dict': {},+        'hyper_parameters': {'_instantiator': 'builtins.print'},+        'pytorch-lightning_version': lightning.__version__,+    }+    path = tmp_path / 'model.ckpt'+    torch.save(checkpoint, path)+    with patch('builtins.print') as instantiate:+        with pytest.raises(ValueError, match='allowlist'):+            Minimal.load_from_checkpoint(path, weights_only=True)+        instantiate.assert_not_called()+    checkpoint['hyper_parameters']['_instantiator'] = f'{namespace}.cli.instantiate_module'+    torch.save(checkpoint, path)+    with patch.object(cli, 'instantiate_module', side_effect=lambda cls, kwargs: cls()) as instantiate:+        assert isinstance(Minimal.load_from_checkpoint(path, weights_only=True), Minimal)+        instantiate.assert_called_once_with(Minimal, {})+++@pytest.mark.unit+def test_default_lifecycle_callbacks_do_not_import_telemetry():+    script = r"""+import importlib.abc+import sys++class BlockTelemetry(importlib.abc.MetaPathFinder):+    def find_spec(self, fullname, path=None, target=None):+        if fullname.startswith('nv_one_logger'):+            raise AssertionError('telemetry imported')++sys.meta_path.insert(0, BlockTelemetry())+import nemo.core+import nemo.utils.exp_manager+from nemo.lightning.base_callback import BaseCallback+from nemo.lightning.callback_group import CallbackGroup++class CustomCallback(BaseCallback):+    calls = 0+    def on_model_init_start(self):+        self.calls += 1++group = CallbackGroup()+assert group.callbacks == []+callback = CustomCallback()+group.register(callback)+group.on_model_init_start()+assert callback.calls == 1+assert not any(name.startswith('nv_one_logger') for name in sys.modules)+"""+    result = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True, timeout=60)+    assert result.returncode == 0, result.stdout + result.stderr+++@pytest.mark.unit+def test_ctc_model_default_configuration_checkpoint(tmp_path):+    from nemo.collections.asr.models import EncDecCTCModel+    from nemo.collections.asr.parts.submodules.ctc_decoding import CTCDecodingConfig++    cfg = OmegaConf.create(+        {+            'preprocessor': {+                '_target_': 'nemo.collections.asr.modules.AudioToMelSpectrogramPreprocessor',+                'features': 4,+            },+            'encoder': {+                '_target_': 'nemo.collections.asr.modules.ConvASREncoder',+                'feat_in': 4,+                'activation': 'relu',+                'jasper': [+                    {+                        'filters': 4,+                        'repeat': 1,+                        'kernel': [1],+                        'stride': [1],+                        'dilation': [1],+                        'dropout': 0.0,+                        'residual': False,+                    }+                ],+            },+            'decoder': {+                '_target_': 'nemo.collections.asr.modules.ConvASRDecoder',+                'feat_in': 4,+                'num_classes': 2,+                'vocabulary': ['a', 'b'],+            },+        }+    )+    assert 'decoding' not in cfg+    model = EncDecCTCModel(cfg)+    assert OmegaConf.get_type(model.cfg.decoding) is CTCDecodingConfig+    path = tmp_path / 'ctc.ckpt'+    torch.save(+        {+            'state_dict': model.state_dict(),+            'hyper_parameters': {'cfg': model.cfg},+            'pytorch-lightning_version': import_module('lightning.pytorch').__version__,+        },+        path,+    )+    restored = EncDecCTCModel.load_from_checkpoint(path, map_location='cpu', weights_only=True)+    assert OmegaConf.to_container(restored.cfg, resolve=True, enum_to_str=True) == OmegaConf.to_container(+        model.cfg, resolve=True, enum_to_str=True+    )+    for name, value in model.state_dict().items():+        torch.testing.assert_close(restored.state_dict()[name], value)+++@pytest.mark.unit+def test_checkpoint_annotations_do_not_resolve_config_values(tmp_path):+    calls = []+    OmegaConf.register_new_resolver('checkpoint_probe', lambda: calls.append('resolved'))+    try:+        cfg = OmegaConf.create({'value': '${checkpoint_probe:}'})+        path = tmp_path / 'annotation.ckpt'+        torch.save(_ItemAccess(cfg), path)+        with pytest.raises(pickle.UnpicklingError, match='typing annotations'):+            torch.load(path, weights_only=True)+        assert calls == []+    finally:+        OmegaConf.clear_resolver('checkpoint_probe')+++@pytest.mark.unit+def test_unregistered_structured_metadata_is_rejected(tmp_path):+    path = tmp_path / 'unknown.ckpt'+    torch.save(OmegaConf.structured(_UnregisteredSchema), path)+    with pytest.raises(pickle.UnpicklingError):+        torch.load(path, weights_only=True)+++@pytest.mark.unit+def test_configuration_registration_keeps_asr_imports_lazy():+    script = "import sys; import nemo.utils; assert not any(n.startswith('nemo.collections.asr') for n in sys.modules)"+    result = subprocess.run([sys.executable, '-c', script], capture_output=True, text=True, timeout=60)+    assert result.returncode == 0, result.stderr+++@dataclass+class _UnregisteredSchema:+    name: str = 'model'+++class _ItemAccess:+    def __init__(self, cfg):+        self.cfg = cfg++    def __reduce__(self):+        return operator.getitem, (self.cfg, 'value')+++class _UnregisteredObject:+    pass+++@pytest.mark.unit+@pytest.mark.parametrize('schema', ['aed', 'adapter', 'path'])+def test_additional_legacy_checkpoint_configurations(tmp_path, schema):+    from pathlib import Path++    from nemo.collections.asr.parts.submodules.multitask_decoding import MultiTaskDecodingConfig+    from nemo.collections.common.parts.adapter_modules import LinearAdapterConfig++    configs = {+        'aed': OmegaConf.structured(MultiTaskDecodingConfig),+        'adapter': OmegaConf.structured(LinearAdapterConfig(in_features=50, dim=8)),+        'path': OmegaConf.create({'path': Path('models/checkpoint')}),+    }+    path = tmp_path / 'legacy.ckpt'+    torch.save(configs[schema], path)+    restored = torch.load(path, weights_only=True)+    assert OmegaConf.to_container(restored) == OmegaConf.to_container(configs[schema])+++@pytest.mark.unit+def test_aed_default_config_checkpoint(tmp_path, test_data_dir):+    from nemo.collections.asr.parts.submodules.multitask_decoding import MultiTaskDecodingConfig+    from tests.collections.asr.test_asr_multitask_model_bpe import asr_model++    initial = asr_model.__wrapped__(test_data_dir)+    cfg = OmegaConf.create(OmegaConf.to_container(initial.cfg))+    del cfg.decoding+    model = type(initial)(cfg)+    assert OmegaConf.get_type(model.cfg.decoding) is MultiTaskDecodingConfig+    _assert_model_checkpoint_roundtrip(model, tmp_path / 'aed.ckpt')+++@pytest.mark.unit+def test_adapter_model_checkpoint_preserves_live_schema(tmp_path):+    from nemo.collections.common.parts.adapter_modules import LinearAdapterConfig+    from tests.core.mixins.adapters.test_adapter_model_mixin import DefaultAdapterModel, get_model_config++    model = DefaultAdapterModel(get_model_config())+    model.add_adapter('linear', LinearAdapterConfig(in_features=50, dim=8))+    assert model.is_adapter_available()+    assert OmegaConf.get_type(model.hparams.cfg.adapters.linear) is LinearAdapterConfig+    _assert_model_checkpoint_roundtrip(model, tmp_path / 'adapter.ckpt')+++@pytest.mark.unit+def test_application_schema_is_preserved_in_model_hparams(tmp_path):+    from omegaconf import open_dict++    from tests.core.test_save_restore import MockModel++    model = MockModel(OmegaConf.create({}))+    with open_dict(model.cfg):+        model.cfg.custom = OmegaConf.structured(_UnregisteredSchema)+        model.cfg.alias = '${custom.name}'+    assert OmegaConf.get_type(model.cfg.custom) is _UnregisteredSchema+    assert OmegaConf.get_type(model.hparams.cfg.custom) is _UnregisteredSchema+    assert OmegaConf.to_container(model.cfg, resolve=False)['alias'] == '${custom.name}'+    _assert_model_checkpoint_roundtrip(model, tmp_path / 'application.ckpt')+++def _assert_model_checkpoint_roundtrip(model, path):+    lightning = import_module('lightning.pytorch')+    trainer = lightning.Trainer(+        default_root_dir=path.parent,+        accelerator='cpu',+        logger=False,+        enable_checkpointing=False,+        enable_progress_bar=False,+        enable_model_summary=False,+    )+    model.set_trainer(trainer)+    trainer.strategy.connect(model)+    trainer.save_checkpoint(path, weights_only=True)+    saved = torch.load(path, map_location='cpu', weights_only=True)+    assert OmegaConf.get_type(saved['hyper_parameters']['cfg']) is dict+    restored = type(model).load_from_checkpoint(path, map_location='cpu', weights_only=True)+    assert OmegaConf.to_container(restored.cfg, resolve=True, enum_to_str=True) == OmegaConf.to_container(+        model.cfg, resolve=True, enum_to_str=True+    )+    for name, value in model.state_dict().items():+        torch.testing.assert_close(restored.state_dict()[name], value)+++@pytest.mark.unit+def test_checkpoint_snapshot_preserves_supported_enum_and_path_values(tmp_path):+    from pathlib import Path++    from nemo.collections.asr.parts.submodules.rnnt_decoding import RNNTDecodingConfig  # noqa: F401+    from nemo.collections.asr.parts.utils.batched_beam_decoding_utils import PruningMode+    from tests.core.test_save_restore import MockModel++    model = MockModel(OmegaConf.create({'mode': PruningMode.EARLY, 'path': Path('models/checkpoint')}))+    path = tmp_path / 'values.ckpt'+    _assert_model_checkpoint_roundtrip(model, path)+    restored = MockModel.load_from_checkpoint(path, map_location='cpu', weights_only=True)+    assert restored.cfg.mode is PruningMode.EARLY+    assert restored.cfg.path == model.cfg.path == Path('models/checkpoint')
tests/core/test_exp_manager.py9 + / 24 −
@@ -210,34 +210,19 @@ def test_trainer_loggers(self, tmp_path):         assert isinstance(test_trainer.logger, pl.loggers.WandbLogger)      @pytest.mark.unit-    def test_trainer_neptune_logger(self, tmp_path):-        pytest.importorskip("neptune", reason="could not import `neptune`, use `pip install neptune` to run this test")--        test_trainer = pl.Trainer(accelerator='cpu', logger=False)-        # Check that a create_neptune_logger=True errors out unless neptune_logger_kwargs is passed.-        with pytest.raises(ValueError):-            _ = exp_manager(-                test_trainer,+    @pytest.mark.parametrize('kwargs', [None, {"name": "test", "project": "test", "api_key": "unused"}])+    def test_trainer_neptune_logger(self, tmp_path, kwargs):+        trainer = pl.Trainer(accelerator='cpu', logger=False, enable_checkpointing=False)+        with pytest.raises(ValueError, match='create_neptune_logger.*Lightning'):+            exp_manager(+                trainer,                 {-                    "create_tensorboard_logger": False,-                    "create_checkpoint_callback": False,-                    "exp_dir": str(tmp_path),+                    "explicit_log_dir": str(tmp_path / 'experiment'),                     "create_neptune_logger": True,+                    "neptune_logger_kwargs": kwargs,                 },             )-        # Check that a NeptuneLogger is attached to logger if create_neptune_logger=True and neptune_logger_kwargs has name-        # and project-        _ = exp_manager(-            test_trainer,-            {-                "create_tensorboard_logger": False,-                "create_checkpoint_callback": False,-                "exp_dir": str(tmp_path),-                "create_neptune_logger": True,-                "neptune_logger_kwargs": {"name": "", "project": "", "api_key": ""},-            },-        )-        assert isinstance(test_trainer.logger, pl.loggers.NeptuneLogger)+        assert not (tmp_path / 'experiment').exists()      @pytest.mark.unit     def test_checkpoint_configurations(self):
tests/core/test_lightning_checkpoint_compatibility.pyadded294 + / 0 −
@@ -0,0 +1,294 @@+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.+# SPDX-License-Identifier: Apache-2.0+"""Resume and callback compatibility with the supported Lightning release."""++import copy+import io+from pathlib import Path+from types import SimpleNamespace+from unittest.mock import Mock, patch++import lightning.pytorch as pl+import pytest+import torch+from lightning.fabric.plugins import TorchCheckpointIO+from lightning.pytorch.plugins.io.wrapper import _WrappingCheckpointIO+from lightning.pytorch.strategies import ModelParallelStrategy+from lightning.pytorch.trainer.connectors.checkpoint_connector import _CheckpointConnector+from lightning.pytorch.trainer.states import TrainerFn+from torch.utils.data import DataLoader++from nemo.collections.common.callbacks import EMA+from nemo.collections.speechlm2.parts.parallel import AutomodelParallelStrategy+from nemo.utils.callbacks.dist_ckpt_io import AsyncFinalizableCheckpointIO+from nemo.utils.callbacks.nemo_model_checkpoint import NeMoModelCheckpoint+from nemo.utils.exp_manager import NeMoCheckpointConnector, exp_manager+++@pytest.mark.unit+@pytest.mark.parametrize('async_save', [False, True])+@pytest.mark.parametrize('at_epoch_end', [False, True])+def test_last_checkpoint_advances_when_metric_plateaus(tmp_path, async_save, at_epoch_end):+    checkpoint = NeMoModelCheckpoint(+        dirpath=tmp_path,+        filename='{step}',+        monitor='val_loss',+        save_top_k=1,+        save_last=True,+        save_on_train_epoch_end=at_epoch_end,+        async_save=async_save,+        save_nemo_on_train_end=False,+    )+    deferred = _DeferredCheckpointIO()+    trainer = pl.Trainer(+        default_root_dir=tmp_path,+        accelerator='cpu',+        devices=1,+        max_epochs=2,+        callbacks=[checkpoint],+        plugins=[deferred] if async_save else None,+        logger=False,+        enable_progress_bar=False,+        enable_model_summary=False,+        num_sanity_val_steps=0,+    )+    trainer.fit(_PlateauModel())+    if async_save:+        # Scheduling updates Lightning bookkeeping; completion must not rewind it.+        assert checkpoint._last_global_step_saved == trainer.global_step+        deferred.flush()+        assert checkpoint._last_global_step_saved == trainer.global_step+    saved = torch.load(checkpoint.last_model_path, weights_only=True)+    assert saved['global_step'] == trainer.global_step == 8+    assert torch.load(checkpoint.best_model_path, weights_only=True)['global_step'] == 4+++@pytest.mark.unit+@pytest.mark.parametrize('weights_only', [None, False, True])+def test_automodel_checkpoint_forwards_load_policy(tmp_path, weights_only):+    strategy = AutomodelParallelStrategy()+    strategy.broadcast = lambda path: path+    with patch.object(ModelParallelStrategy, 'load_checkpoint', return_value={'global_step': 7}) as load:+        assert strategy.load_checkpoint(tmp_path / 'model.ckpt', weights_only=weights_only) == {'global_step': 7}+    load.assert_called_once_with(tmp_path / 'model.ckpt', weights_only=weights_only)+++@pytest.mark.unit+def test_resume_connector_forwards_load_policy():+    trainer = SimpleNamespace(ckpt_path=None)+    connector = NeMoCheckpointConnector(trainer)+    with patch.object(_CheckpointConnector, 'resume_start') as resume:+        connector.resume_start(weights_only=True)+    resume.assert_called_once_with(None, weights_only=True)+++@pytest.mark.unit+@pytest.mark.parametrize('shared_memory', [False, True])+@pytest.mark.parametrize('weights_only', [False, True])+def test_s3_checkpoint_load_policy_and_map_location(tmp_path, monkeypatch, shared_memory, weights_only):+    pytest.importorskip('boto3')+    from nemo.utils.callbacks import s3_checkpoint_io as module++    path = tmp_path / 'model.ckpt'+    torch.save({'tensor': torch.ones(2)}, path)+    monkeypatch.setattr(module, 'SHARED_MEM_DIR', str(tmp_path if shared_memory else tmp_path / 'missing'))+    monkeypatch.setattr(torch.distributed, 'get_rank', lambda: 0)+    monkeypatch.setattr(module.S3Utils, 'download_s3_file_to_stream', lambda **kw: io.BytesIO(path.read_bytes()))+    monkeypatch.setattr(+        module.S3Utils, 'download_s3_file_to_path', lambda **kw: Path(kw['file_path']).write_bytes(path.read_bytes())+    )+    checkpoint_io = module.S3CheckpointIO('s3://bucket')+    map_location = Mock(side_effect=lambda storage, loc: storage)+    with patch.object(module.torch, 'load', wraps=torch.load) as load:+        loaded = checkpoint_io.load_checkpoint(+            's3://bucket/model.ckpt', map_location=map_location, weights_only=weights_only+        )+    assert torch.equal(loaded['tensor'], torch.ones(2))+    assert load.call_args.kwargs['weights_only'] is weights_only+    assert load.call_args.kwargs['map_location'] is map_location+    map_location.assert_called()+++@pytest.mark.unit+@pytest.mark.parametrize('stage', [TrainerFn.VALIDATING, TrainerFn.TESTING, TrainerFn.PREDICTING])+def test_ema_evaluation_without_sidecar(tmp_path, stage):+    trainer = pl.Trainer(+        default_root_dir=tmp_path,+        accelerator='cpu',+        logger=False,+        enable_progress_bar=False,+        enable_model_summary=False,+        callbacks=[NeMoModelCheckpoint(dirpath=tmp_path)],+    )+    trainer.state.fn = stage+    trainer.ckpt_path = str(tmp_path / 'model.ckpt')+    checkpoint = {'optimizer_states': ['unchanged']}+    EMA(decay=0.9).on_load_checkpoint(trainer, pl.LightningModule(), checkpoint)+    assert checkpoint['optimizer_states'] == ['unchanged']+++@pytest.mark.unit+def test_neptune_rejected_before_creating_experiment(tmp_path):+    trainer = pl.Trainer(default_root_dir=tmp_path, accelerator='cpu', logger=False, enable_checkpointing=False)+    directory = tmp_path / 'experiment'+    with pytest.raises(ValueError, match='create_neptune_logger.*Lightning'):+        exp_manager(trainer, {'explicit_log_dir': str(directory), 'create_neptune_logger': True})+    assert not directory.exists()+++class _PlateauModel(pl.LightningModule):+    def __init__(self):+        super().__init__()+        self.weight = torch.nn.Parameter(torch.ones(()))++    def training_step(self, batch, batch_idx):+        return self.weight.square()++    def validation_step(self, batch, batch_idx):+        self.log('val_loss', 1.0)++    def configure_optimizers(self):+        return torch.optim.SGD(self.parameters(), lr=0.1)++    def train_dataloader(self):+        return DataLoader(torch.ones(4), batch_size=1)++    def val_dataloader(self):+        return DataLoader(torch.ones(1), batch_size=1)+++class _DeferredCheckpointIO(AsyncFinalizableCheckpointIO):+    def __init__(self):+        _WrappingCheckpointIO.__init__(self, TorchCheckpointIO())+        self.pending = []++    def save_checkpoint(self, checkpoint, path, storage_options=None):+        self.pending.append((copy.deepcopy(checkpoint), path, storage_options['finalize_fn']))++    def flush(self):+        for checkpoint, path, finalize in self.pending:+            self.checkpoint_io.save_checkpoint(checkpoint, path)+            finalize()+        self.pending.clear()++    def teardown(self):+        pass+++@pytest.mark.unit+def test_automodel_resume_connector_restores_metadata(tmp_path):+    strategy = AutomodelParallelStrategy()+    strategy.broadcast = lambda path: path+    connector = _CheckpointConnector(SimpleNamespace(strategy=strategy))+    state = {'global_step': 7, 'pytorch-lightning_version': pl.__version__}+    with patch.object(ModelParallelStrategy, 'load_checkpoint', return_value=state) as load:+        connector.resume_start(tmp_path / 'model.ckpt', weights_only=True)+    assert connector._loaded_checkpoint['global_step'] == 7+    load.assert_called_once_with(tmp_path / 'model.ckpt', weights_only=True)+++@pytest.mark.unit+@pytest.mark.parametrize('sidecar', ['valid', 'missing', 'unsupported'])+def test_ema_fit_sidecar_policy(tmp_path, sidecar):+    import pickle+    from lightning.pytorch.utilities.exceptions import MisconfigurationException++    trainer = pl.Trainer(+        default_root_dir=tmp_path,+        accelerator='cpu',+        logger=False,+        enable_progress_bar=False,+        enable_model_summary=False,+        callbacks=[NeMoModelCheckpoint(dirpath=tmp_path)],+    )+    trainer.state.fn = TrainerFn.FITTING+    trainer.ckpt_path = str(tmp_path / 'model.ckpt')+    if sidecar != 'missing':+        value = torch.ones(2) if sidecar == 'valid' else _UnsupportedValue()+        torch.save({'optimizer_states': [value]}, tmp_path / 'model-EMA.ckpt')+    checkpoint = {'optimizer_states': ['unchanged']}+    if sidecar == 'valid':+        EMA(decay=0.9).on_load_checkpoint(trainer, pl.LightningModule(), checkpoint)+        torch.testing.assert_close(checkpoint['optimizer_states'][0], torch.ones(2))+    else:+        error = MisconfigurationException if sidecar == 'missing' else pickle.UnpicklingError+        with pytest.raises(error):+            EMA(decay=0.9).on_load_checkpoint(trainer, pl.LightningModule(), checkpoint)+        assert checkpoint['optimizer_states'] == ['unchanged']+++class _UnsupportedValue:+    pass+++@pytest.mark.unit+@pytest.mark.parametrize('accumulate', [1, 2])+def test_default_resume_matches_previous_lightning_behavior(tmp_path, accumulate):+    def make_trainer(directory, steps):+        trainer = pl.Trainer(+            default_root_dir=directory,+            accelerator='cpu',+            devices=1,+            logger=False,+            enable_checkpointing=False,+            enable_progress_bar=False,+            enable_model_summary=False,+            max_steps=steps,+            max_epochs=3,+            val_check_interval=2,+            num_sanity_val_steps=0,+            accumulate_grad_batches=accumulate,+        )+        exp_manager(+            trainer,+            {+                'explicit_log_dir': str(directory),+                'create_tensorboard_logger': False,+                'create_preemption_callback': False,+                'checkpoint_callback_params': {+                    'monitor': 'val_loss',+                    'save_top_k': 1,+                    'save_last': True,+                    'save_nemo_on_train_end': False,+                },+            },+        )+        return trainer++    first = make_trainer(tmp_path / 'first', 2)+    first.fit(_ResumeModel())+    resumed = make_trainer(tmp_path / 'resumed', 4)+    model = _ResumeModel()+    resumed.fit(model, ckpt_path=first.checkpoint_callback.last_model_path)+    # Reference traces independently compared with Lightning 2.4. The default+    # non-stateful dataloader restarts its iterator in both releases.+    assert model.seen == ([1, 2] if accumulate == 1 else [1, 2, 3, 4])+    assert model.validation_steps == ([4] if accumulate == 1 else [3, 4])+    assert resumed.global_step == resumed.fit_loop.epoch_loop._batches_that_stepped == 4+    assert resumed.optimizers[0].param_groups[0]['lr'] == pytest.approx(0.06561)+    assert model.weight.item() == pytest.approx(0.24586007 if accumulate == 1 else 0.23775484)+++class _ResumeModel(_PlateauModel):+    def __init__(self):+        super().__init__()+        self.weight.data.fill_(0.25)+        self.seen = []+        self.validation_steps = []++    def training_step(self, batch, batch_idx):+        self.seen.append(int(batch.item()))+        return (self.weight * batch / 10).square().mean()++    def on_validation_epoch_end(self):+        self.validation_steps.append(self.global_step)++    def configure_optimizers(self):+        optimizer = torch.optim.SGD(self.parameters(), lr=0.1)+        return {+            'optimizer': optimizer,+            'lr_scheduler': {'scheduler': torch.optim.lr_scheduler.StepLR(optimizer, 1, 0.9), 'interval': 'step'},+        }++    def train_dataloader(self):+        return DataLoader(torch.arange(1, 9, dtype=torch.float32), batch_size=1)
tests/lightning/test_one_logger_callback.py9 + / 0 −
@@ -16,6 +16,7 @@ """Unit tests for OneLoggerNeMoCallback."""  import os+from importlib import import_module from types import SimpleNamespace from unittest.mock import MagicMock, patch @@ -24,6 +25,14 @@ from lightning.pytorch.callbacks import ModelCheckpoint from omegaconf import OmegaConf +# The legacy integration is inactive; retain these tests for compatible environments.+try:+    import_module('nemo.lightning.one_logger_callback')+except TypeError as error:+    if 'OneLoggerPTLTrainer.save_checkpoint: weights_only' not in str(error):+        raise+    pytest.skip('Legacy telemetry integration requires an update for this Lightning version', allow_module_level=True)+ from nemo.lightning.base_callback import BaseCallback from nemo.lightning.one_logger_callback import (     OneLoggerNeMoCallback,
tests/utils/test_lightning_callback_defaults.pyadded86 + / 0 −
@@ -0,0 +1,86 @@+# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.+# SPDX-License-Identifier: Apache-2.0+"""NeMo preserves the callback defaults used before the Lightning upgrade."""++from importlib import import_module++import pytest++from nemo.utils.lightning_logger_patch import preserve_trainer_callback_defaults+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+@pytest.mark.parametrize('flags', [{}, {'enable_progress_bar': None, 'enable_model_summary': None}])+def test_default_callbacks_are_tqdm_and_plain_summary(tmp_path, namespace, flags):+    lightning = import_module(namespace)+    callbacks = import_module(f'{namespace}.callbacks')+    trainer = lightning.Trainer(default_root_dir=tmp_path, accelerator='cpu', logger=False, **flags)+    assert type(trainer.progress_bar_callback) is callbacks.TQDMProgressBar+    assert (+        type(next(cb for cb in trainer.callbacks if isinstance(cb, callbacks.ModelSummary))) is callbacks.ModelSummary+    )+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+@pytest.mark.parametrize('option', ['enable_progress_bar', 'enable_model_summary', 'barebones'])+def test_disabled_callbacks_stay_disabled(tmp_path, namespace, option):+    lightning = import_module(namespace)+    callbacks = import_module(f'{namespace}.callbacks')+    flags = {option: option == 'barebones'}+    trainer = lightning.Trainer(default_root_dir=tmp_path, accelerator='cpu', logger=False, **flags)+    if option in ('enable_progress_bar', 'barebones'):+        assert trainer.progress_bar_callback is None+    if option in ('enable_model_summary', 'barebones'):+        assert not any(isinstance(cb, callbacks.ModelSummary) for cb in trainer.callbacks)+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+@pytest.mark.parametrize('rich', [False, True])+def test_explicit_progress_bar_keeps_old_summary_selection(tmp_path, namespace, rich):+    pytest.importorskip('rich')+    lightning = import_module(namespace)+    callbacks = import_module(f'{namespace}.callbacks')+    progress = callbacks.RichProgressBar() if rich else callbacks.TQDMProgressBar(refresh_rate=7)+    trainer = lightning.Trainer(default_root_dir=tmp_path, accelerator='cpu', logger=False, callbacks=[progress])+    assert trainer.progress_bar_callback is progress+    summary = next(cb for cb in trainer.callbacks if isinstance(cb, callbacks.ModelSummary))+    assert type(summary) is (callbacks.RichModelSummary if rich else callbacks.ModelSummary)+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+@pytest.mark.parametrize('rich', [False, True])+def test_explicit_summary_is_retained(tmp_path, namespace, rich):+    pytest.importorskip('rich')+    lightning = import_module(namespace)+    callbacks = import_module(f'{namespace}.callbacks')+    summary = callbacks.RichModelSummary(max_depth=3) if rich else callbacks.ModelSummary(max_depth=3)+    trainer = lightning.Trainer(default_root_dir=tmp_path, accelerator='cpu', logger=False, callbacks=[summary])+    assert [cb for cb in trainer.callbacks if isinstance(cb, callbacks.ModelSummary)] == [summary]+++@pytest.mark.unit+def test_callback_patch_is_idempotent():+    from lightning.pytorch.trainer.connectors.callback_connector import _CallbackConnector++    before = _CallbackConnector._configure_progress_bar+    preserve_trainer_callback_defaults()+    preserve_trainer_callback_defaults()+    assert _CallbackConnector._configure_progress_bar is before+++@pytest.mark.unit+@pytest.mark.parametrize('namespace', ['lightning.pytorch', 'pytorch_lightning'])+def test_explicit_progress_bar_validation_is_not_bypassed(tmp_path, namespace):+    lightning = import_module(namespace)+    callbacks = import_module(f'{namespace}.callbacks')+    exceptions = import_module(f'{namespace}.utilities.exceptions')+    for flags, bars in [+        ({'enable_progress_bar': False}, [callbacks.TQDMProgressBar()]),+        ({}, [callbacks.TQDMProgressBar(), callbacks.TQDMProgressBar()]),+    ]:+        with pytest.raises(exceptions.MisconfigurationException):+            lightning.Trainer(default_root_dir=tmp_path, accelerator='cpu', logger=False, callbacks=bars, **flags)
uv.lock49 + / 33 −
@@ -4241,16 +4241,16 @@ cu13 = [  [[package]] name = "hydra-core"-version = "1.3.2"+version = "1.3.8" source = { registry = "https://pypi.org/simple" } dependencies = [     { name = "antlr4-python3-runtime" },     { name = "omegaconf" },     { name = "packaging" }, ]-sdist = { url = "https://files.pythonhosted.org/packages/6d/8e/07e42bc434a847154083b315779b0a81d567154504624e181caf2c71cd98/hydra-core-1.3.2.tar.gz", hash = "sha256:8a878ed67216997c3e9d88a8e72e7b4767e81af37afb4ea3334b269a4390a824", size = 3263494, upload-time = "2023-02-23T18:33:43.03Z" }+sdist = { url = "https://files.pythonhosted.org/packages/7c/1d/e1ad315ca93f1b090b0782942535fea8344e8b2d749c92117b2bf45f4407/hydra_core-1.3.8.tar.gz", hash = "sha256:18d5dfb841734a936507e75e7b8390b406ed22494f7fb8b4b7b7c122102de3d5", size = 3278858, upload-time = "2026-10-25T13:42:22.491Z" } wheels = [-    { url = "https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl", hash = "sha256:fa0238a9e31df3373b35b0bfb672c34cc92718d21f81311d8996a16de1141d8b", size = 154547, upload-time = "2023-02-23T18:33:40.801Z" },+    { url = "https://files.pythonhosted.org/packages/13/69/8e5cf7ad847538fc180fbd06e8442b8bede53ee4852458a16e24231395e9/hydra_core-1.3.8-py3-none-any.whl", hash = "sha256:d5f184c291691cba835c7f04bc4e36270ad88b1d7e394a3712f5af084c30b394", size = 168949, upload-time = "2026-10-25T13:42:20.959Z" }, ]  [[package]]@@ -5120,7 +5120,7 @@ wheels = [  [[package]] name = "lightning"-version = "2.4.0"+version = "2.6.6" source = { registry = "https://pypi.org/simple" } dependencies = [     { name = "fsspec", extra = ["http"] },@@ -5137,9 +5137,9 @@ dependencies = [     { name = "tqdm" },     { name = "typing-extensions" }, ]-sdist = { url = "https://files.pythonhosted.org/packages/56/d0/78ea244ac044cd4df15aa8294a50ff3561fb177e7e5ba788aaa542046cae/lightning-2.4.0.tar.gz", hash = "sha256:9156604cc56e4b2b603f34fa7f0fe5107375c8e6d85e74544b319a15faa9ed0e", size = 620632, upload-time = "2024-08-07T09:46:44.399Z" }+sdist = { url = "https://files.pythonhosted.org/packages/39/b8/fbf1469d917c3ed3b22e00ab3a852fb9a5f85b9dc7f7060eedc3449098e7/lightning-2.6.6.tar.gz", hash = "sha256:1c6329e241f8a85f29fedead89c1ee138b36caa08de60c260c6a7eaee81c8295", size = 659902, upload-time = "2026-09-10T09:41:03.009Z" } wheels = [-    { url = "https://files.pythonhosted.org/packages/a3/2c/85eaf42c983b0cd81bcda5876da2c8e2a9fd347908666ea9855724369171/lightning-2.4.0-py3-none-any.whl", hash = "sha256:560163af9711cf59055c448232c473150a299089efce0d2be3cc3288082d8768", size = 810971, upload-time = "2024-08-07T09:46:39.874Z" },+    { url = "https://files.pythonhosted.org/packages/12/43/7da7fe42f254ce57777d1a86eece3f86d1b3329fc568d48e2bc16c8b7ebc/lightning-2.6.6-py3-none-any.whl", hash = "sha256:2bcbd6ee840071cd076c7dc9953d7ac1040df92f1c5fa76583aae644f037ab85", size = 849265, upload-time = "2026-09-10T09:41:00.882Z" }, ]  [[package]]@@ -6840,6 +6840,7 @@ all = [     { name = "pypinyin" },     { name = "pypinyin-dict" },     { name = "pystoi" },+    { name = "pytorch-lightning" },     { name = "sacrebleu" },     { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-all') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-compiled') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-cu12') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-cu13') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2-only') or (python_full_version < '3.11' and extra != 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-all') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-compiled') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-cu12') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-cu13') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2-only') or (python_full_version >= '3.11' and extra != 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },@@ -6866,6 +6867,7 @@ asr = [     { name = "omegaconf" },     { name = "packaging" },     { name = "pandas" },+    { name = "pytorch-lightning" },     { name = "sacrebleu" },     { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },@@ -6905,6 +6907,7 @@ audio = [     { name = "pandas" },     { name = "pesq", marker = "platform_machine != 'x86_64' or sys_platform != 'darwin' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "pystoi" },+    { name = "pytorch-lightning" },     { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "sentencepiece" },@@ -6924,6 +6927,7 @@ common = [     { name = "nv-one-logger-training-telemetry" },     { name = "omegaconf" },     { name = "pandas" },+    { name = "pytorch-lightning" },     { name = "sentencepiece" },     { name = "torchmetrics" },     { name = "transformers" },@@ -6964,6 +6968,7 @@ core = [     { name = "nv-one-logger-pytorch-lightning-integration" },     { name = "nv-one-logger-training-telemetry" },     { name = "omegaconf" },+    { name = "pytorch-lightning" },     { name = "torchmetrics" },     { name = "transformers" },     { name = "wandb" },@@ -6988,6 +6993,7 @@ lightning = [     { name = "nv-one-logger-pytorch-lightning-integration" },     { name = "nv-one-logger-training-telemetry" },     { name = "omegaconf" },+    { name = "pytorch-lightning" },     { name = "torchmetrics" },     { name = "transformers" },     { name = "wandb" },@@ -7019,6 +7025,7 @@ speechlm2 = [     { name = "pyopenjtalk" },     { name = "pypinyin" },     { name = "pypinyin-dict" },+    { name = "pytorch-lightning" },     { name = "sacrebleu" },     { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-all') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-compiled') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-cu12') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-cu13') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2') or (python_full_version < '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2-only') or (python_full_version < '3.11' and extra != 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-all') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-compiled') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-cu12') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-cu13') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2') or (python_full_version >= '3.11' and extra == 'extra-12-nemo-toolkit-speechlm2-only') or (python_full_version >= '3.11' and extra != 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },@@ -7058,6 +7065,7 @@ tts = [     { name = "pyopenjtalk" },     { name = "pypinyin" },     { name = "pypinyin-dict" },+    { name = "pytorch-lightning" },     { name = "sacrebleu" },     { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },     { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-12-nemo-toolkit-all' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-compiled-a100') or (extra == 'extra-12-nemo-toolkit-compiled' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-compiled-a100' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-cu13') or (extra == 'extra-12-nemo-toolkit-cu12' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-cu13' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2' and extra == 'extra-12-nemo-toolkit-vllm') or (extra == 'extra-12-nemo-toolkit-speechlm2-only' and extra == 'extra-12-nemo-toolkit-vllm')" },@@ -7153,14 +7161,14 @@ requires-dist = [     { name = "flashoptim", marker = "extra == 'speechlm2'" },     { name = "flashoptim", marker = "extra == 'speechlm2-only'" },     { name = "huggingface-hub", specifier = ">=0.24" },-    { name = "hydra-core", marker = "extra == 'all'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'asr'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'audio'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'common'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'core'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'lightning'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'speechlm2'", specifier = ">1.3,<=1.3.2" },-    { name = "hydra-core", marker = "extra == 'tts'", specifier = ">1.3,<=1.3.2" },+    { name = "hydra-core", marker = "extra == 'all'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'asr'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'audio'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'common'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'core'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'lightning'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'speechlm2'", specifier = ">=1.3.8,<1.4" },+    { name = "hydra-core", marker = "extra == 'tts'", specifier = ">=1.3.8,<1.4" },     { name = "janome", marker = "extra == 'all'" },     { name = "janome", marker = "extra == 'speechlm2'" },     { name = "janome", marker = "extra == 'tts'" },@@ -7188,14 +7196,14 @@ requires-dist = [     { name = "librosa", marker = "extra == 'speechlm2'", specifier = ">=0.10.1" },     { name = "librosa", marker = "extra == 'tts'" },     { name = "librosa", marker = "extra == 'tts'", specifier = ">=0.10.1" },-    { name = "lightning", marker = "extra == 'all'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'asr'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'audio'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'common'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'core'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'lightning'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'speechlm2'", specifier = ">2.2.1,<=2.4.0" },-    { name = "lightning", marker = "extra == 'tts'", specifier = ">2.2.1,<=2.4.0" },+    { name = "lightning", marker = "extra == 'all'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'asr'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'audio'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'common'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'core'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'lightning'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'speechlm2'", specifier = ">=2.6.6,<2.7" },+    { name = "lightning", marker = "extra == 'tts'", specifier = ">=2.6.6,<2.7" },     { name = "mamba-ssm", marker = "extra == 'compiled'", specifier = "==2.3.2.post1" },     { name = "mamba-ssm", marker = "extra == 'compiled-a100'", specifier = "==2.3.2.post1" },     { name = "matplotlib", marker = "extra == 'all'" },@@ -7241,14 +7249,14 @@ requires-dist = [     { name = "nv-one-logger-training-telemetry", marker = "extra == 'lightning'", specifier = ">=2.3.1" },     { name = "nv-one-logger-training-telemetry", marker = "extra == 'speechlm2'", specifier = ">=2.3.1" },     { name = "nv-one-logger-training-telemetry", marker = "extra == 'tts'", specifier = ">=2.3.1" },-    { name = "omegaconf", marker = "extra == 'all'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'asr'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'audio'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'common'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'core'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'lightning'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'speechlm2'", specifier = "<=2.3" },-    { name = "omegaconf", marker = "extra == 'tts'", specifier = "<=2.3" },+    { name = "omegaconf", marker = "extra == 'all'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'asr'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'audio'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'common'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'core'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'lightning'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'speechlm2'", specifier = ">=2.3,<2.4" },+    { name = "omegaconf", marker = "extra == 'tts'", specifier = ">=2.3,<2.4" },     { name = "onnx", specifier = ">=1.7.0" },     { name = "onnx-ir", marker = "extra == 'compiled'", specifier = "==0.2.1" },     { name = "onnx-ir", marker = "extra == 'compiled-a100'", specifier = "==0.2.1" },@@ -7283,6 +7291,14 @@ requires-dist = [     { name = "pypinyin-dict", marker = "extra == 'tts'" },     { name = "pystoi", marker = "extra == 'all'" },     { name = "pystoi", marker = "extra == 'audio'" },+    { name = "pytorch-lightning", marker = "extra == 'all'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'asr'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'audio'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'common'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'core'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'lightning'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'speechlm2'", specifier = ">=2.6.6,<2.7" },+    { name = "pytorch-lightning", marker = "extra == 'tts'", specifier = ">=2.6.6,<2.7" },     { name = "sacrebleu", marker = "extra == 'all'" },     { name = "sacrebleu", marker = "extra == 'asr'" },     { name = "sacrebleu", marker = "extra == 'asr-only'" },@@ -11525,7 +11541,7 @@ wheels = [  [[package]] name = "pytorch-lightning"-version = "2.6.1"+version = "2.6.6" source = { registry = "https://pypi.org/simple" } dependencies = [     { name = "fsspec", extra = ["http"] },@@ -11541,9 +11557,9 @@ dependencies = [     { name = "tqdm" },     { name = "typing-extensions" }, ]-sdist = { url = "https://files.pythonhosted.org/packages/8b/ac/ebd5f6f58691cbd4f73836e43e1727f3814311b960c41f88e259606ca2b2/pytorch_lightning-2.6.1.tar.gz", hash = "sha256:ba08f8901cf226fcca473046ad9346f414e99117762dc869c76e650d5b3d7bdc", size = 665563, upload-time = "2026-01-30T14:59:11.636Z" }+sdist = { url = "https://files.pythonhosted.org/packages/97/e4/5025cb42311b4f8cc6d1b12cd81399ca433159f8e3831ab229c5a6c5177e/pytorch_lightning-2.6.6.tar.gz", hash = "sha256:52c52b20522dd3d3feb3bb3aae2dbe3f291e8e836d4111a74cfd72e9df018834", size = 662507, upload-time = "2026-09-10T09:41:16.684Z" } wheels = [-    { url = "https://files.pythonhosted.org/packages/0e/93/c8c361bf0a2fe50f828f32def460e8b8a14b93955d3fd302b1a9b63b19e4/pytorch_lightning-2.6.1-py3-none-any.whl", hash = "sha256:1f8118567ec829e3055f16cf1aa320883a86a47c836951bfd9dcfa34ec7ffd59", size = 857273, upload-time = "2026-01-30T14:59:10.141Z" },+    { url = "https://files.pythonhosted.org/packages/4f/cf/ba8e4cc1b982ff6dc6f9fdef0e4074605438b82640f6270888a10e7e42c3/pytorch_lightning-2.6.6-py3-none-any.whl", hash = "sha256:71f95c7b22f25c4f91cc659e1734bcdc6c69c8b66543f2ed2d78dbe29ce2f167", size = 853045, upload-time = "2026-09-10T09:41:14.923Z" }, ]  [[package]]