# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import os import argparse import torch from omegaconf import OmegaConf from lipforcing.configs.config_utils import serialize_config from lipforcing.configs import config_utils import lipforcing.utils.logging_utils as logger from lipforcing.utils.basic_utils import get_batch_size_total from lipforcing.utils.distributed import ddp, world_size, is_rank0 from lipforcing.utils.io_utils import set_env_vars from lipforcing.configs.config import BaseConfig def parse_args(parser: argparse.ArgumentParser) -> argparse.Namespace: parser.add_argument("--config", default="configs.config", help="Path to the config file") parser.add_argument("--log_level", default="INFO", help="Log level (e.g. DEBUG, INFO)") parser.add_argument( "opts", default=None, nargs=argparse.REMAINDER, help="""Modify config options at the end of the command. For Yacs configs, use space-separated "PATH.KEY VALUE" pairs. For python-based LazyConfig, use "path.key=value".""", ) parser.add_argument( "--dryrun", action="store_true", help="Do a dry run without training. Useful for debugging the config.", ) args = parser.parse_args() return args def set_cuda_backend(deterministic: bool = True, benchmark: bool = True, tf32_enabled: bool = True): # Initialize cuDNN. torch.backends.cudnn.deterministic = deterministic torch.backends.cudnn.benchmark = benchmark # Floating-point precision settings. torch.backends.cudnn.allow_tf32 = tf32_enabled torch.backends.cuda.matmul.allow_tf32 = tf32_enabled torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = tf32_enabled logger.critical( f"cuDNN deterministic: {deterministic}, " f"cuDNN benchmark: {benchmark}, " f"enable TF32: {tf32_enabled}" ) def setup(args: argparse.Namespace, evaluation: bool = False) -> BaseConfig: if hasattr(args, "log_level"): # set log level for logger (INFO, by default) logger.set_log_level(args.log_level) # Import the config from the python file config: BaseConfig = config_utils.import_config_from_python_file(args.config) if hasattr(args, "opts"): # Override the config with the command line arguments config = config_utils.override_config_with_opts(config, args.opts) # Update checkpointer save_dir config.trainer.checkpointer.save_dir = f"{config.log_config.save_path}/{config.trainer.checkpointer.save_dir}" # save config config_save_path = config.log_config.save_path if evaluation: config_save_path = os.path.join(config_save_path, config.eval.samples_dir) if is_rank0(): serialize_config(config, return_type="file", path=config_save_path, filename="config.yaml") # Check for dryrun if getattr(args, "dryrun", False): logger.info("Dryrun") logger.info(OmegaConf.to_yaml(OmegaConf.load(f"{config_save_path}/config.yaml"))) logger.info(f"config.yaml is saved at {config_save_path}") exit(0) # distributed setup if config.trainer.ddp or config.trainer.fsdp: # check if ddp is available if not torch.distributed.is_available(): raise RuntimeError("Distributed training is not available, please check your PyTorch installation.") # initialize DDP ddp.init() logger.info(f"Distributed training initialized, world size: {world_size()}") else: logger.info("No DDP or FSDP parallelism") # Check if we can use memory-efficient FSDP meta init if config.model.fsdp_meta_init: if not config.trainer.fsdp: logger.warning("Ignoring fsdp_meta_init since FSDP is disabled.") config.model.fsdp_meta_init = False elif evaluation: logger.warning("Ignoring fsdp_meta_init for evaluation/inference.") config.model.fsdp_meta_init = False # NOTE: fsdp_meta_init is compatible with pretrained_ckpt_path. The # load path skips dcp.load on ranks where v.parameters() contains meta # tensors, so only rank 0 loads weights and FSDP wrap broadcasts them # to the other ranks via sync_module_states. # Global batch size if getattr(config.trainer, "batch_size_global", None) is not None: batch_size = config.dataloader_train.batch_size * world_size() accum_rounds = max(config.trainer.batch_size_global // batch_size, 1) new_batch_size_global = accum_rounds * batch_size if new_batch_size_global != config.trainer.batch_size_global: logger.critical( f"Requested global batch size {config.trainer.batch_size_global} is not divisible by current batch size {batch_size}. New global batch size will be {new_batch_size_global}." ) if accum_rounds != config.trainer.grad_accum_rounds: logger.info( f"Changing gradient accumulation rounds from {config.trainer.grad_accum_rounds} to {accum_rounds} to match requested global batch size." ) config.trainer.grad_accum_rounds = accum_rounds logger.critical( f"Global batch size: {get_batch_size_total(config)} (Batch size per GPU: {config.dataloader_train.batch_size}, Gradient accumulation rounds: {config.trainer.grad_accum_rounds}, World size: {world_size()})" ) # Set up s3 environmental variables set_env_vars(config.trainer.checkpointer.s3_credential) # Set up CUDA backend set_cuda_backend(config.trainer.cudnn.deterministic, config.trainer.cudnn.benchmark, config.trainer.tf32_enabled) return config