# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function import copy import io import os import os.path as osp import pkgutil import re import warnings from collections import OrderedDict from importlib import import_module from tempfile import TemporaryDirectory import mmcv import torch import torch.nn as nn import torchvision from mmcv.parallel import is_module_wrapper from mmengine.dist import get_dist_info from mmengine.fileio import FileClient from mmengine.fileio import load as load_file from mmengine.utils import mkdir_or_exist from ptflops import get_model_complexity_info from torch.utils import model_zoo ENV_MMCV_HOME = "MMCV_HOME" ENV_XDG_CACHE_HOME = "XDG_CACHE_HOME" DEFAULT_CACHE_DIR = "~/.cache" layers_position = { "PoseResNet_50": 158, "PoseResNet_101": 311, "PoseEfficientNet_B4": 415, } def m_flops(model, cfg): if cfg.DATASET.DATA_TYPE != "video": macs, params = get_model_complexity_info( model, (3, cfg.DATASET.IMAGE_SIZE[0], cfg.DATASET.IMAGE_SIZE[0]), as_strings=True, verbose=True, ) else: macs, params = get_model_complexity_info( model, ( 3, cfg.DATASET.DATA.SAMPLES_PER_VIDEO.TRAIN, cfg.DATASET.IMAGE_SIZE[0], cfg.DATASET.IMAGE_SIZE[0], ), as_strings=True, verbose=True, ) print("{:<30} {:<8}".format("Computational complexity: ", macs)) print("{:<30} {:<8}".format("Number of parameters: ", params)) def n_param_model(model): print("Number of parameters", sum(p.numel() for p in model.parameters())) print( "Number of trainable parameters", sum(p.numel() for p in model.parameters() if p.requires_grad), ) def preset_model(cfg, model, optimizer=None, scaler=None): # Loading models from config, make sure the pretrained path correct to the model name start_epoch = 0 if "pretrained" in cfg.TRAIN and os.path.isfile(cfg.TRAIN.pretrained): model, optimizer, start_epoch, scaler = load_model( model, cfg.TRAIN.pretrained, optimizer=optimizer, scaler=scaler, resume=cfg.TRAIN.resume, lr=cfg.TRAIN.lr, lr_step=cfg.TRAIN.lr_scheduler.milestones, gamma=cfg.TRAIN.lr_scheduler.gamma, ) else: model.init_weights(**cfg.MODEL.INIT_WEIGHTS) print("Loading model successfully -- {}".format(cfg.MODEL.type)) # Showing model FLOPS m_flops(model, cfg) # Freeze backbone if begin_epoch < warm up if cfg.TRAIN.freeze_backbone and start_epoch < cfg.TRAIN.warm_up: freeze_backbone(cfg.MODEL, model) n_param_model(model) return model, optimizer, start_epoch, scaler def load_pretrained(model, weight_path): """ This function only care about state dict of model For other modules such as optimizer, resume learning, please refer @load_model """ state_dict = torch.load(weight_path, map_location=torch.device("cpu"))["state_dict"] model.load_state_dict(state_dict, strict=True) return model def freeze_backbone(cfg, model): """ This func to freeze some specific layers to warm up the models """ if hasattr(model, "backbone"): backbone = model.backbone for param in backbone.parameters(): param.requires_grad = False else: for i, (n, p) in enumerate(model.named_parameters()): if i <= layers_position[f"{cfg.type}_{cfg.num_layers}"]: p.requires_grad = False def unfreeze_backbone(model): """ This func to unfreeze all model layers """ for param in model.parameters(): if not param.requires_grad: param.requires_grad = True def load_model( model, model_path, optimizer=None, scaler=None, resume=False, lr=None, lr_step=None, gamma=None, ): start_epoch = 0 checkpoint = torch.load(model_path, map_location=lambda storage, loc: storage) print("loaded {}, epoch {}".format(model_path, checkpoint["epoch"])) state_dict_ = checkpoint["state_dict"] state_dict = {} # convert data_parallal to model for k in state_dict_: if k.startswith("module") and not k.startswith("module_list"): state_dict[k[7:]] = state_dict_[k] else: state_dict[k] = state_dict_[k] model_state_dict = model.state_dict() # check loaded parameters and created model parameters msg = ( "If you see this, your model does not fully load the " + "pre-trained weight. Please make sure " + "you have correctly specified --arch xxx " + "or set the correct --num_classes for your own dataset." ) for k in state_dict: if k in model_state_dict: if state_dict[k].shape != model_state_dict[k].shape: print( "Skip loading parameter {}, required shape{}, " "loaded shape{}. {}".format( k, model_state_dict[k].shape, state_dict[k].shape, msg ) ) state_dict[k] = model_state_dict[k] else: print("Drop parameter {}.".format(k) + msg) for k in model_state_dict: if not (k in state_dict): print("No param {}.".format(k) + msg) state_dict[k] = model_state_dict[k] model.load_state_dict(state_dict, strict=False) # resume optimizer parameters if optimizer is not None and resume: if "optimizer" in checkpoint: optimizer.load_state_dict(checkpoint["optimizer"]) if "scaler" in checkpoint: scaler.load_state_dict(checkpoint["scaler"]) start_epoch = checkpoint["epoch"] + 1 start_lr = lr for step in lr_step: if start_epoch >= step: start_lr *= gamma for param_group in optimizer.param_groups: param_group["lr"] = start_lr print("Resumed optimizer with start lr", start_lr) else: print("No optimizer parameters in checkpoint.") return model, optimizer, start_epoch, scaler def save_model(path, epoch, model, optimizer=None, scaler=None): if isinstance(model, torch.nn.DataParallel): state_dict = model.module.state_dict() else: state_dict = model.state_dict() data = {"epoch": epoch, "state_dict": state_dict} if not (optimizer is None): data["optimizer"] = optimizer.state_dict() if not (scaler is None): data["scaler"] = scaler.state_dict() torch.save(data, path) def _get_mmcv_home(): mmcv_home = os.path.expanduser( os.getenv( ENV_MMCV_HOME, os.path.join(os.getenv(ENV_XDG_CACHE_HOME, DEFAULT_CACHE_DIR), "mmcv"), ) ) mkdir_or_exist(mmcv_home) return mmcv_home def load_url_dist(url, model_dir=None, map_location="cpu"): """In distributed setting, this function only download checkpoint at local rank 0.""" rank, world_size = get_dist_info() rank = int(os.environ.get("LOCAL_RANK", rank)) if rank == 0: checkpoint = model_zoo.load_url( url, model_dir=model_dir, map_location=map_location ) if world_size > 1: torch.distributed.barrier() if rank > 0: checkpoint = model_zoo.load_url( url, model_dir=model_dir, map_location=map_location ) return checkpoint def get_torchvision_models(): model_urls = dict() for _, name, ispkg in pkgutil.walk_packages(torchvision.models.__path__): if ispkg: continue _zoo = import_module(f"torchvision.models.{name}") if hasattr(_zoo, "model_urls"): _urls = getattr(_zoo, "model_urls") model_urls.update(_urls) return model_urls def get_external_models(): mmcv_home = _get_mmcv_home() default_json_path = osp.join(mmcv.__path__[0], "model_zoo/open_mmlab.json") default_urls = load_file(default_json_path) assert isinstance(default_urls, dict) external_json_path = osp.join(mmcv_home, "open_mmlab.json") if osp.exists(external_json_path): external_urls = load_file(external_json_path) assert isinstance(external_urls, dict) default_urls.update(external_urls) return default_urls def get_deprecated_model_names(): deprecate_json_path = osp.join(mmcv.__path__[0], "model_zoo/deprecated.json") deprecate_urls = load_file(deprecate_json_path) assert isinstance(deprecate_urls, dict) return deprecate_urls def get_mmcls_models(): mmcls_json_path = osp.join(mmcv.__path__[0], "model_zoo/mmcls.json") mmcls_urls = load_file(mmcls_json_path) return mmcls_urls def _process_mmcls_checkpoint(checkpoint): state_dict = checkpoint["state_dict"] new_state_dict = OrderedDict() for k, v in state_dict.items(): if k.startswith("backbone."): new_state_dict[k[9:]] = v new_checkpoint = dict(state_dict=new_state_dict) return new_checkpoint def load_pavimodel_dist(model_path, map_location=None): """In distributed setting, this function only download checkpoint at local rank 0.""" try: from pavi import modelcloud except ImportError: raise ImportError("Please install pavi to load checkpoint from modelcloud.") rank, world_size = get_dist_info() rank = int(os.environ.get("LOCAL_RANK", rank)) if rank == 0: model = modelcloud.get(model_path) with TemporaryDirectory() as tmp_dir: downloaded_file = osp.join(tmp_dir, model.name) model.download(downloaded_file) checkpoint = torch.load(downloaded_file, map_location=map_location) if world_size > 1: torch.distributed.barrier() if rank > 0: model = modelcloud.get(model_path) with TemporaryDirectory() as tmp_dir: downloaded_file = osp.join(tmp_dir, model.name) model.download(downloaded_file) checkpoint = torch.load(downloaded_file, map_location=map_location) return checkpoint def load_fileclient_dist(filename, backend, map_location): """In distributed setting, this function only download checkpoint at local rank 0.""" rank, world_size = get_dist_info() rank = int(os.environ.get("LOCAL_RANK", rank)) allowed_backends = ["ceph"] if backend not in allowed_backends: raise ValueError(f"Load from Backend {backend} is not supported.") if rank == 0: fileclient = FileClient(backend=backend) buffer = io.BytesIO(fileclient.get(filename)) checkpoint = torch.load(buffer, map_location=map_location) if world_size > 1: torch.distributed.barrier() if rank > 0: fileclient = FileClient(backend=backend) buffer = io.BytesIO(fileclient.get(filename)) checkpoint = torch.load(buffer, map_location=map_location) return checkpoint def load_state_dict(module, state_dict, strict=False, logger=None): """Load state_dict to a module. This method is modified from :meth:`torch.nn.Module.load_state_dict`. Default value for ``strict`` is set to ``False`` and the message for param mismatch will be shown even if strict is False. Args: module (Module): Module that receives the state_dict. state_dict (OrderedDict): Weights. strict (bool): whether to strictly enforce that the keys in :attr:`state_dict` match the keys returned by this module's :meth:`~torch.nn.Module.state_dict` function. Default: ``False``. logger (:obj:`logging.Logger`, optional): Logger to log the error message. If not specified, print function will be used. """ unexpected_keys = [] all_missing_keys = [] err_msg = [] metadata = getattr(state_dict, "_metadata", None) state_dict = state_dict.copy() if metadata is not None: state_dict._metadata = metadata # use _load_from_state_dict to enable checkpoint version control def load(module, prefix=""): # recursively check parallel module in case that the model has a # complicated structure, e.g., nn.Module(nn.Module(DDP)) if is_module_wrapper(module): module = module.module local_metadata = {} if metadata is None else metadata.get(prefix[:-1], {}) module._load_from_state_dict( state_dict, prefix, local_metadata, True, all_missing_keys, unexpected_keys, err_msg, ) for name, child in module._modules.items(): if child is not None: load(child, prefix + name + ".") load(module) load = None # break load->load reference cycle # ignore "num_batches_tracked" of BN layers missing_keys = [key for key in all_missing_keys if "num_batches_tracked" not in key] if unexpected_keys: err_msg.append( "unexpected key in source " f'state_dict: {", ".join(unexpected_keys)}\n' ) if missing_keys: err_msg.append( f'missing keys in source state_dict: {", ".join(missing_keys)}\n' ) rank, _ = get_dist_info() if len(err_msg) > 0 and rank == 0: err_msg.insert(0, "The model and loaded state dict do not match exactly\n") err_msg = "\n".join(err_msg) if strict: raise RuntimeError(err_msg) elif logger is not None: logger.warning(err_msg) else: print(err_msg) def _load_checkpoint(filename, map_location=None): """Load checkpoint from somewhere (modelzoo, file, url). Args: filename (str): Accept local filepath, URL, ``torchvision://xxx``, ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for details. map_location (str | None): Same as :func:`torch.load`. Default: None. Returns: dict | OrderedDict: The loaded checkpoint. It can be either an OrderedDict storing model weights or a dict containing other information, which depends on the checkpoint. """ if filename.startswith("modelzoo://"): warnings.warn( 'The URL scheme of "modelzoo://" is deprecated, please ' 'use "torchvision://" instead' ) model_urls = get_torchvision_models() model_name = filename[11:] checkpoint = load_url_dist(model_urls[model_name]) elif filename.startswith("torchvision://"): model_urls = get_torchvision_models() model_name = filename[14:] checkpoint = load_url_dist(model_urls[model_name]) elif filename.startswith("open-mmlab://"): model_urls = get_external_models() model_name = filename[13:] deprecated_urls = get_deprecated_model_names() if model_name in deprecated_urls: warnings.warn( f"open-mmlab://{model_name} is deprecated in favor " f"of open-mmlab://{deprecated_urls[model_name]}" ) model_name = deprecated_urls[model_name] model_url = model_urls[model_name] # check if is url if model_url.startswith(("http://", "https://")): checkpoint = load_url_dist(model_url) else: filename = osp.join(_get_mmcv_home(), model_url) if not osp.isfile(filename): raise IOError(f"{filename} is not a checkpoint file") checkpoint = torch.load(filename, map_location=map_location) elif filename.startswith("mmcls://"): model_urls = get_mmcls_models() model_name = filename[8:] checkpoint = load_url_dist(model_urls[model_name]) checkpoint = _process_mmcls_checkpoint(checkpoint) elif filename.startswith(("http://", "https://")): checkpoint = load_url_dist(filename) elif filename.startswith("pavi://"): model_path = filename[7:] checkpoint = load_pavimodel_dist(model_path, map_location=map_location) elif filename.startswith("s3://"): checkpoint = load_fileclient_dist( filename, backend="ceph", map_location=map_location ) else: if not osp.isfile(filename): raise IOError(f"{filename} is not a checkpoint file") checkpoint = torch.load(filename, map_location=map_location) return checkpoint def load_checkpoint( model, filename, map_location="cpu", strict=False, logger=None, patch_padding="pad", part_features=None, ): """Load checkpoint from a file or URI. Args: model (Module): Module to load checkpoint. filename (str): Accept local filepath, URL, ``torchvision://xxx``, ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for details. map_location (str): Same as :func:`torch.load`. strict (bool): Whether to allow different params for the model and checkpoint. logger (:mod:`logging.Logger` or None): The logger for error message. patch_padding (str): 'pad' or 'bilinear' or 'bicubic', used for interpolate patch embed from 14x14 to 16x16 Returns: dict or OrderedDict: The loaded checkpoint. """ checkpoint = _load_checkpoint(filename, map_location) # OrderedDict is a subclass of dict if not isinstance(checkpoint, dict): raise RuntimeError(f"No state_dict found in checkpoint file {filename}") # get state_dict from checkpoint if "state_dict" in checkpoint: state_dict = checkpoint["state_dict"] elif "model" in checkpoint: state_dict = checkpoint["model"] elif "module" in checkpoint: state_dict = checkpoint["module"] else: state_dict = checkpoint # strip prefix of state_dict if list(state_dict.keys())[0].startswith("module."): state_dict = {k[7:]: v for k, v in state_dict.items()} # for MoBY, load model of online branch if sorted(list(state_dict.keys()))[0].startswith("encoder"): state_dict = { k.replace("encoder.", ""): v for k, v in state_dict.items() if k.startswith("encoder.") } rank, _ = get_dist_info() layer_names = [name for name, param in model.named_parameters()] if ( "patch_embed.proj.weight" in state_dict and "patch_embed.proj._conv_stem.weight" not in layer_names ): proj_weight = state_dict["patch_embed.proj.weight"] orig_size = proj_weight.shape[2:] current_size = model.patch_embed.proj.weight.shape[2:] padding_size = current_size[0] - orig_size[0] padding_l = padding_size // 2 padding_r = padding_size - padding_l if orig_size != current_size: if "pad" in patch_padding: proj_weight = torch.nn.functional.pad( proj_weight, (padding_l, padding_r, padding_l, padding_r) ) elif "bilinear" in patch_padding: proj_weight = torch.nn.functional.interpolate( proj_weight, size=current_size, mode="bilinear", align_corners=False ) elif "bicubic" in patch_padding: proj_weight = torch.nn.functional.interpolate( proj_weight, size=current_size, mode="bicubic", align_corners=False ) state_dict["patch_embed.proj.weight"] = proj_weight if "pos_embed" in state_dict: pos_embed_checkpoint = state_dict["pos_embed"] # pos_embed_checkpoint = pos_embed_checkpoint[:, :-1, :] embedding_size = pos_embed_checkpoint.shape[-1] H, W = model.patch_embed.patch_shape num_patches = model.patch_embed.num_patches num_extra_tokens = model.pos_embed.shape[-2] - num_patches # height (== width) for the checkpoint position embedding orig_size = int((pos_embed_checkpoint.shape[-2] - num_extra_tokens) ** 0.5) if rank == 0: print( "Position interpolate from %dx%d to %dx%d" % (orig_size, orig_size, H, W) ) extra_tokens = pos_embed_checkpoint[:, :num_extra_tokens] # only the position tokens are interpolated pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:] pos_tokens = pos_tokens.reshape( -1, orig_size, orig_size, embedding_size ).permute(0, 3, 1, 2) pos_tokens = torch.nn.functional.interpolate( pos_tokens, size=(H, W), mode="bicubic", align_corners=False ) pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(1, 2) new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=1) state_dict["pos_embed"] = new_pos_embed new_state_dict = copy.deepcopy(state_dict) if part_features is not None: current_keys = list(model.state_dict().keys()) for key in current_keys: if "mlp.experts" in key: source_key = re.sub(r"experts.\d+.", "fc2.", key) new_state_dict[key] = state_dict[source_key][-part_features:] elif "fc2" in key: new_state_dict[key] = state_dict[key][:-part_features] # load state_dict load_state_dict(model, new_state_dict, strict, logger) return checkpoint def swin_converter(ckpt): new_ckpt = OrderedDict() def correct_unfold_reduction_order(x): out_channel, in_channel = x.shape x = x.reshape(out_channel, 4, in_channel // 4) x = x[:, [0, 2, 1, 3], :].transpose(1, 2).reshape(out_channel, in_channel) return x def correct_unfold_norm_order(x): in_channel = x.shape[0] x = x.reshape(4, in_channel // 4) x = x[[0, 2, 1, 3], :].transpose(0, 1).reshape(in_channel) return x for k, v in ckpt.items(): if k.startswith("head"): continue elif k.startswith("layers"): new_v = v if "attn." in k: new_k = k.replace("attn.", "attn.w_msa.") elif "mlp." in k: if "mlp.fc1." in k: new_k = k.replace("mlp.fc1.", "ffn.layers.0.0.") elif "mlp.fc2." in k: new_k = k.replace("mlp.fc2.", "ffn.layers.1.") else: new_k = k.replace("mlp.", "ffn.") elif "downsample" in k: new_k = k if "reduction." in k: new_v = correct_unfold_reduction_order(v) elif "norm." in k: new_v = correct_unfold_norm_order(v) else: new_k = k new_k = new_k.replace("layers", "stages", 1) elif k.startswith("patch_embed"): new_v = v if "proj" in k: new_k = k.replace("proj", "projection") else: new_k = k else: new_v = v new_k = k new_ckpt["backbone." + new_k] = new_v return new_ckpt