autodl-projects/lib/xlayers/super_module.py

187 lines
6.2 KiB
Python
Raw Normal View History

2021-03-18 09:02:55 +01:00
#####################################################
2021-03-20 08:56:37 +01:00
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2021.03 #
2021-03-18 09:02:55 +01:00
#####################################################
2021-05-13 09:57:41 +02:00
import os
2021-03-18 09:02:55 +01:00
import abc
2021-05-13 09:57:41 +02:00
import tempfile
import warnings
2021-03-20 08:56:37 +01:00
from typing import Optional, Union, Callable
import torch
2021-03-17 11:06:29 +01:00
import torch.nn as nn
2021-03-18 11:32:26 +01:00
from enum import Enum
2021-03-19 08:17:49 +01:00
import spaces
2021-05-12 07:54:06 +02:00
from .super_utils import IntSpaceType, BoolSpaceType
from .super_utils import LayerOrder, SuperRunMode
from .super_utils import TensorContainer
from .super_utils import ShapeContainer
2021-05-06 10:38:58 +02:00
2021-05-13 09:57:41 +02:00
BEST_DIR_KEY = "best_model_dir"
BEST_SCORE_KEY = "best_model_score"
2021-05-06 10:38:58 +02:00
2021-03-18 13:15:50 +01:00
class SuperModule(abc.ABC, nn.Module):
2021-03-18 09:02:55 +01:00
"""This class equips the nn.Module class with the ability to apply AutoDL."""
def __init__(self):
super(SuperModule, self).__init__()
2021-03-18 13:15:50 +01:00
self._super_run_type = SuperRunMode.Default
2021-03-19 08:17:49 +01:00
self._abstract_child = None
2021-03-20 08:56:37 +01:00
self._verbose = False
2021-05-13 09:57:41 +02:00
self._meta_info = {}
2021-03-18 09:02:55 +01:00
2021-03-19 08:17:49 +01:00
def set_super_run_type(self, super_run_type):
def _reset_super_run(m):
if isinstance(m, SuperModule):
m._super_run_type = super_run_type
self.apply(_reset_super_run)
def add_module(self, name: str, module: Optional[torch.nn.Module]) -> None:
if not isinstance(module, SuperModule):
warnings.warn(
2021-04-23 11:14:49 +02:00
"Add {:}:{:} module, which is not SuperModule, into {:}".format(
name, module.__class__.__name__, self.__class__.__name__
)
+ "\n"
+ "It may cause some functions invalid."
)
super(SuperModule, self).add_module(name, module)
2021-03-20 08:56:37 +01:00
def apply_verbose(self, verbose):
def _reset_verbose(m):
if isinstance(m, SuperModule):
m._verbose = verbose
self.apply(_reset_verbose)
2021-03-19 16:57:23 +01:00
def apply_candidate(self, abstract_child):
2021-03-19 08:17:49 +01:00
if not isinstance(abstract_child, spaces.VirtualNode):
raise ValueError(
"Invalid abstract child program: {:}".format(abstract_child)
)
self._abstract_child = abstract_child
2021-05-07 08:27:15 +02:00
def get_w_container(self):
2021-05-06 10:38:58 +02:00
container = TensorContainer()
for name, param in self.named_parameters():
container.append(name, param, True)
for name, buf in self.named_buffers():
container.append(name, buf, False)
return container
2021-05-12 13:09:17 +02:00
def analyze_weights(self):
with torch.no_grad():
for name, param in self.named_parameters():
shapestr = "[{:10s}] shape={:}".format(name, list(param.shape))
finalstr = shapestr + "{:.2f} +- {:.2f}".format(
param.mean(), param.std()
)
print(finalstr)
2021-05-13 05:40:04 +02:00
def numel(self, buffer=True):
total = 0
for name, param in self.named_parameters():
total += param.numel()
if buffer:
for name, buf in self.named_buffers():
total += buf.numel()
return total
2021-05-13 09:57:41 +02:00
def save_best(self, score):
if BEST_DIR_KEY not in self._meta_info:
tempdir = tempfile.mkdtemp("-xlayers")
self._meta_info[BEST_DIR_KEY] = tempdir
if BEST_SCORE_KEY not in self._meta_info:
self._meta_info[BEST_SCORE_KEY] = None
best_score = self._meta_info[BEST_SCORE_KEY]
if best_score is None or best_score < score:
best_save_path = os.path.join(
self._meta_info[BEST_DIR_KEY],
"best-{:}.pth".format(self.__class__.__name__),
)
self._meta_info[BEST_SCORE_KEY] = score
torch.save(self.state_dict(), best_save_path)
return True, self._meta_info[BEST_SCORE_KEY]
else:
return False, self._meta_info[BEST_SCORE_KEY]
def load_best(self):
if BEST_DIR_KEY not in self._meta_info or BEST_SCORE_KEY not in self._meta_info:
raise ValueError("Please call save_best at first")
best_save_path = os.path.join(
self._meta_info[BEST_DIR_KEY],
"best-{:}.pth".format(self.__class__.__name__),
)
state_dict = torch.load(best_save_path)
self.load_state_dict(state_dict)
2021-03-19 08:17:49 +01:00
@property
2021-03-18 09:02:55 +01:00
def abstract_search_space(self):
raise NotImplementedError
2021-03-18 11:32:26 +01:00
@property
def super_run_type(self):
return self._super_run_type
2021-03-19 08:17:49 +01:00
@property
def abstract_child(self):
return self._abstract_child
2021-03-20 08:56:37 +01:00
@property
def verbose(self):
return self._verbose
2021-03-18 11:32:26 +01:00
@abc.abstractmethod
def forward_raw(self, *inputs):
2021-03-19 08:17:49 +01:00
"""Use the largest candidate for forward. Similar to the original PyTorch model."""
raise NotImplementedError
@abc.abstractmethod
def forward_candidate(self, *inputs):
2021-03-18 11:32:26 +01:00
raise NotImplementedError
2021-03-20 08:56:37 +01:00
@property
def name_with_id(self):
return "name={:}, id={:}".format(self.__class__.__name__, id(self))
def get_shape_str(self, tensors):
if isinstance(tensors, (list, tuple)):
shapes = [self.get_shape_str(tensor) for tensor in tensors]
if len(shapes) == 1:
return shapes[0]
else:
return ", ".join(shapes)
elif isinstance(tensors, (torch.Tensor, nn.Parameter)):
return str(tuple(tensors.shape))
else:
raise TypeError("Invalid input type: {:}.".format(type(tensors)))
2021-03-18 11:32:26 +01:00
def forward(self, *inputs):
2021-03-20 08:56:37 +01:00
if self.verbose:
print(
"[{:}] inputs shape: {:}".format(
self.name_with_id, self.get_shape_str(inputs)
)
)
2021-03-18 11:32:26 +01:00
if self.super_run_type == SuperRunMode.FullModel:
2021-03-20 08:56:37 +01:00
outputs = self.forward_raw(*inputs)
2021-03-19 08:17:49 +01:00
elif self.super_run_type == SuperRunMode.Candidate:
2021-03-20 08:56:37 +01:00
outputs = self.forward_candidate(*inputs)
2021-03-18 11:32:26 +01:00
else:
raise ModeError(
"Unknown Super Model Run Mode: {:}".format(self.super_run_type)
)
2021-03-20 08:56:37 +01:00
if self.verbose:
print(
"[{:}] outputs shape: {:}".format(
self.name_with_id, self.get_shape_str(outputs)
)
)
return outputs
2021-05-07 04:26:35 +02:00
def forward_with_container(self, inputs, container, prefix=[]):
raise NotImplementedError