mirror of
https://github.com/hanzoai/mugen.git
synced 2026-10-30 18:27:38 +00:00
82 lines
3.6 KiB
Python
82 lines
3.6 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
from collections import defaultdict
|
|
import logging
|
|
import typing as tp
|
|
|
|
import flashy
|
|
import torch
|
|
|
|
from ..optim import ModuleDictEMA
|
|
from .utils import copy_state
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BestStateDictManager(flashy.state.StateDictSource):
|
|
"""BestStateDictManager maintains a copy of best state_dict() for registered sources.
|
|
|
|
BestStateDictManager has two main attributes:
|
|
states (dict): State dict of the registered StateDictSource.
|
|
param_ids (dict): Dict of parameter ids for registered states from ModuleDictEMA and other sources.
|
|
|
|
When registering new sources, the BestStateDictManager will ensure two conflicting sources between
|
|
ModuleDictEMA and original modules are not both registered as it would otherwise create ambiguity about
|
|
what to consider for best state.
|
|
|
|
Args:
|
|
device (torch.device or str): Device on which we keep the copy.
|
|
dtype (torch.dtype): Data type for the state parameters.
|
|
"""
|
|
def __init__(self, device: tp.Union[torch.device, str] = 'cpu',
|
|
dtype: tp.Optional[torch.dtype] = None):
|
|
self.device = device
|
|
self.states: dict = {}
|
|
self.param_ids: dict = defaultdict(dict)
|
|
self.dtype = dtype
|
|
|
|
def _get_parameter_ids(self, state_dict):
|
|
return {id(p): name for name, p in state_dict.items() if isinstance(p, torch.Tensor)}
|
|
|
|
def _validate_no_parameter_ids_overlap(self, name: str, param_ids: dict):
|
|
for registered_name, registered_param_ids in self.param_ids.items():
|
|
if registered_name != name:
|
|
overlap = set.intersection(registered_param_ids.keys(), param_ids.keys())
|
|
assert len(overlap) == 0, f"Found {len(overlap)} / {len(param_ids.keys())} overlapping parameters"
|
|
f" in {name} and already registered {registered_name}: {' '.join(overlap)}"
|
|
|
|
def update(self, name: str, source: flashy.state.StateDictSource):
|
|
if name not in self.states:
|
|
raise ValueError(f"{name} missing from registered states.")
|
|
self.states[name] = copy_state(source.state_dict(), device=self.device, dtype=self.dtype)
|
|
|
|
def register(self, name: str, source: flashy.state.StateDictSource):
|
|
if name in self.states:
|
|
raise ValueError(f"{name} already present in states.")
|
|
# Registering parameter ids for EMA and non-EMA states allows us to check that
|
|
# there is no overlap that would create ambiguity about how to handle the best state
|
|
param_ids = self._get_parameter_ids(source.state_dict())
|
|
if isinstance(source, ModuleDictEMA):
|
|
logger.debug(f"Registering to best state: ModuleDictEMA '{name}' with {len(param_ids)} params")
|
|
self._validate_no_parameter_ids_overlap(name, param_ids)
|
|
self.param_ids[name] = param_ids
|
|
else:
|
|
logger.debug(f"Registering to best state: StateDictSource '{name}' with {len(param_ids)} params")
|
|
self._validate_no_parameter_ids_overlap('base', param_ids)
|
|
self.param_ids['base'].update(param_ids)
|
|
# Register state
|
|
self.states[name] = copy_state(source.state_dict(), device=self.device, dtype=self.dtype)
|
|
|
|
def state_dict(self) -> flashy.state.StateDict:
|
|
return self.states
|
|
|
|
def load_state_dict(self, state: flashy.state.StateDict):
|
|
for name, sub_state in state.items():
|
|
for k, v in sub_state.items():
|
|
self.states[name][k].copy_(v)
|