# Copyright (c) 2023 Amphion. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import os import random from pathlib import Path import re import accelerate import json5 import numpy as np import torch from accelerate.utils import ProjectConfiguration from torch.utils.data import DataLoader from tqdm import tqdm from models.vocoders.vocoder_dataset import VocoderConcatDataset from models.vocoders.vocoder_sampler import build_samplers class VocoderTrainer: def __init__(self): super().__init__() def _init_accelerator(self): """Initialize the accelerator components.""" self.exp_dir = os.path.join( os.path.abspath(self.cfg.log_dir), self.args.exp_name ) project_config = ProjectConfiguration( project_dir=self.exp_dir, logging_dir=os.path.join(self.exp_dir, "log") ) self.accelerator = accelerate.Accelerator( gradient_accumulation_steps=self.cfg.train.gradient_accumulation_step, log_with=self.cfg.train.tracker, project_config=project_config, ) if self.accelerator.is_main_process: os.makedirs(project_config.project_dir, exist_ok=True) os.makedirs(project_config.logging_dir, exist_ok=True) with self.accelerator.main_process_first(): self.accelerator.init_trackers(self.args.exp_name) def _build_dataset(self): pass def _build_criterion(self): pass def _build_model(self): pass def _build_dataloader(self): """Build dataloader which merges a series of datasets.""" # Build dataset instance for each dataset and combine them by ConcatDataset Dataset, Collator = self._build_dataset() # Build train set datasets_list = [] for dataset in self.cfg.dataset: subdataset = Dataset(self.cfg, dataset, is_valid=False) datasets_list.append(subdataset) train_dataset = VocoderConcatDataset(datasets_list, full_audio_inference=True) train_collate = Collator(self.cfg) _, batch_sampler = build_samplers(train_dataset, self.cfg, self.logger, "train") train_loader = DataLoader( train_dataset, collate_fn=train_collate, batch_sampler=batch_sampler, num_workers=self.cfg.train.dataloader.num_worker, pin_memory=self.cfg.train.dataloader.pin_memory, ) # Build test set datasets_list = [] for dataset in self.cfg.dataset: subdataset = Dataset(self.cfg, dataset, is_valid=True) datasets_list.append(subdataset) valid_dataset = VocoderConcatDataset(datasets_list, full_audio_inference=True) valid_collate = Collator(self.cfg) _, batch_sampler = build_samplers(valid_dataset, self.cfg, self.logger, "train") valid_loader = DataLoader( valid_dataset, collate_fn=valid_collate, batch_sampler=batch_sampler, num_workers=self.cfg.train.dataloader.num_worker, pin_memory=self.cfg.train.dataloader.pin_memory, ) return train_loader, valid_loader def _build_optimizer(self): pass def _build_scheduler(self): pass def _load_model(self, checkpoint_dir, checkpoint_path=None, resume_type="resume"): """Load model from checkpoint. If a folder is given, it will load the latest checkpoint in checkpoint_dir. If a path is given it will load the checkpoint specified by checkpoint_path. **Only use this method after** ``accelerator.prepare()``. """ if checkpoint_path is None: ls = [str(i) for i in Path(checkpoint_dir).glob("*")] ls.sort(key=lambda x: int(x.split("_")[-3].split("-")[-1]), reverse=True) checkpoint_path = ls[0] if resume_type == "resume": self.accelerator.load_state(checkpoint_path) elif resume_type == "finetune": accelerate.load_checkpoint_and_dispatch( self.accelerator.unwrap_model(self.model), os.path.join(checkpoint_path, "pytorch_model.bin"), ) self.logger.info("Load model weights for finetune SUCCESS!") else: raise ValueError("Unsupported resume type: {}".format(resume_type)) self.epoch = int(checkpoint_path.split("_")[-3].split("-")[-1]) + 1 self.step = int(checkpoint_path.split("_")[-2].split("-")[-1]) + 1 return checkpoint_path def train_loop(self): pass def _train_epoch(self): pass def _valid_epoch(self): pass def _train_step(self): pass def _valid_step(self): pass def _inference(self): pass def _set_random_seed(self, seed): """Set random seed for all possible random modules.""" random.seed(seed) np.random.seed(seed) torch.random.manual_seed(seed) def _check_nan(self, loss): if torch.any(torch.isnan(loss)): self.logger.fatal("Fatal Error: NaN!") self.logger.error("loss = {:.6f}".format(loss.item()), in_order=True) def _check_basic_configs(self): if self.cfg.train.gradient_accumulation_step <= 0: self.logger.fatal("Invalid gradient_accumulation_step value!") self.logger.error( f"Invalid gradient_accumulation_step value: {self.cfg.train.gradient_accumulation_step}. It should be positive." ) self.accelerator.end_training() raise ValueError( f"Invalid gradient_accumulation_step value: {self.cfg.train.gradient_accumulation_step}. It should be positive." ) def _count_parameters(self): pass def _dump_cfg(self, path): os.makedirs(os.path.dirname(path), exist_ok=True) json5.dump( self.cfg, open(path, "w"), indent=4, sort_keys=True, ensure_ascii=False, quote_keys=True, ) def _is_valid_pattern(self, directory_name): directory_name = str(directory_name) pattern = r"^epoch-\d{4}_step-\d{7}_loss-\d{1}\.\d{6}" return re.match(pattern, directory_name) is not None