Files
SayanoAI-Comfy-RVC/lib/infer_pack/loaders.py
T

129 lines
5.6 KiB
Python

from collections import defaultdict
import os
from typing import Dict, List, Optional
import torch
from transformers import HubertModel, HubertConfig
import safetensors.torch as st
from safetensors import safe_open
import json
class HubertModelWithFinalProj(HubertModel):
def __init__(self, config):
super().__init__(config)
# The final projection layer is only used for backward compatibility.
# Following https://github.com/auspicious3000/contentvec/issues/6
# Remove this layer is necessary to achieve the desired outcome.
self.final_proj = torch.nn.Linear(config.hidden_size, config.classifier_proj_size)
@staticmethod
def from_safetensors(path: str, device="cpu", framework="pt"):
assert path.endswith(".safetensors"), f"{path} must end with '.safetensors'"
with safe_open(path, framework=framework, device="cpu") as f:
metadata = f.metadata()
state_dict = {}
for key in f.keys():
state_dict[key] = f.get_tensor(key)
model = HubertModelWithFinalProj(HubertConfig.from_dict(json.loads(metadata["config"])))
model.load_state_dict(state_dict=state_dict)
model.eval()
return model.to(device)
def to_safetensors(self, sf_filename: str, discard_names: Optional[List[str]]=[]):
assert hasattr(self, "state_dict"), f"Model does not have state_dict"
state_dict = self.state_dict()
to_removes = _remove_duplicate_names(state_dict, discard_names=discard_names)
metadata = dict(format="pt", config=json.dumps(self.config.to_dict()))
for kept_name, to_remove_group in to_removes.items():
for to_remove in to_remove_group:
if to_remove not in metadata:
metadata[to_remove] = kept_name
del state_dict[to_remove]
# Force tensors to be contiguous
state_dict = {k: v.contiguous() for k, v in state_dict.items()}
dirname = os.path.dirname(sf_filename)
os.makedirs(dirname, exist_ok=True)
st.save_file(state_dict, sf_filename, metadata=metadata)
reloaded = HubertModelWithFinalProj.from_safetensors(sf_filename, device=self.device)
assert self.equals(reloaded), f"{sf_filename} does not equal source!"
del reloaded
def extract_features(self, source: torch.Tensor, version="v2", **kwargs):
with torch.no_grad():
output_layer = 9 if version == "v1" else 12
# https://github.com/facebookresearch/fairseq/blob/main/fairseq/models/hubert/hubert.py#L476
output = self(source.to(self.config.torch_dtype), output_hidden_states=True)["hidden_states"][output_layer-1]
features = self.final_proj(output) if version == "v1" else output
return features.to(source.dtype)
def equals(self, other: "HubertModelWithFinalProj"):
# check config
if self.config.to_dict()!=other.config.to_dict(): return False
# check parameters
self_dict = self.state_dict()
other_dict = other.state_dict()
for k in other_dict:
if not torch.equal(self_dict[k], other_dict[k]):
print(f"{k} is not the same")
return False
# for p1, p2 in zip(self.parameters(), other.parameters()):
# if p1.data.ne(p2.data).sum() > 0: return False
# check output
inputs = torch.ones((1,16000))
if self(inputs).last_hidden_state.data.ne(other(inputs).last_hidden_state.data).sum() > 0: return False
return True
# from: https://github.com/huggingface/safetensors/blob/main/bindings/python/convert.py#L36
def _remove_duplicate_names(
state_dict: Dict[str, torch.Tensor],
*,
preferred_names: List[str] = None,
discard_names: List[str] = None,
) -> Dict[str, List[str]]:
if preferred_names is None:
preferred_names = []
preferred_names = set(preferred_names)
if discard_names is None:
discard_names = []
discard_names = set(discard_names)
shareds = st._find_shared_tensors(state_dict)
to_remove = defaultdict(list)
for shared in shareds:
complete_names = set([name for name in shared if st._is_complete(state_dict[name])])
if not complete_names:
if len(shared) == 1:
# Force contiguous
name = list(shared)[0]
state_dict[name] = state_dict[name].clone()
complete_names = {name}
else:
raise RuntimeError(
f"Error while trying to find names to remove to save state dict, but found no suitable name to keep for saving amongst: {shared}. None is covering the entire storage.Refusing to save/load the model since you could be storing much more memory than needed. Please refer to https://huggingface.co/docs/safetensors/torch_shared_tensors for more information. Or open an issue."
)
keep_name = sorted(list(complete_names))[0]
# Mecanism to preferentially select keys to keep
# coming from the on-disk file to allow
# loading models saved with a different choice
# of keep_name
preferred = complete_names.difference(discard_names)
if preferred:
keep_name = sorted(list(preferred))[0]
if preferred_names:
preferred = preferred_names.intersection(complete_names)
if preferred:
keep_name = sorted(list(preferred))[0]
for name in sorted(shared):
if name != keep_name:
to_remove[keep_name].append(name)
return to_remove