| import os |
| import sys |
| import json |
| import glob |
| import argparse |
| from easydict import EasyDict as edict |
|
|
| import torch |
| |
| import numpy as np |
| import random |
| |
|
|
| from trellis import models, datasets, trainers |
| from trellis.utils.dist_utils import setup_dist |
|
|
|
|
| def find_ckpt(cfg): |
| |
| cfg['load_ckpt'] = None |
| if cfg.load_dir != '': |
| if cfg.ckpt == 'latest': |
| files = glob.glob(os.path.join(cfg.load_dir, 'ckpts', 'misc_*.pt')) |
| if len(files) != 0: |
| cfg.load_ckpt = max([ |
| int(os.path.basename(f).split('step')[-1].split('.')[0]) |
| for f in files |
| ]) |
| elif cfg.ckpt == 'none': |
| cfg.load_ckpt = None |
| else: |
| cfg.load_ckpt = int(cfg.ckpt) |
| return cfg |
|
|
|
|
| def setup_rng(rank): |
| |
| |
| |
| |
| seed = cfg.get('seed', 42) |
| torch.manual_seed(seed + rank) |
| torch.cuda.manual_seed_all(seed + rank) |
| np.random.seed(seed + rank) |
| random.seed(seed + rank) |
| |
| |
| |
|
|
|
|
| def get_model_summary(model): |
| model_summary = 'Parameters:\n' |
| model_summary += '=' * 128 + '\n' |
| model_summary += f'{"Name":<{72}}{"Shape":<{32}}{"Type":<{16}}{"Grad"}\n' |
| num_params = 0 |
| num_trainable_params = 0 |
| for name, param in model.named_parameters(): |
| model_summary += f'{name:<{72}}{str(param.shape):<{32}}{str(param.dtype):<{16}}{param.requires_grad}\n' |
| num_params += param.numel() |
| if param.requires_grad: |
| num_trainable_params += param.numel() |
| model_summary += '\n' |
| model_summary += f'Number of parameters: {num_params}\n' |
| model_summary += f'Number of trainable parameters: {num_trainable_params}\n' |
| return model_summary |
|
|
|
|
| def main(cfg): |
| |
| local_rank = int(os.environ.get('LOCAL_RANK', 0)) |
| rank = int(os.environ.get('RANK', 0)) |
| world_size = int(os.environ.get('WORLD_SIZE', 1)) |
|
|
| if world_size > 1: |
| torch.cuda.set_device(local_rank) |
| |
| |
| |
| |
| master_addr = os.environ.get('MASTER_ADDR', 'localhost') |
| master_port = os.environ.get('MASTER_PORT', '12345') |
| print(f"Rank {rank}: Initializing distributed training. local_rank={local_rank}, world_size={world_size}, master_addr={master_addr}, master_port={master_port}") |
| setup_dist(rank, local_rank, world_size, master_addr, master_port) |
| else: |
| print("Rank 0: Running in single GPU mode.") |
| if torch.cuda.is_available(): |
| torch.cuda.set_device(local_rank) |
|
|
| |
| setup_rng(rank) |
|
|
| if cfg.random_cond_gt: |
| cfg.dataset.args.random_cond_gt = True |
| if cfg.coords_aug_size is not None: |
| cfg.dataset.args.coords_aug_size = cfg.coords_aug_size |
| if cfg.feats_aug_grid_size is not None: |
| cfg.dataset.args.feats_aug_grid_size = cfg.feats_aug_grid_size |
| if cfg.feats_aug_ratio is not None: |
| cfg.dataset.args.feats_aug_ratio = cfg.feats_aug_ratio |
| if cfg.voxel_aug_ratio is not None: |
| cfg.dataset.args.voxel_aug_ratio = cfg.voxel_aug_ratio |
| if cfg.adapt_simple_edit_data: |
| cfg.dataset.args.adapt_simple_edit_data = True |
| if cfg.mixamo_data_repeat_ratio is not None: |
| cfg.dataset.args.mixamo_data_repeat_ratio = cfg.mixamo_data_repeat_ratio |
| if cfg.random_ori_edit is not None: |
| cfg.dataset.args.random_ori_edit = cfg.random_ori_edit |
| if cfg.simple_edit_data_if_filtered: |
| cfg.dataset.args.simple_edit_data_if_filtered = True |
| |
| |
| default_data_dir = 'data' |
| dataset = getattr(datasets, cfg.dataset.name)(default_data_dir, **cfg.dataset.args) |
|
|
| |
| if cfg.ori_ss_latents_weights is not None: |
| cfg.models.denoiser.args.ori_ss_latents_weights = cfg.ori_ss_latents_weights |
| if cfg.feats_3d_t is not None: |
| cfg.models.denoiser.args.feats_3d_t = cfg.feats_3d_t |
| model_dict = { |
| name: getattr(models, model.name)(**model.args).cuda() |
| for name, model in cfg.models.items() |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
|
|
| if cfg.lr is not None: |
| cfg.trainer.args.optimizer.args.lr = cfg.lr |
|
|
| if cfg.batch_size_per_gpu is not None: |
| cfg.trainer.args.batch_size_per_gpu = cfg.batch_size_per_gpu |
|
|
| if cfg.batch_split is not None: |
| cfg.trainer.args.batch_split = cfg.batch_split |
|
|
| if cfg.max_steps is not None: |
| cfg.trainer.args.max_steps = cfg.max_steps |
|
|
| if cfg.train_only_editing_weights: |
| for name, param in model_dict['denoiser'].named_parameters(): |
| if 'editing' in name: |
| param.requires_grad = True |
| else: |
| param.requires_grad = False |
|
|
| if cfg.debug: |
| cfg.trainer.args.max_steps = 100 |
| cfg.trainer.args.i_print = 1 |
| cfg.trainer.args.i_log = 1 |
| cfg.trainer.args.i_sample = 10 |
| cfg.trainer.args.i_save = 100 |
| cfg.trainer.args.init_sample = False |
| cfg.trainer.args.init_dataset_vis = False |
|
|
| if cfg.no_sample_images: |
| cfg.trainer.args.no_sample_images = True |
|
|
| |
| if rank == 0: |
| for name, backbone in model_dict.items(): |
| model_summary = get_model_summary(backbone) |
| print(f'\n\nBackbone: {name}\n' + model_summary) |
| with open(os.path.join(cfg.output_dir, f'{name}_model_summary.txt'), 'w') as fp: |
| print(model_summary, file=fp) |
|
|
| |
| trainer = getattr(trainers, cfg.trainer.name)( |
| model_dict, dataset, **cfg.trainer.args, |
| output_dir=cfg.output_dir, load_dir=cfg.load_dir, step=cfg.load_ckpt |
| ) |
|
|
| |
| if not cfg.tryrun: |
| if cfg.profile: |
| trainer.profile() |
| else: |
| trainer.run() |
|
|
| if world_size > 1: |
| torch.distributed.barrier() |
| torch.distributed.destroy_process_group() |
|
|
|
|
| if __name__ == '__main__': |
| |
| parser = argparse.ArgumentParser() |
| |
| parser.add_argument('--config', type=str, required=True, help='Experiment config file') |
| |
| parser.add_argument('--output_dir', type=str, required=True, help='Output directory') |
| parser.add_argument('--load_dir', type=str, default='', help='Load directory, default to output_dir') |
| parser.add_argument('--ckpt', type=str, default='latest', help='Checkpoint step to resume training, default to latest') |
| parser.add_argument('--data_dir', type=str, default='/path_to_3DEditVerse/', help='Data directory') |
| parser.add_argument('--auto_retry', type=int, default=0, help='Number of retries on error (simplified for torchrun)') |
| parser.add_argument('--seed', type=int, default=42, help='Base random seed.') |
| |
| parser.add_argument('--tryrun', action='store_true', help='Try run without training') |
| parser.add_argument('--profile', action='store_true', help='Profile training') |
| |
| parser.add_argument('--lr', type=float, default=None, help='Learning rate') |
| parser.add_argument('--batch_size_per_gpu', type=int, default=None, help='Batch size per gpu') |
| parser.add_argument('--batch_split', type=int, default=None, help='Batch split') |
| parser.add_argument('--max_steps', type=int, default=None, help='Max steps') |
| parser.add_argument('--debug', action='store_true', help='Debug mode') |
| parser.add_argument('--train_only_editing_weights', action='store_true', help='Train only editing weights') |
| parser.add_argument('--ori_ss_latents_weights', type=float, default=None, help='Weight for ori ss latents fusing with noising latents') |
| parser.add_argument('--feats_3d_t', type=float, nargs=2, default=None, help='Feats 3d t') |
| parser.add_argument('--no_sample_images', action='store_true', help='No sample images') |
| |
| parser.add_argument('--random_cond_gt', action='store_true', help='Use random cond gt') |
| parser.add_argument('--coords_aug_size', type=int, default=None, help='Coords aug size') |
| parser.add_argument('--feats_aug_grid_size', type=int, nargs='*', default=None, help='Feats aug grid size') |
| parser.add_argument('--feats_aug_ratio', type=float, nargs=2, default=None, help='Feats aug ratio') |
| parser.add_argument('--voxel_aug_ratio', type=float, nargs=2, default=None, help='Voxel aug ratio') |
| parser.add_argument('--adapt_simple_edit_data', action='store_true', help='Adapt simple edit data') |
| parser.add_argument('--mixamo_data_repeat_ratio', type=float, default=None, help='Mixamo data repeat ratio') |
| parser.add_argument('--random_ori_edit', type=float, default=None, help='Random ori edit data') |
| parser.add_argument('--simple_edit_data_if_filtered', action='store_true', help='Simple edit data if filtered') |
| opt = parser.parse_args() |
|
|
| opt.load_dir = opt.load_dir if opt.load_dir != '' else opt.output_dir |
| |
| |
| |
| |
|
|
| |
| def replace_data_dir_placeholders(value): |
| if isinstance(value, str): |
| return value.replace('/path_to_3DEditVerse', opt.data_dir) |
| if isinstance(value, list): |
| return [replace_data_dir_placeholders(v) for v in value] |
| if isinstance(value, dict): |
| return {k: replace_data_dir_placeholders(v) for k, v in value.items()} |
| return value |
|
|
| config_from_file = replace_data_dir_placeholders(json.load(open(opt.config, 'r'))) |
| |
| |
| cfg = edict() |
| cfg.update(opt.__dict__) |
| cfg.update(config_from_file) |
| |
| |
| |
| |
|
|
| |
| |
| for key, value in opt.__dict__.items(): |
| |
| if value is not None: |
| |
| |
| |
| is_default_argparse = False |
| for action in parser._actions: |
| if action.dest == key: |
| if action.default == value and not isinstance(action, argparse._StoreTrueAction) and not isinstance(action, argparse._StoreFalseAction): |
| |
| |
| pass |
| cfg[key] = value |
|
|
|
|
| |
| |
| |
| current_rank = int(os.environ.get('RANK', 0)) |
| world_size_for_setup = int(os.environ.get('WORLD_SIZE', 1)) |
|
|
| if current_rank == 0: |
| print('\n\nConfig:') |
| print('=' * 80) |
| |
| |
| config_to_print_save = dict(cfg) |
| print(json.dumps(config_to_print_save, indent=4)) |
|
|
| os.makedirs(cfg.output_dir, exist_ok=True) |
| |
| with open(os.path.join(cfg.output_dir, 'command.txt'), 'w') as fp: |
| print(' '.join(['python'] + sys.argv), file=fp) |
| with open(os.path.join(cfg.output_dir, 'config.json'), 'w') as fp: |
| json.dump(config_to_print_save, fp, indent=4) |
|
|
| |
| |
| |
| |
| |
| |
|
|
| cfg = find_ckpt(cfg) |
|
|
| if cfg.auto_retry > 0 and world_size_for_setup > 1: |
| print(f"Warning: auto_retry ({cfg.auto_retry}) within torchrun script has limited effect and might not recover from all errors. Job-level retry is preferred.") |
|
|
| for rty in range(cfg.auto_retry + 1): |
| try: |
| |
| main(cfg) |
| break |
| except Exception as e: |
| print(f"Error during main execution: {e}") |
| if rty < cfg.auto_retry: |
| print(f"Retrying ({rty + 1}/{cfg.auto_retry})...") |
| if world_size_for_setup > 1: |
| torch.distributed.barrier() |
| |
| |
| |
| else: |
| print("Max retries reached. Failing.") |
| if world_size_for_setup > 1 and torch.distributed.is_initialized(): |
| torch.distributed.destroy_process_group() |
| raise e |
|
|