chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:35:17 +08:00
commit 344816a5d8
136 changed files with 25044 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
*.ds
*.onnx
*.npy
*.wav
temp/
cache/
assets/
View File
+32
View File
@@ -0,0 +1,32 @@
import numpy as np
import onnxruntime as ort
import tqdm
n_tokens = 10
n_frames = 100
n_runs = 20
speedup = 20
provider = 'DmlExecutionProvider'
tokens = np.array([[1] * n_tokens], dtype=np.int64)
durations = np.array([[n_frames // n_tokens] * n_tokens], dtype=np.int64)
f0 = np.array([[440.] * n_frames], dtype=np.float32)
speedup = np.array(speedup, dtype=np.int64)
session = ort.InferenceSession('model1.onnx', providers=[provider])
for _ in tqdm.tqdm(range(n_runs)):
session.run(['mel'], {
'tokens': tokens,
'durations': durations,
'f0': f0,
'speedup': speedup
})
session = ort.InferenceSession('model2.onnx', providers=[provider])
for _ in tqdm.tqdm(range(n_runs)):
session.run(['mel'], {
'tokens': tokens,
'durations': durations,
'f0': f0,
'speedup': speedup
})
@@ -0,0 +1,16 @@
import numpy as np
import onnxruntime as ort
import tqdm
n_frames = 1000
n_runs = 20
mel = np.random.randn(1, n_frames, 128).astype(np.float32)
f0 = np.random.randn(1, n_frames).astype(np.float32) + 440.
provider = 'DmlExecutionProvider'
session = ort.InferenceSession('nsf_hifigan.onnx', providers=[provider])
for _ in tqdm.tqdm(range(n_runs)):
session.run(['waveform'], {
'mel': mel,
'f0': f0
})
+3
View File
@@ -0,0 +1,3 @@
from .acoustic_exporter import DiffSingerAcousticExporter
from .variance_exporter import DiffSingerVarianceExporter
from .nsf_hifigan_exporter import NSFHiFiGANExporter
+424
View File
@@ -0,0 +1,424 @@
import json
from pathlib import Path
from typing import Union, List, Tuple, Dict
import onnx
import onnxsim
import torch
import yaml
from basics.base_exporter import BaseExporter
from deployment.modules.toplevel import DiffSingerAcousticONNX
from modules.fastspeech.param_adaptor import VARIANCE_CHECKLIST
from utils import load_ckpt, onnx_helper, remove_suffix
from utils.hparams import hparams
from utils.phoneme_utils import load_phoneme_dictionary
class DiffSingerAcousticExporter(BaseExporter):
def __init__(
self,
device: Union[str, torch.device] = 'cpu',
cache_dir: Path = None,
ckpt_steps: int = None,
freeze_gender: float = None,
freeze_velocity: bool = False,
export_spk: List[Tuple[str, Dict[str, float]]] = None,
freeze_spk: Tuple[str, Dict[str, float]] = None
):
super().__init__(device=device, cache_dir=cache_dir)
# Basic attributes
self.model_name: str = hparams['exp_name']
self.ckpt_steps: int = ckpt_steps
self.spk_map: dict = self.build_spk_map()
self.lang_map: dict = self.build_lang_map()
self.phoneme_dictionary = load_phoneme_dictionary()
self.use_lang_id = hparams.get('use_lang_id', False) and len(self.phoneme_dictionary.cross_lingual_phonemes) > 0
self.model = self.build_model()
self.fs2_aux_cache_path = self.cache_dir / (
'fs2_aux.onnx' if self.model.use_shallow_diffusion else 'fs2.onnx'
)
self.diffusion_cache_path = self.cache_dir / 'diffusion.onnx'
# Attributes for logging
self.model_class_name = remove_suffix(self.model.__class__.__name__, 'ONNX')
fs2_aux_cls_logging = [remove_suffix(self.model.fs2.__class__.__name__, 'ONNX')]
if self.model.use_shallow_diffusion:
fs2_aux_cls_logging.append(remove_suffix(
self.model.aux_decoder.decoder.__class__.__name__, 'ONNX'
))
self.fs2_aux_class_name = ', '.join(fs2_aux_cls_logging)
self.aux_decoder_class_name = remove_suffix(
self.model.aux_decoder.decoder.__class__.__name__, 'ONNX'
) if self.model.use_shallow_diffusion else None
self.backbone_class_name = remove_suffix(self.model.diffusion.backbone.__class__.__name__, 'ONNX')
self.diffusion_class_name = remove_suffix(self.model.diffusion.__class__.__name__, 'ONNX')
# Attributes for exporting
self.expose_gender = freeze_gender is None
self.expose_velocity = not freeze_velocity
self.freeze_spk: Tuple[str, Dict[str, float]] = freeze_spk \
if hparams['use_spk_id'] else None
self.export_spk: List[Tuple[str, Dict[str, float]]] = export_spk \
if hparams['use_spk_id'] and export_spk is not None else []
if hparams['use_key_shift_embed'] and not self.expose_gender:
shift_min, shift_max = hparams['augmentation_args']['random_pitch_shifting']['range']
key_shift = freeze_gender * shift_max if freeze_gender >= 0. else freeze_gender * abs(shift_min)
key_shift = max(min(key_shift, shift_max), shift_min) # clip key shift
self.model.fs2.register_buffer('frozen_key_shift', torch.FloatTensor([key_shift]).to(self.device))
if hparams['use_spk_id']:
if not self.export_spk and self.freeze_spk is None:
# In case the user did not specify any speaker settings:
if len(self.spk_map) == 1:
# If there is only one speaker, freeze him/her.
first_spk = next(iter(self.spk_map.keys()))
self.freeze_spk = (first_spk, {first_spk: 1.0})
else:
# If there are multiple speakers, export them all.
self.export_spk = [(name, {name: 1.0}) for name in self.spk_map.keys()]
if self.freeze_spk is not None:
self.model.fs2.register_buffer('frozen_spk_embed', self._perform_spk_mix(self.freeze_spk[1]))
def build_model(self) -> DiffSingerAcousticONNX:
model = DiffSingerAcousticONNX(
vocab_size=len(self.phoneme_dictionary),
out_dims=hparams['audio_num_mel_bins'],
cross_lingual_token_idx=sorted({
self.phoneme_dictionary.encode_one(p)
for p in self.phoneme_dictionary.cross_lingual_phonemes
})
).eval().to(self.device)
load_ckpt(model, hparams['work_dir'], ckpt_steps=self.ckpt_steps,
prefix_in_ckpt='model', strict=True, device=self.device)
return model
def export(self, path: Path):
path.mkdir(parents=True, exist_ok=True)
model_name = self.model_name
if self.freeze_spk is not None:
model_name += '.' + self.freeze_spk[0]
self.export_model(path / f'{model_name}.onnx')
self.export_attachments(path)
def export_model(self, path: Path):
self._torch_export_model()
fs2_aux_onnx = self._optimize_fs2_aux_graph(onnx.load(self.fs2_aux_cache_path))
diffusion_onnx = self._optimize_diffusion_graph(onnx.load(self.diffusion_cache_path))
model_onnx = self._merge_fs2_aux_diffusion_graphs(fs2_aux_onnx, diffusion_onnx)
onnx.save(model_onnx, path)
self.fs2_aux_cache_path.unlink()
self.diffusion_cache_path.unlink()
print(f'| export model => {path}')
def export_attachments(self, path: Path):
for spk in self.export_spk:
self._export_spk_embed(
path / f'{self.model_name}.{spk[0]}.emb',
self._perform_spk_mix(spk[1])
)
self.export_dictionaries(path)
self._export_phonemes(path)
model_name = self.model_name
if self.freeze_spk is not None:
model_name += '.' + self.freeze_spk[0]
dsconfig = {
# basic configs
'phonemes': f'{self.model_name}.phonemes.json',
'languages': f'{self.model_name}.languages.json',
'use_lang_id': self.use_lang_id,
'acoustic': f'{model_name}.onnx',
'hidden_size': hparams['hidden_size'],
'vocoder': 'pc_nsf_hifigan_44.1k_hop512_128bin_2025.02',
}
# multi-speaker
if len(self.export_spk) > 0:
dsconfig['speakers'] = [f'{self.model_name}.{spk[0]}' for spk in self.export_spk]
# parameters
if self.expose_gender:
dsconfig['augmentation_args'] = {
'random_pitch_shifting': {
'range': hparams['augmentation_args']['random_pitch_shifting']['range']
}
}
dsconfig['use_key_shift_embed'] = self.expose_gender
dsconfig['use_speed_embed'] = self.expose_velocity
for variance in VARIANCE_CHECKLIST:
dsconfig[f'use_{variance}_embed'] = (variance in self.model.fs2.variance_embed_list)
# sampling acceleration and shallow diffusion
dsconfig['use_continuous_acceleration'] = True
dsconfig['use_variable_depth'] = self.model.use_shallow_diffusion
dsconfig['max_depth'] = 1 - self.model.diffusion.t_start
# mel specification
dsconfig['sample_rate'] = hparams['audio_sample_rate']
dsconfig['hop_size'] = hparams['hop_size']
dsconfig['win_size'] = hparams['win_size']
dsconfig['fft_size'] = hparams['fft_size']
dsconfig['num_mel_bins'] = hparams['audio_num_mel_bins']
dsconfig['mel_fmin'] = hparams['fmin']
dsconfig['mel_fmax'] = hparams['fmax'] if hparams['fmax'] is not None else hparams['audio_sample_rate'] / 2
dsconfig['mel_base'] = 'e'
dsconfig['mel_scale'] = 'slaney'
config_path = path / 'dsconfig.yaml'
with open(config_path, 'w', encoding='utf8') as fw:
yaml.safe_dump(dsconfig, fw, sort_keys=False)
print(f'| export configs => {config_path} **PLEASE EDIT BEFORE USE**')
@torch.no_grad()
def _torch_export_model(self):
# Prepare inputs for FastSpeech2 and aux decoder tracing
n_frames = 10
tokens = torch.LongTensor([[1]]).to(self.device)
durations = torch.LongTensor([[n_frames]]).to(self.device)
f0 = torch.FloatTensor([[440.] * n_frames]).to(self.device)
variances = {
v_name: torch.zeros(1, n_frames, dtype=torch.float32, device=self.device)
for v_name in self.model.fs2.variance_embed_list
}
kwargs: Dict[str, torch.Tensor] = {}
arguments = (tokens, durations, f0, variances, kwargs)
input_names = ['tokens', 'durations', 'f0'] + self.model.fs2.variance_embed_list
dynamic_axes = {
'tokens': {
1: 'n_tokens'
},
'durations': {
1: 'n_tokens'
},
'f0': {
1: 'n_frames'
},
**{
v_name: {
1: 'n_frames'
}
for v_name in self.model.fs2.variance_embed_list
}
}
if hparams['use_key_shift_embed']:
if self.expose_gender:
kwargs['gender'] = torch.rand((1, n_frames), dtype=torch.float32, device=self.device)
input_names.append('gender')
dynamic_axes['gender'] = {
1: 'n_frames'
}
if hparams['use_speed_embed']:
if self.expose_velocity:
kwargs['velocity'] = torch.rand((1, n_frames), dtype=torch.float32, device=self.device)
input_names.append('velocity')
dynamic_axes['velocity'] = {
1: 'n_frames'
}
if hparams['use_spk_id'] and not self.freeze_spk:
kwargs['spk_embed'] = torch.rand(
(1, n_frames, hparams['hidden_size']),
dtype=torch.float32, device=self.device
)
input_names.append('spk_embed')
dynamic_axes['spk_embed'] = {
1: 'n_frames'
}
if self.use_lang_id:
kwargs['languages'] = torch.zeros_like(tokens)
input_names.append('languages')
dynamic_axes['languages'] = {
1: 'n_tokens'
}
dynamic_axes['condition'] = {
1: 'n_frames'
}
# PyTorch ONNX export for FastSpeech2 and aux decoder
output_names = ['condition']
if self.model.use_shallow_diffusion:
output_names.append('aux_mel')
dynamic_axes['aux_mel'] = {
1: 'n_frames'
}
print(f'Exporting {self.fs2_aux_class_name}...')
torch.onnx.export(
self.model.view_as_fs2_aux(),
arguments,
self.fs2_aux_cache_path,
input_names=input_names,
output_names=output_names,
dynamic_axes=dynamic_axes,
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
condition = torch.rand((1, n_frames, hparams['hidden_size']), device=self.device)
# Prepare inputs for backbone tracing and GaussianDiffusion scripting
shape = (1, 1, hparams['audio_num_mel_bins'], n_frames)
noise = torch.randn(shape, device=self.device)
x_aux = torch.randn((1, n_frames, hparams['audio_num_mel_bins']), device=self.device)
dummy_time = (torch.rand((1,), device=self.device) * self.model.diffusion.time_scale_factor).float()
dummy_depth = torch.tensor(0.1, device=self.device)
dummy_steps = 5
print(f'Tracing {self.backbone_class_name} backbone...')
if self.model.diffusion_type == 'ddpm':
major_mel_decoder = self.model.view_as_diffusion()
elif self.model.diffusion_type == 'reflow':
major_mel_decoder = self.model.view_as_reflow()
else:
raise ValueError(f'Invalid diffusion type: {self.model.diffusion_type}')
major_mel_decoder.diffusion.set_backbone(
torch.jit.trace(
major_mel_decoder.diffusion.backbone,
(
noise,
dummy_time,
condition.transpose(1, 2)
)
)
)
print(f'Scripting {self.diffusion_class_name}...')
diffusion_inputs = [
condition,
*([x_aux, dummy_depth] if self.model.use_shallow_diffusion else [])
]
major_mel_decoder = torch.jit.script(
major_mel_decoder,
example_inputs=[
(
*diffusion_inputs,
1 # p_sample branch
),
(
*diffusion_inputs,
dummy_steps # p_sample_plms branch
)
]
)
# PyTorch ONNX export for GaussianDiffusion
print(f'Exporting {self.diffusion_class_name}...')
torch.onnx.export(
major_mel_decoder,
(
*diffusion_inputs,
dummy_steps
),
self.diffusion_cache_path,
input_names=[
'condition',
*(['x_aux', 'depth'] if self.model.use_shallow_diffusion else []),
'steps'
],
output_names=[
'mel'
],
dynamic_axes={
'condition': {
1: 'n_frames'
},
**({'x_aux': {1: 'n_frames'}} if self.model.use_shallow_diffusion else {}),
'mel': {
1: 'n_frames'
}
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
@torch.no_grad()
def _perform_spk_mix(self, spk_mix: Dict[str, float]):
spk_mix_ids = []
spk_mix_values = []
for name, value in spk_mix.items():
spk_mix_ids.append(self.spk_map[name])
assert value >= 0., f'Speaker mix checks failed.\n' \
f'Proportion of speaker \'{name}\' is negative.'
spk_mix_values.append(value)
spk_mix_id_N = torch.LongTensor(spk_mix_ids).to(self.device)[None] # => [1, N]
spk_mix_value_N = torch.FloatTensor(spk_mix_values).to(self.device)[None] # => [1, N]
spk_mix_value_sum = spk_mix_value_N.sum()
assert spk_mix_value_sum > 0., 'Speaker mix checks failed.\n' \
'Proportions of speaker mix sum to zero.'
spk_mix_value_N /= spk_mix_value_sum # normalize
spk_mix_embed = torch.sum(
self.model.fs2.spk_embed(spk_mix_id_N) * spk_mix_value_N.unsqueeze(2), # => [1, N, H]
dim=1, keepdim=True
) # => [1, 1, H]
return spk_mix_embed
def _optimize_fs2_aux_graph(self, fs2: onnx.ModelProto) -> onnx.ModelProto:
print(f'Running ONNX Simplifier on {self.fs2_aux_class_name}...')
fs2, check = onnxsim.simplify(fs2, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.model_reorder_io_list(
fs2, 'input',
target_name='languages', insert_after_name='tokens'
)
print(f'| optimize graph: {self.fs2_aux_class_name}')
return fs2
def _optimize_diffusion_graph(self, diffusion: onnx.ModelProto) -> onnx.ModelProto:
onnx_helper.model_override_io_shapes(diffusion, output_shapes={
'mel': (1, 'n_frames', hparams['audio_num_mel_bins'])
})
print(f'Running ONNX Simplifier #1 on {self.diffusion_class_name}...')
diffusion, check = onnxsim.simplify(diffusion, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.graph_fold_back_to_squeeze(diffusion.graph)
onnx_helper.graph_extract_conditioner_projections(
graph=diffusion.graph, op_type='Conv',
weight_pattern=r'diffusion\..*\.conditioner_projection\.weight',
alias_prefix='/diffusion/backbone/cache'
)
onnx_helper.graph_remove_unused_values(diffusion.graph)
print(f'Running ONNX Simplifier #2 on {self.diffusion_class_name}...')
diffusion, check = onnxsim.simplify(
diffusion,
include_subgraph=True
)
assert check, 'Simplified ONNX model could not be validated'
print(f'| optimize graph: {self.diffusion_class_name}')
return diffusion
def _merge_fs2_aux_diffusion_graphs(self, fs2: onnx.ModelProto, diffusion: onnx.ModelProto) -> onnx.ModelProto:
onnx_helper.model_add_prefixes(
fs2, dim_prefix=('fs2aux.' if self.model.use_shallow_diffusion else 'fs2.'),
ignored_pattern=r'(n_tokens)|(n_frames)'
)
onnx_helper.model_add_prefixes(diffusion, dim_prefix='diffusion.', ignored_pattern='n_frames')
print(f'Merging {self.fs2_aux_class_name} and {self.diffusion_class_name} '
f'back into {self.model_class_name}...')
merged = onnx.compose.merge_models(
fs2, diffusion, io_map=[
('condition', 'condition'),
*([('aux_mel', 'x_aux')] if self.model.use_shallow_diffusion else []),
],
prefix1='', prefix2='', doc_string='',
producer_name=fs2.producer_name, producer_version=fs2.producer_version,
domain=fs2.domain, model_version=fs2.model_version
)
merged.graph.name = fs2.graph.name
print(f'Running ONNX Simplifier on {self.model_class_name}...')
merged, check = onnxsim.simplify(
merged,
include_subgraph=True
)
assert check, 'Simplified ONNX model could not be validated'
print(f'| optimize graph: {self.model_class_name}')
return merged
# noinspection PyMethodMayBeStatic
def _export_spk_embed(self, path: Path, spk_embed: torch.Tensor):
with open(path, 'wb') as f:
f.write(spk_embed.cpu().numpy().tobytes())
print(f'| export spk embed => {path}')
def _export_phonemes(self, path: Path):
ph_path = path / f'{self.model_name}.phonemes.json'
self.phoneme_dictionary.dump(ph_path)
print(f'| export phonemes => {ph_path}')
lang_path = path / f'{self.model_name}.languages.json'
with open(lang_path, 'w', encoding='utf8') as f:
json.dump(self.lang_map, f, ensure_ascii=False, indent=2)
print(f'| export languages => {lang_path}')
@@ -0,0 +1,127 @@
import json
from pathlib import Path
from typing import Union
import onnx
import onnxsim
import torch
import yaml
from torch import nn
from basics.base_exporter import BaseExporter
from deployment.modules.nsf_hifigan import NSFHiFiGANONNX
from utils import load_ckpt, onnx_helper, remove_suffix
from utils.hparams import hparams
class NSFHiFiGANExporter(BaseExporter):
def __init__(
self,
device: Union[str, torch.device] = 'cpu',
cache_dir: Path = None,
model_path: Path = None,
model_name: str = 'nsf_hifigan'
):
super().__init__(device=device, cache_dir=cache_dir)
self.model_path = model_path
self.model_name = model_name
self.vocoder_pitch_controllable = False
self.model = self.build_model()
self.model_class_name = remove_suffix(self.model.__class__.__name__, 'ONNX')
self.model_cache_path = (self.cache_dir / self.model_name).with_suffix('.onnx')
def build_model(self) -> nn.Module:
config_path = self.model_path.with_name('config.json')
with open(config_path, 'r', encoding='utf8') as f:
config = json.load(f)
assert hparams.get('mel_base') == 'e', (
"Mel base must be set to \'e\' according to 2nd stage of the migration plan. "
"See https://github.com/openvpi/DiffSinger/releases/tag/v2.3.0 for more details."
)
model = NSFHiFiGANONNX(config).eval().to(self.device)
self.vocoder_pitch_controllable = config.get("pc_aug", False)
load_ckpt(model.generator, str(self.model_path),
prefix_in_ckpt=None, key_in_ckpt='generator',
strict=True, device=self.device)
model.generator.remove_weight_norm()
return model
def export(self, path: Path):
path.mkdir(parents=True, exist_ok=True)
self.export_model(path / self.model_cache_path.name)
self.export_attachments(path)
def export_model(self, path: Path):
self._torch_export_model()
model_onnx = self._optimize_model_graph(onnx.load(self.model_cache_path))
onnx.save(model_onnx, path)
self.model_cache_path.unlink()
print(f'| export model => {path}')
def export_attachments(self, path: Path):
config_path = path / 'vocoder.yaml'
with open(config_path, 'w', encoding='utf8') as fw:
yaml.safe_dump({
# basic configs
'name': self.model_name,
'model': self.model_cache_path.name,
# mel specifications
'sample_rate': hparams['audio_sample_rate'],
'hop_size': hparams['hop_size'],
'win_size': hparams['win_size'],
'fft_size': hparams['fft_size'],
'num_mel_bins': hparams['audio_num_mel_bins'],
'mel_fmin': hparams['fmin'],
'mel_fmax': hparams['fmax'] if hparams['fmax'] is not None else hparams['audio_sample_rate'] / 2,
'mel_base': 'e',
'mel_scale': 'slaney',
'pitch_controllable': self.vocoder_pitch_controllable,
# Some old vocoder versions may have severe performance issues on CUDA;
# the issues were fixed in newer versions, and this flag is to distinguish them
'force_on_cpu': False,
}, fw, sort_keys=False)
print(f'| export configs => {config_path} **PLEASE EDIT BEFORE USE**')
@torch.no_grad()
def _torch_export_model(self):
# Prepare inputs for NSFHiFiGAN
n_frames = 10
mel = torch.randn((1, n_frames, hparams['audio_num_mel_bins']), dtype=torch.float32, device=self.device)
f0 = torch.randn((1, n_frames), dtype=torch.float32, device=self.device) + 440.
# PyTorch ONNX export for NSFHiFiGAN
print(f'Exporting {self.model_class_name}...')
torch.onnx.export(
self.model,
(
mel,
f0
),
self.model_cache_path,
input_names=[
'mel',
'f0'
],
output_names=[
'waveform'
],
dynamic_axes={
'mel': {
1: 'n_frames'
},
'f0': {
1: 'n_frames'
},
'waveform': {
1: 'n_samples'
}
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
def _optimize_model_graph(self, model: onnx.ModelProto) -> onnx.ModelProto:
print(f'Running ONNX simplifier for {self.model_class_name}...')
model, check = onnxsim.simplify(model, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
return model
+808
View File
@@ -0,0 +1,808 @@
import json
from pathlib import Path
from typing import Union, List, Tuple, Dict
import onnx
import onnxsim
import torch
import yaml
from basics.base_exporter import BaseExporter
from deployment.modules.toplevel import DiffSingerVarianceONNX
from modules.fastspeech.param_adaptor import VARIANCE_CHECKLIST
from utils import load_ckpt, onnx_helper, remove_suffix
from utils.hparams import hparams
from utils.phoneme_utils import load_phoneme_dictionary
class DiffSingerVarianceExporter(BaseExporter):
def __init__(
self,
device: Union[str, torch.device] = 'cpu',
cache_dir: Path = None,
ckpt_steps: int = None,
freeze_glide: bool = False,
freeze_expr: bool = False,
export_spk: List[Tuple[str, Dict[str, float]]] = None,
freeze_spk: Tuple[str, Dict[str, float]] = None
):
super().__init__(device=device, cache_dir=cache_dir)
# Basic attributes
self.model_name: str = hparams['exp_name']
self.ckpt_steps: int = ckpt_steps
self.spk_map: dict = self.build_spk_map()
self.lang_map: dict = self.build_lang_map()
self.phoneme_dictionary = load_phoneme_dictionary()
self.use_lang_id = hparams.get('use_lang_id', False) and len(self.phoneme_dictionary.cross_lingual_phonemes) > 0
self.model = self.build_model()
self.linguistic_encoder_cache_path = self.cache_dir / 'linguistic.onnx'
self.dur_predictor_cache_path = self.cache_dir / 'dur.onnx'
self.pitch_preprocess_cache_path = self.cache_dir / 'pitch_pre.onnx'
self.pitch_predictor_cache_path = self.cache_dir / 'pitch.onnx'
self.pitch_postprocess_cache_path = self.cache_dir / 'pitch_post.onnx'
self.variance_preprocess_cache_path = self.cache_dir / 'variance_pre.onnx'
self.multi_var_predictor_cache_path = self.cache_dir / 'variance.onnx'
self.variance_postprocess_cache_path = self.cache_dir / 'variance_post.onnx'
# Attributes for logging
self.fs2_class_name = remove_suffix(self.model.fs2.__class__.__name__, 'ONNX')
self.dur_predictor_class_name = \
remove_suffix(self.model.fs2.dur_predictor.__class__.__name__, 'ONNX') \
if self.model.predict_dur else None
self.pitch_backbone_class_name = \
remove_suffix(self.model.pitch_predictor.backbone.__class__.__name__, 'ONNX') \
if self.model.predict_pitch else None
self.pitch_predictor_class_name = \
remove_suffix(self.model.pitch_predictor.__class__.__name__, 'ONNX') \
if self.model.predict_pitch else None
self.variance_backbone_class_name = \
remove_suffix(self.model.variance_predictor.backbone.__class__.__name__, 'ONNX') \
if self.model.predict_variances else None
self.multi_var_predictor_class_name = \
remove_suffix(self.model.variance_predictor.__class__.__name__, 'ONNX') \
if self.model.predict_variances else None
# Attributes for exporting
self.expose_expr = not freeze_expr
self.freeze_glide = freeze_glide
self.freeze_spk: Tuple[str, Dict[str, float]] = freeze_spk \
if hparams['use_spk_id'] else None
self.export_spk: List[Tuple[str, Dict[str, float]]] = export_spk \
if hparams['use_spk_id'] and export_spk is not None else []
if hparams['use_spk_id']:
if not self.export_spk and self.freeze_spk is None:
# In case the user did not specify any speaker settings:
if len(self.spk_map) == 1:
# If there is only one speaker, freeze him/her.
first_spk = next(iter(self.spk_map.keys()))
self.freeze_spk = (first_spk, {first_spk: 1.0})
else:
# If there are multiple speakers, export them all.
self.export_spk = [(name, {name: 1.0}) for name in self.spk_map.keys()]
if self.freeze_spk is not None:
self.model.register_buffer('frozen_spk_embed', self._perform_spk_mix(self.freeze_spk[1]))
def build_model(self) -> DiffSingerVarianceONNX:
model = DiffSingerVarianceONNX(
vocab_size=len(self.phoneme_dictionary),
cross_lingual_token_idx=sorted({
self.phoneme_dictionary.encode_one(p)
for p in self.phoneme_dictionary.cross_lingual_phonemes
})
).eval().to(self.device)
load_ckpt(model, hparams['work_dir'], ckpt_steps=self.ckpt_steps,
prefix_in_ckpt='model', strict=True, device=self.device)
model.build_smooth_op(self.device)
return model
def export(self, path: Path):
path.mkdir(parents=True, exist_ok=True)
model_name = self.model_name
if self.freeze_spk is not None:
model_name += '.' + self.freeze_spk[0]
self.export_model(path, model_name)
self.export_attachments(path)
def export_model(self, path: Path, model_name: str = None):
self._torch_export_model()
linguistic_onnx = self._optimize_linguistic_graph(onnx.load(self.linguistic_encoder_cache_path))
linguistic_path = path / f'{model_name}.linguistic.onnx'
onnx.save(linguistic_onnx, linguistic_path)
print(f'| export linguistic encoder => {linguistic_path}')
self.linguistic_encoder_cache_path.unlink()
if self.model.predict_dur:
dur_predictor_onnx = self._optimize_dur_predictor_graph(onnx.load(self.dur_predictor_cache_path))
dur_predictor_path = path / f'{model_name}.dur.onnx'
onnx.save(dur_predictor_onnx, dur_predictor_path)
self.dur_predictor_cache_path.unlink()
print(f'| export dur predictor => {dur_predictor_path}')
if self.model.predict_pitch:
pitch_predictor_onnx = self._optimize_merge_pitch_predictor_graph(
onnx.load(self.pitch_preprocess_cache_path),
onnx.load(self.pitch_predictor_cache_path),
onnx.load(self.pitch_postprocess_cache_path)
)
pitch_predictor_path = path / f'{model_name}.pitch.onnx'
onnx.save(pitch_predictor_onnx, pitch_predictor_path)
self.pitch_preprocess_cache_path.unlink()
self.pitch_predictor_cache_path.unlink()
self.pitch_postprocess_cache_path.unlink()
print(f'| export pitch predictor => {pitch_predictor_path}')
if self.model.predict_variances:
variance_predictor_onnx = self._optimize_merge_variance_predictor_graph(
onnx.load(self.variance_preprocess_cache_path),
onnx.load(self.multi_var_predictor_cache_path),
onnx.load(self.variance_postprocess_cache_path)
)
variance_predictor_path = path / f'{model_name}.variance.onnx'
onnx.save(variance_predictor_onnx, variance_predictor_path)
self.variance_preprocess_cache_path.unlink()
self.multi_var_predictor_cache_path.unlink()
self.variance_postprocess_cache_path.unlink()
print(f'| export variance predictor => {variance_predictor_path}')
def export_attachments(self, path: Path):
for spk in self.export_spk:
self._export_spk_embed(
path / f'{self.model_name}.{spk[0]}.emb',
self._perform_spk_mix(spk[1])
)
self.export_dictionaries(path)
self._export_phonemes(path)
model_name = self.model_name
if self.freeze_spk is not None:
model_name += '.' + self.freeze_spk[0]
dsconfig = {
# basic configs
'phonemes': f'{self.model_name}.phonemes.json',
'languages': f'{self.model_name}.languages.json',
'use_lang_id': self.use_lang_id,
'linguistic': f'{model_name}.linguistic.onnx',
'hidden_size': self.model.hidden_size,
'predict_dur': self.model.predict_dur,
}
# multi-speaker
if len(self.export_spk) > 0:
dsconfig['speakers'] = [f'{self.model_name}.{spk[0]}' for spk in self.export_spk]
# functionalities
if self.model.predict_dur:
dsconfig['dur'] = f'{model_name}.dur.onnx'
if self.model.predict_pitch:
dsconfig['pitch'] = f'{model_name}.pitch.onnx'
dsconfig['use_expr'] = self.expose_expr
dsconfig['use_note_rest'] = self.model.use_melody_encoder
if self.model.predict_variances:
dsconfig['variance'] = f'{model_name}.variance.onnx'
for variance in VARIANCE_CHECKLIST:
dsconfig[f'predict_{variance}'] = (variance in self.model.variance_prediction_list)
# sampling acceleration
dsconfig['use_continuous_acceleration'] = True
# frame specifications
dsconfig['sample_rate'] = hparams['audio_sample_rate']
dsconfig['hop_size'] = hparams['hop_size']
config_path = path / 'dsconfig.yaml'
with open(config_path, 'w', encoding='utf8') as fw:
yaml.safe_dump(dsconfig, fw, sort_keys=False)
print(f'| export configs => {config_path} **PLEASE EDIT BEFORE USE**')
@torch.no_grad()
def _torch_export_model(self):
# Prepare inputs for FastSpeech2 and dur predictor tracing
tokens = torch.LongTensor([[1] * 5]).to(self.device)
ph_dur = torch.LongTensor([[3, 5, 2, 1, 4]]).to(self.device)
word_div = torch.LongTensor([[2, 2, 1]]).to(self.device)
word_dur = torch.LongTensor([[8, 3, 4]]).to(self.device)
languages = torch.LongTensor([[0] * 5]).to(self.device)
encoder_out = torch.rand(1, 5, hparams['hidden_size'], dtype=torch.float32, device=self.device)
x_masks = tokens == 0
ph_midi = torch.LongTensor([[60] * 5]).to(self.device)
encoder_output_names = ['encoder_out', 'x_masks']
encoder_common_axes = {
'encoder_out': {
1: 'n_tokens'
},
'x_masks': {
1: 'n_tokens'
}
}
input_lang_id = self.use_lang_id
input_spk_embed = hparams['use_spk_id'] and not self.freeze_spk
print(f'Exporting {self.fs2_class_name}...')
if self.model.predict_dur:
torch.onnx.export(
self.model.view_as_linguistic_encoder(),
(
tokens,
word_div,
word_dur,
*([languages] if input_lang_id else [])
),
self.linguistic_encoder_cache_path,
input_names=[
'tokens',
'word_div',
'word_dur',
*(['languages'] if input_lang_id else [])
],
output_names=encoder_output_names,
dynamic_axes={
'tokens': {
1: 'n_tokens'
},
'word_div': {
1: 'n_words'
},
'word_dur': {
1: 'n_words'
},
**encoder_common_axes,
**({'languages': {1: 'n_tokens'}} if input_lang_id else {})
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
print(f'Exporting {self.dur_predictor_class_name}...')
torch.onnx.export(
self.model.view_as_dur_predictor(),
(
encoder_out,
x_masks,
ph_midi,
*([torch.rand(
1, 5, hparams['hidden_size'],
dtype=torch.float32, device=self.device
)] if input_spk_embed else [])
),
self.dur_predictor_cache_path,
input_names=[
'encoder_out',
'x_masks',
'ph_midi',
*(['spk_embed'] if input_spk_embed else [])
],
output_names=[
'ph_dur_pred'
],
dynamic_axes={
'ph_midi': {
1: 'n_tokens'
},
'ph_dur_pred': {
1: 'n_tokens'
},
**({'spk_embed': {1: 'n_tokens'}} if input_spk_embed else {}),
**encoder_common_axes
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
else:
torch.onnx.export(
self.model.view_as_linguistic_encoder(),
(
tokens,
ph_dur,
*([languages] if input_lang_id else [])
),
self.linguistic_encoder_cache_path,
input_names=[
'tokens',
'ph_dur',
*(['languages'] if input_lang_id else [])
],
output_names=encoder_output_names,
dynamic_axes={
'tokens': {
1: 'n_tokens'
},
'ph_dur': {
1: 'n_tokens'
},
**encoder_common_axes,
**({'languages': {1: 'n_tokens'}} if input_lang_id else {})
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
# Common dummy inputs
dummy_time = (torch.rand((1,), device=self.device) * hparams.get('time_scale_factor', 1.0)).float()
dummy_steps = 5
if self.model.predict_pitch:
use_melody_encoder = hparams.get('use_melody_encoder', False)
use_glide_embed = use_melody_encoder and hparams['use_glide_embed'] and not self.freeze_glide
# Prepare inputs for preprocessor of the pitch predictor
note_midi = torch.FloatTensor([[60.] * 4]).to(self.device)
note_dur = torch.LongTensor([[2, 6, 3, 4]]).to(self.device)
pitch = torch.FloatTensor([[60.] * 15]).to(self.device)
retake = torch.ones_like(pitch, dtype=torch.bool)
pitch_input_args = (
encoder_out,
ph_dur,
{
'note_midi': note_midi,
**({'note_rest': note_midi >= 0} if use_melody_encoder else {}),
'note_dur': note_dur,
**({'note_glide': torch.zeros_like(note_midi, dtype=torch.long)} if use_glide_embed else {}),
'pitch': pitch,
**({'expr': torch.ones_like(pitch)} if self.expose_expr else {}),
'retake': retake,
**({'spk_embed': torch.rand(
1, 15, hparams['hidden_size'], dtype=torch.float32, device=self.device
)} if input_spk_embed else {})
}
)
torch.onnx.export(
self.model.view_as_pitch_preprocess(),
pitch_input_args,
self.pitch_preprocess_cache_path,
input_names=[
'encoder_out', 'ph_dur', 'note_midi',
*(['note_rest'] if use_melody_encoder else []),
'note_dur',
*(['note_glide'] if use_glide_embed else []),
'pitch',
*(['expr'] if self.expose_expr else []),
'retake',
*(['spk_embed'] if input_spk_embed else [])
],
output_names=[
'pitch_cond', 'base_pitch'
],
dynamic_axes={
'encoder_out': {
1: 'n_tokens'
},
'ph_dur': {
1: 'n_tokens'
},
'note_midi': {
1: 'n_notes'
},
**({'note_rest': {1: 'n_notes'}} if use_melody_encoder else {}),
'note_dur': {
1: 'n_notes'
},
**({'note_glide': {1: 'n_notes'}} if use_glide_embed else {}),
'pitch': {
1: 'n_frames'
},
**({'expr': {1: 'n_frames'}} if self.expose_expr else {}),
'retake': {
1: 'n_frames'
},
'pitch_cond': {
1: 'n_frames'
},
'base_pitch': {
1: 'n_frames'
},
**({'spk_embed': {1: 'n_frames'}} if input_spk_embed else {})
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
# Prepare inputs for backbone tracing and pitch predictor scripting
shape = (1, 1, hparams['pitch_prediction_args']['repeat_bins'], 15)
noise = torch.randn(shape, device=self.device)
condition = torch.rand((1, hparams['hidden_size'], 15), device=self.device)
print(f'Tracing {self.pitch_backbone_class_name} backbone...')
pitch_predictor = self.model.view_as_pitch_predictor()
pitch_predictor.pitch_predictor.set_backbone(
torch.jit.trace(
pitch_predictor.pitch_predictor.backbone,
(
noise,
dummy_time,
condition
)
)
)
print(f'Scripting {self.pitch_predictor_class_name}...')
pitch_predictor = torch.jit.script(
pitch_predictor,
example_inputs=[
(
condition.transpose(1, 2),
1 # p_sample branch
),
(
condition.transpose(1, 2),
dummy_steps # p_sample_plms branch
)
]
)
print(f'Exporting {self.pitch_predictor_class_name}...')
torch.onnx.export(
pitch_predictor,
(
condition.transpose(1, 2),
dummy_steps
),
self.pitch_predictor_cache_path,
input_names=[
'pitch_cond',
'steps'
],
output_names=[
'x_pred'
],
dynamic_axes={
'pitch_cond': {
1: 'n_frames'
},
'x_pred': {
1: 'n_frames'
}
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
# Prepare inputs for postprocessor of the multi-variance predictor
torch.onnx.export(
self.model.view_as_pitch_postprocess(),
(
pitch,
pitch
),
self.pitch_postprocess_cache_path,
input_names=[
'x_pred',
'base_pitch'
],
output_names=[
'pitch_pred'
],
dynamic_axes={
'x_pred': {
1: 'n_frames'
},
'base_pitch': {
1: 'n_frames'
},
'pitch_pred': {
1: 'n_frames'
}
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
if self.model.predict_variances:
total_repeat_bins = hparams['variances_prediction_args']['total_repeat_bins']
repeat_bins = total_repeat_bins // len(self.model.variance_prediction_list)
# Prepare inputs for preprocessor of the multi-variance predictor
pitch = torch.FloatTensor([[60.] * 15]).to(self.device)
variances = {
v_name: torch.FloatTensor([[0.] * 15]).to(self.device)
for v_name in self.model.variance_prediction_list
}
retake = torch.ones_like(pitch, dtype=torch.bool)[..., None].tile(len(self.model.variance_prediction_list))
torch.onnx.export(
self.model.view_as_variance_preprocess(),
(
encoder_out,
ph_dur,
pitch,
variances,
retake,
*([torch.rand(
1, 15, hparams['hidden_size'],
dtype=torch.float32, device=self.device
)] if input_spk_embed else [])
),
self.variance_preprocess_cache_path,
input_names=[
'encoder_out', 'ph_dur', 'pitch',
*self.model.variance_prediction_list,
'retake',
*(['spk_embed'] if input_spk_embed else [])
],
output_names=[
'variance_cond'
],
dynamic_axes={
'encoder_out': {
1: 'n_tokens'
},
'ph_dur': {
1: 'n_tokens'
},
'pitch': {
1: 'n_frames'
},
**{
v_name: {
1: 'n_frames'
}
for v_name in self.model.variance_prediction_list
},
'retake': {
1: 'n_frames'
},
**({'spk_embed': {1: 'n_frames'}} if input_spk_embed else {})
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
# Prepare inputs for backbone tracing and multi-variance predictor scripting
shape = (1, len(self.model.variance_prediction_list), repeat_bins, 15)
noise = torch.randn(shape, device=self.device)
condition = torch.rand((1, hparams['hidden_size'], 15), device=self.device)
step = (torch.rand((1,), device=self.device) * hparams.get('time_scale_factor', hparams['K_step']))
print(f'Tracing {self.variance_backbone_class_name} backbone...')
multi_var_predictor = self.model.view_as_variance_predictor()
multi_var_predictor.variance_predictor.set_backbone(
torch.jit.trace(
multi_var_predictor.variance_predictor.backbone,
(
noise,
step,
condition
)
)
)
print(f'Scripting {self.multi_var_predictor_class_name}...')
multi_var_predictor = torch.jit.script(
multi_var_predictor,
example_inputs=[
(
condition.transpose(1, 2),
1 # p_sample branch
),
(
condition.transpose(1, 2),
dummy_steps # p_sample_plms branch
)
]
)
print(f'Exporting {self.multi_var_predictor_class_name}...')
torch.onnx.export(
multi_var_predictor,
(
condition.transpose(1, 2),
dummy_steps
),
self.multi_var_predictor_cache_path,
input_names=[
'variance_cond',
'steps'
],
output_names=[
'xs_pred'
],
dynamic_axes={
'variance_cond': {
1: 'n_frames'
},
'xs_pred': {
(1 if len(self.model.variance_prediction_list) == 1 else 2): 'n_frames'
}
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
# Prepare inputs for postprocessor of the multi-variance predictor
xs_shape = (1, 15) \
if len(self.model.variance_prediction_list) == 1 \
else (1, len(self.model.variance_prediction_list), 15)
xs_pred = torch.randn(xs_shape, dtype=torch.float32, device=self.device)
torch.onnx.export(
self.model.view_as_variance_postprocess(),
(
xs_pred
),
self.variance_postprocess_cache_path,
input_names=[
'xs_pred'
],
output_names=[
f'{v_name}_pred'
for v_name in self.model.variance_prediction_list
],
dynamic_axes={
'xs_pred': {
(1 if len(self.model.variance_prediction_list) == 1 else 2): 'n_frames'
},
**{
f'{v_name}_pred': {
1: 'n_frames'
}
for v_name in self.model.variance_prediction_list
}
},
opset_version=17,
**onnx_helper.TORCHSCRIPT_EXPORT_KWARGS
)
@torch.no_grad()
def _perform_spk_mix(self, spk_mix: Dict[str, float]):
spk_mix_ids = []
spk_mix_values = []
for name, value in spk_mix.items():
spk_mix_ids.append(self.spk_map[name])
assert value >= 0., f'Speaker mix checks failed.\n' \
f'Proportion of speaker \'{name}\' is negative.'
spk_mix_values.append(value)
spk_mix_id_N = torch.LongTensor(spk_mix_ids).to(self.device)[None] # => [1, N]
spk_mix_value_N = torch.FloatTensor(spk_mix_values).to(self.device)[None] # => [1, N]
spk_mix_value_sum = spk_mix_value_N.sum()
assert spk_mix_value_sum > 0., 'Speaker mix checks failed.\n' \
'Proportions of speaker mix sum to zero.'
spk_mix_value_N /= spk_mix_value_sum # normalize
spk_mix_embed = torch.sum(
self.model.spk_embed(spk_mix_id_N) * spk_mix_value_N.unsqueeze(2), # => [1, N, H]
dim=1, keepdim=True
) # => [1, 1, H]
return spk_mix_embed
def _optimize_linguistic_graph(self, linguistic: onnx.ModelProto) -> onnx.ModelProto:
onnx_helper.model_override_io_shapes(
linguistic,
output_shapes={
'encoder_out': (1, 'n_tokens', hparams['hidden_size'])
}
)
print(f'Running ONNX Simplifier on {self.fs2_class_name}...')
linguistic, check = onnxsim.simplify(linguistic, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.model_reorder_io_list(
linguistic, 'input',
target_name='languages', insert_after_name='tokens'
)
print(f'| optimize graph: {self.fs2_class_name}')
return linguistic
def _optimize_dur_predictor_graph(self, dur_predictor: onnx.ModelProto) -> onnx.ModelProto:
onnx_helper.model_override_io_shapes(
dur_predictor,
output_shapes={
'ph_dur_pred': (1, 'n_tokens')
}
)
print(f'Running ONNX Simplifier on {self.dur_predictor_class_name}...')
dur_predictor, check = onnxsim.simplify(dur_predictor, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
print(f'| optimize graph: {self.dur_predictor_class_name}')
return dur_predictor
def _optimize_merge_pitch_predictor_graph(
self, pitch_pre: onnx.ModelProto, pitch_predictor: onnx.ModelProto, pitch_post: onnx.ModelProto
) -> onnx.ModelProto:
onnx_helper.model_override_io_shapes(
pitch_pre, output_shapes={'pitch_cond': (1, 'n_frames', hparams['hidden_size'])}
)
pitch_pre, check = onnxsim.simplify(pitch_pre, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.model_override_io_shapes(
pitch_predictor, output_shapes={'pitch_pred': (1, 'n_frames')}
)
print(f'Running ONNX Simplifier #1 on {self.pitch_predictor_class_name}...')
pitch_predictor, check = onnxsim.simplify(pitch_predictor, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.graph_fold_back_to_squeeze(pitch_predictor.graph)
onnx_helper.graph_extract_conditioner_projections(
graph=pitch_predictor.graph, op_type='Conv',
weight_pattern=r'pitch_predictor\..*\.conditioner_projection\.weight',
alias_prefix='/pitch_predictor/backbone/cache'
)
onnx_helper.graph_remove_unused_values(pitch_predictor.graph)
print(f'Running ONNX Simplifier #2 on {self.pitch_predictor_class_name}...')
pitch_predictor, check = onnxsim.simplify(pitch_predictor, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.model_add_prefixes(pitch_pre, node_prefix='/pre', ignored_pattern=r'.*embed.*')
onnx_helper.model_add_prefixes(pitch_pre, dim_prefix='pre.', ignored_pattern='(n_tokens)|(n_notes)|(n_frames)')
onnx_helper.model_add_prefixes(pitch_post, node_prefix='/post', ignored_pattern=None)
onnx_helper.model_add_prefixes(pitch_post, dim_prefix='post.', ignored_pattern='n_frames')
pitch_pre_diffusion = onnx.compose.merge_models(
pitch_pre, pitch_predictor, io_map=[('pitch_cond', 'pitch_cond')],
prefix1='', prefix2='', doc_string='',
producer_name=pitch_pre.producer_name, producer_version=pitch_pre.producer_version,
domain=pitch_pre.domain, model_version=pitch_pre.model_version
)
pitch_pre_diffusion.graph.name = pitch_pre.graph.name
pitch_predictor = onnx.compose.merge_models(
pitch_pre_diffusion, pitch_post, io_map=[
('x_pred', 'x_pred'), ('base_pitch', 'base_pitch')
], prefix1='', prefix2='', doc_string='',
producer_name=pitch_pre.producer_name, producer_version=pitch_pre.producer_version,
domain=pitch_pre.domain, model_version=pitch_pre.model_version
)
pitch_predictor.graph.name = pitch_pre.graph.name
print(f'| optimize graph: {self.pitch_predictor_class_name}')
return pitch_predictor
def _optimize_merge_variance_predictor_graph(
self, var_pre: onnx.ModelProto, var_diffusion: onnx.ModelProto, var_post: onnx.ModelProto
):
onnx_helper.model_override_io_shapes(
var_pre, output_shapes={'variance_cond': (1, 'n_frames', hparams['hidden_size'])}
)
var_pre, check = onnxsim.simplify(var_pre, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.model_override_io_shapes(
var_diffusion, output_shapes={
'xs_pred': (1, 'n_frames')
if len(self.model.variance_prediction_list) == 1
else (1, len(self.model.variance_prediction_list), 'n_frames')
}
)
print(f'Running ONNX Simplifier #1 on {self.multi_var_predictor_class_name}...')
var_diffusion, check = onnxsim.simplify(var_diffusion, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
onnx_helper.graph_fold_back_to_squeeze(var_diffusion.graph)
onnx_helper.graph_extract_conditioner_projections(
graph=var_diffusion.graph, op_type='Conv',
weight_pattern=r'variance_predictor\..*\.conditioner_projection\.weight',
alias_prefix='/variance_predictor/backbone/cache'
)
onnx_helper.graph_remove_unused_values(var_diffusion.graph)
print(f'Running ONNX Simplifier #2 on {self.multi_var_predictor_class_name}...')
var_diffusion, check = onnxsim.simplify(var_diffusion, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
var_post, check = onnxsim.simplify(var_post, include_subgraph=True)
assert check, 'Simplified ONNX model could not be validated'
ignored_variance_names = '|'.join([f'({v_name})' for v_name in self.model.variance_prediction_list])
onnx_helper.model_add_prefixes(
var_pre, node_prefix='/pre', value_info_prefix='/pre', initializer_prefix='/pre',
ignored_pattern=fr'.*((embed)|{ignored_variance_names}).*'
)
onnx_helper.model_add_prefixes(var_pre, dim_prefix='pre.', ignored_pattern='(n_tokens)|(n_frames)')
onnx_helper.model_add_prefixes(
var_post, node_prefix='/post', value_info_prefix='/post', initializer_prefix='/post',
ignored_pattern=None
)
onnx_helper.model_add_prefixes(var_post, dim_prefix='post.', ignored_pattern='n_frames')
print(f'Merging {self.multi_var_predictor_class_name} subroutines...')
var_pre_diffusion = onnx.compose.merge_models(
var_pre, var_diffusion, io_map=[('variance_cond', 'variance_cond')],
prefix1='', prefix2='', doc_string='',
producer_name=var_pre.producer_name, producer_version=var_pre.producer_version,
domain=var_pre.domain, model_version=var_pre.model_version
)
var_pre_diffusion.graph.name = var_pre.graph.name
var_predictor = onnx.compose.merge_models(
var_pre_diffusion, var_post, io_map=[('xs_pred', 'xs_pred')],
prefix1='', prefix2='', doc_string='',
producer_name=var_pre.producer_name, producer_version=var_pre.producer_version,
domain=var_pre.domain, model_version=var_pre.model_version
)
var_predictor.graph.name = var_pre.graph.name
return var_predictor
# noinspection PyMethodMayBeStatic
def _export_spk_embed(self, path: Path, spk_embed: torch.Tensor):
with open(path, 'wb') as f:
f.write(spk_embed.cpu().numpy().tobytes())
print(f'| export spk embed => {path}')
def _export_phonemes(self, path: Path):
ph_path = path / f'{self.model_name}.phonemes.json'
self.phoneme_dictionary.dump(ph_path)
print(f'| export phonemes => {ph_path}')
lang_path = path / f'{self.model_name}.languages.json'
with open(lang_path, 'w', encoding='utf8') as fw:
json.dump(self.lang_map, fw, ensure_ascii=False, indent=2)
print(f'| export languages => {lang_path}')
View File
+220
View File
@@ -0,0 +1,220 @@
from __future__ import annotations
from typing import List, Tuple
import torch
from torch import Tensor
from modules.core import (
GaussianDiffusion, PitchDiffusion, MultiVarianceDiffusion
)
def extract(a, t):
return a[t].reshape((1, 1, 1, 1))
# noinspection PyMethodOverriding
class GaussianDiffusionONNX(GaussianDiffusion):
@property
def backbone(self):
return self.denoise_fn
# We give up the setter for the property `backbone` because this will cause TorchScript to fail
# @backbone.setter
@torch.jit.unused
def set_backbone(self, value):
self.denoise_fn = value
def q_sample(self, x_start, t, noise):
return (
extract(self.sqrt_alphas_cumprod, t) * x_start +
extract(self.sqrt_one_minus_alphas_cumprod, t) * noise
)
def p_sample(self, x, t, cond):
x_pred = self.denoise_fn(x, t, cond)
x_recon = (
extract(self.sqrt_recip_alphas_cumprod, t) * x -
extract(self.sqrt_recipm1_alphas_cumprod, t) * x_pred
)
# This is previously inherited from original DiffSinger repository
# and disabled due to some loudness issues when speedup = 1.
# x_recon = torch.clamp(x_recon, min=-1., max=1.)
model_mean = (
extract(self.posterior_mean_coef1, t) * x_recon +
extract(self.posterior_mean_coef2, t) * x
)
model_log_variance = extract(self.posterior_log_variance_clipped, t)
noise = torch.randn_like(x)
# no noise when t == 0
nonzero_mask = ((t > 0).float()).reshape(1, 1, 1, 1)
return model_mean + nonzero_mask * (0.5 * model_log_variance).exp() * noise
def p_sample_ddim(self, x, t, interval: int, cond):
a_t = extract(self.alphas_cumprod, t)
t_prev = t - interval
a_prev = extract(self.alphas_cumprod, t_prev * (t_prev > 0))
noise_pred = self.denoise_fn(x, t, cond=cond)
x_prev = a_prev.sqrt() * (
x / a_t.sqrt() + (((1 - a_prev) / a_prev).sqrt() - ((1 - a_t) / a_t).sqrt()) * noise_pred
)
return x_prev
def plms_get_x_pred(self, x, noise_t, t, t_prev):
a_t = extract(self.alphas_cumprod, t)
a_prev = extract(self.alphas_cumprod, t_prev)
a_t_sq, a_prev_sq = a_t.sqrt(), a_prev.sqrt()
x_delta = (a_prev - a_t) * ((1 / (a_t_sq * (a_t_sq + a_prev_sq))) * x - 1 / (
a_t_sq * (((1 - a_prev) * a_t).sqrt() + ((1 - a_t) * a_prev).sqrt())) * noise_t)
x_pred = x + x_delta
return x_pred
def p_sample_plms(self, x_prev, t, interval: int, cond, noise_list: List[Tensor], stage: int):
noise_pred = self.denoise_fn(x_prev, t, cond)
t_prev = t - interval
t_prev = t_prev * (t_prev > 0)
if stage == 0:
x_pred = self.plms_get_x_pred(x_prev, noise_pred, t, t_prev)
noise_pred_prev = self.denoise_fn(x_pred, t_prev, cond)
noise_pred_prime = (noise_pred + noise_pred_prev) / 2.
elif stage == 1:
noise_pred_prime = (3. * noise_pred - noise_list[-1]) / 2.
elif stage == 2:
noise_pred_prime = (23. * noise_pred - 16. * noise_list[-1] + 5. * noise_list[-2]) / 12.
else:
noise_pred_prime = (55. * noise_pred - 59. * noise_list[-1] + 37.
* noise_list[-2] - 9. * noise_list[-3]) / 24.
x_prev = self.plms_get_x_pred(x_prev, noise_pred_prime, t, t_prev)
return noise_pred, x_prev
def norm_spec(self, x):
k = (self.spec_max - self.spec_min) / 2.
b = (self.spec_max + self.spec_min) / 2.
return (x - b) / k
def denorm_spec(self, x):
k = (self.spec_max - self.spec_min) / 2.
b = (self.spec_max + self.spec_min) / 2.
return x * k + b
def forward(self, condition, x_start=None, depth=None, steps: int = 10):
condition = condition.transpose(1, 2) # [1, T, H] => [1, H, T]
device = condition.device
n_frames = condition.shape[2]
noise = torch.randn((1, self.num_feats, self.out_dims, n_frames), device=device)
if x_start is None:
speedup = max(1, self.timesteps // steps)
speedup = self.timestep_factors[torch.sum(self.timestep_factors <= speedup) - 1]
step_range = torch.arange(0, self.k_step, speedup, dtype=torch.long, device=device).flip(0)[:, None]
x = noise
else:
depth_int64 = min(torch.round(depth * self.timesteps).long(), self.k_step)
speedup = max(1, depth_int64 // steps)
depth_int64 = depth_int64 // speedup * speedup # make depth_int64 a multiple of speedup
step_range = torch.arange(0, depth_int64, speedup, dtype=torch.long, device=device).flip(0)[:, None]
x_start = self.norm_spec(x_start).transpose(-2, -1)
if self.num_feats == 1:
x_start = x_start[:, None, :, :]
if depth_int64 >= self.timesteps:
x = noise
elif depth_int64 > 0:
x = self.q_sample(
x_start, torch.full((1,), depth_int64 - 1, device=device, dtype=torch.long), noise
)
else:
x = x_start
if speedup > 1:
for t in step_range:
x = self.p_sample_ddim(x, t, interval=speedup, cond=condition)
# plms_noise_stage: int = 0
# noise_list: List[Tensor] = []
# for t in step_range:
# noise_pred, x = self.p_sample_plms(
# x, t, interval=speedup, cond=condition,
# noise_list=noise_list, stage=plms_noise_stage
# )
# if plms_noise_stage == 0:
# noise_list = [noise_pred]
# plms_noise_stage = plms_noise_stage + 1
# else:
# if plms_noise_stage >= 3:
# noise_list.pop(0)
# else:
# plms_noise_stage = plms_noise_stage + 1
# noise_list.append(noise_pred)
else:
for t in step_range:
x = self.p_sample(x, t, cond=condition)
if self.num_feats == 1:
x = x.squeeze(1).permute(0, 2, 1) # [B, 1, M, T] => [B, T, M]
else:
x = x.permute(0, 1, 3, 2) # [B, F, M, T] => [B, F, T, M]
x = self.denorm_spec(x)
return x
class PitchDiffusionONNX(GaussianDiffusionONNX, PitchDiffusion):
def __init__(self, vmin: float, vmax: float,
cmin: float, cmax: float, repeat_bins,
timesteps=1000, k_step=1000,
backbone_type=None, backbone_args=None,
betas=None):
self.vmin = vmin
self.vmax = vmax
self.cmin = cmin
self.cmax = cmax
super(PitchDiffusion, self).__init__(
vmin=vmin, vmax=vmax, repeat_bins=repeat_bins,
timesteps=timesteps, k_step=k_step,
backbone_type=backbone_type, backbone_args=backbone_args,
betas=betas
)
def clamp_spec(self, x):
return x.clamp(min=self.cmin, max=self.cmax)
def denorm_spec(self, x):
d = (self.spec_max - self.spec_min) / 2.
m = (self.spec_max + self.spec_min) / 2.
x = x * d + m
x = x.mean(dim=-1)
return x
class MultiVarianceDiffusionONNX(GaussianDiffusionONNX, MultiVarianceDiffusion):
def __init__(
self, ranges: List[Tuple[float, float]],
clamps: List[Tuple[float | None, float | None] | None],
repeat_bins, timesteps=1000, k_step=1000,
backbone_type=None, backbone_args=None,
betas=None
):
assert len(ranges) == len(clamps)
self.clamps = clamps
vmin = [r[0] for r in ranges]
vmax = [r[1] for r in ranges]
if len(vmin) == 1:
vmin = vmin[0]
if len(vmax) == 1:
vmax = vmax[0]
super(MultiVarianceDiffusion, self).__init__(
vmin=vmin, vmax=vmax, repeat_bins=repeat_bins,
timesteps=timesteps, k_step=k_step,
backbone_type=backbone_type, backbone_args=backbone_args,
betas=betas
)
def denorm_spec(self, x):
d = (self.spec_max - self.spec_min) / 2.
m = (self.spec_max + self.spec_min) / 2.
x = x * d + m
x = x.mean(dim=-1)
return x
+235
View File
@@ -0,0 +1,235 @@
import copy
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from modules.commons.common_layers import NormalInitEmbedding as Embedding
from modules.fastspeech.acoustic_encoder import FastSpeech2Acoustic
from modules.fastspeech.variance_encoder import FastSpeech2Variance
from utils.hparams import hparams
from utils.phoneme_utils import PAD_INDEX
f0_bin = 256
f0_max = 1100.0
f0_min = 50.0
f0_mel_min = 1127 * np.log(1 + f0_min / 700)
f0_mel_max = 1127 * np.log(1 + f0_max / 700)
def uniform_attention_pooling(spk_embed, durations):
_, T_mel, _ = spk_embed.shape
ph_starts = torch.cumsum(torch.cat([torch.zeros_like(durations[:, :1]), durations[:, :-1]], dim=1), dim=1)
ph_ends = ph_starts + durations
mel_indices = torch.arange(T_mel, device=spk_embed.device).view(1, 1, T_mel)
phoneme_to_mel_mask = (mel_indices >= ph_starts.unsqueeze(-1)) & (mel_indices < ph_ends.unsqueeze(-1))
uniform_scores = phoneme_to_mel_mask.float()
sum_scores = uniform_scores.sum(dim=2, keepdim=True)
attn_weights = uniform_scores / (sum_scores + (sum_scores == 0).float()) # [B, T_ph, T_mel]
ph_spk_embed = torch.bmm(attn_weights, spk_embed)
return ph_spk_embed
def f0_to_coarse(f0):
f0_mel = 1127 * (1 + f0 / 700).log()
a = (f0_bin - 2) / (f0_mel_max - f0_mel_min)
b = f0_mel_min * a - 1.
f0_mel = torch.where(f0_mel > 0, f0_mel * a - b, f0_mel)
torch.clip_(f0_mel, min=1., max=float(f0_bin - 1))
f0_coarse = torch.round(f0_mel).long()
return f0_coarse
class LengthRegulator(nn.Module):
# noinspection PyMethodMayBeStatic
def forward(self, dur):
token_idx = torch.arange(1, dur.shape[1] + 1, device=dur.device)[None, :, None]
dur_cumsum = torch.cumsum(dur, dim=1)
dur_cumsum_prev = F.pad(dur_cumsum, (1, -1), mode='constant', value=0)
pos_idx = torch.arange(dur.sum(dim=1).max(), device=dur.device)[None, None]
token_mask = (pos_idx >= dur_cumsum_prev[:, :, None]) & (pos_idx < dur_cumsum[:, :, None])
mel2ph = (token_idx * token_mask).sum(dim=1)
return mel2ph
class FastSpeech2AcousticONNX(FastSpeech2Acoustic):
def __init__(self, vocab_size, cross_lingual_token_idx=None):
super().__init__(vocab_size=vocab_size)
self.register_buffer(
'cross_lingual_token_idx',
torch.LongTensor(cross_lingual_token_idx),
persistent=False
) # [N,]
if len(cross_lingual_token_idx) == 0:
self.use_lang_id = False
# for temporary compatibility; will be completely removed in the future
self.f0_embed_type = hparams.get('f0_embed_type', 'continuous')
if self.f0_embed_type == 'discrete':
self.pitch_embed = Embedding(300, hparams['hidden_size'], PAD_INDEX)
self.lr = LengthRegulator()
if hparams['use_key_shift_embed']:
self.shift_min, self.shift_max = hparams['augmentation_args']['random_pitch_shifting']['range']
if hparams['use_speed_embed']:
self.speed_min, self.speed_max = hparams['augmentation_args']['random_time_stretching']['range']
# noinspection PyMethodOverriding
def forward(
self, tokens, durations,
f0, variances: dict,
gender=None, velocity=None,
spk_embed=None,
languages=None
):
txt_embed = self.txt_embed(tokens)
durations = durations * (tokens > 0)
mel2ph = self.lr(durations)
_mel2ph = mel2ph
f0 = f0 * (mel2ph > 0)
mel2ph = mel2ph[..., None].repeat((1, 1, hparams['hidden_size']))
if self.use_variance_scaling:
dur_embed = self.dur_embed(torch.log(1 + durations.float())[:, :, None])
else:
dur_embed = self.dur_embed(durations.float()[:, :, None])
if self.use_lang_id:
lang_mask = torch.any(
tokens[..., None] == self.cross_lingual_token_idx[None, None],
dim=-1
)
lang_embed = self.lang_embed(languages * lang_mask)
extra_embed = dur_embed + lang_embed
else:
extra_embed = dur_embed
if hparams.get('use_mix_ln', False):
if hasattr(self, 'frozen_spk_embed'):
ph_spk_embed = self.frozen_spk_embed.repeat(1, tokens.shape[1], 1)
else:
ph_spk_embed = uniform_attention_pooling(spk_embed, durations)
else:
ph_spk_embed = None
encoded = self.encoder(txt_embed, extra_embed, tokens == PAD_INDEX, spk_embed=ph_spk_embed)
encoded = F.pad(encoded, (0, 0, 1, 0))
condition = torch.gather(encoded, 1, mel2ph)
if self.use_stretch_embed:
stretch = torch.round(1000 * self.sr(_mel2ph, durations))
table = self.stretch_embed(torch.arange(0, 1001, device=stretch.device))
stretch_embed = torch.index_select(table, 0, stretch.view(-1).long()).view_as(condition)
condition += stretch_embed
stretch_embed_rnn_out, _ = self.stretch_embed_rnn(condition)
condition += stretch_embed_rnn_out
if self.f0_embed_type == 'discrete':
pitch = f0_to_coarse(f0)
pitch_embed = self.pitch_embed(pitch)
else:
f0_mel = (1 + f0 / 700).log()
pitch_embed = self.pitch_embed(f0_mel[:, :, None])
condition += pitch_embed
if self.use_variance_embeds:
variance_embeds = torch.stack([
self.variance_embeds[v_name](variances[v_name][:, :, None] * self.variance_scaling_factor[v_name])
for v_name in self.variance_embed_list
], dim=-1).sum(-1)
condition += variance_embeds
if hparams['use_key_shift_embed']:
if hasattr(self, 'frozen_key_shift'):
key_shift_embed = self.key_shift_embed(self.frozen_key_shift[:, None, None] * self.variance_scaling_factor['key_shift'])
else:
gender = torch.clip(gender, min=-1., max=1.)
gender_mask = (gender < 0.).float()
key_shift = gender * ((1. - gender_mask) * self.shift_max + gender_mask * abs(self.shift_min))
key_shift_embed = self.key_shift_embed(key_shift[:, :, None] * self.variance_scaling_factor['key_shift'])
condition += key_shift_embed
if hparams['use_speed_embed']:
if velocity is not None:
velocity = torch.clip(velocity, min=self.speed_min, max=self.speed_max)
speed_embed = self.speed_embed(velocity[:, :, None] * self.variance_scaling_factor['speed'])
else:
speed_embed = self.speed_embed(torch.FloatTensor([1.]).to(condition.device)[:, None, None] * self.variance_scaling_factor['speed'])
condition += speed_embed
if hparams['use_spk_id']:
if hasattr(self, 'frozen_spk_embed'):
condition += self.frozen_spk_embed
else:
condition += spk_embed
return condition
class FastSpeech2VarianceONNX(FastSpeech2Variance):
def __init__(self, vocab_size, cross_lingual_token_idx=None):
super().__init__(vocab_size=vocab_size)
self.register_buffer(
'cross_lingual_token_idx',
torch.LongTensor(cross_lingual_token_idx),
persistent=False
)
if len(cross_lingual_token_idx) == 0:
self.use_lang_id = False
self.lr = LengthRegulator()
def forward_encoder_word(self, tokens, word_div, word_dur, languages=None):
txt_embed = self.txt_embed(tokens)
ph2word = self.lr(word_div)
onset = ph2word > F.pad(ph2word, [1, -1])
onset_embed = self.onset_embed(onset.long())
ph_word_dur = torch.gather(F.pad(word_dur, [1, 0]), 1, ph2word)
word_dur_embed = self.word_dur_embed(ph_word_dur.float()[:, :, None])
extra_embed = onset_embed + word_dur_embed
if self.use_lang_id:
lang_mask = torch.any(
tokens[..., None] == self.cross_lingual_token_idx[None, None],
dim=-1
)
lang_embed = self.lang_embed(languages * lang_mask)
extra_embed += lang_embed
x_masks = tokens == PAD_INDEX
return self.encoder(txt_embed, extra_embed, x_masks), x_masks
def forward_encoder_phoneme(self, tokens, ph_dur, languages=None):
txt_embed = self.txt_embed(tokens)
if self.use_variance_scaling:
ph_dur_embed = self.ph_dur_embed(torch.log(1 + ph_dur.float())[:, :, None])
else:
ph_dur_embed = self.ph_dur_embed(ph_dur.float()[:, :, None])
if self.use_lang_id:
lang_mask = torch.any(
tokens[..., None] == self.cross_lingual_token_idx[None, None],
dim=-1
)
lang_embed = self.lang_embed(languages * lang_mask)
extra_embed = ph_dur_embed + lang_embed
else:
extra_embed = ph_dur_embed
x_masks = tokens == PAD_INDEX
return self.encoder(txt_embed, extra_embed, x_masks), x_masks
def forward_dur_predictor(self, encoder_out, x_masks, ph_midi, spk_embed=None):
midi_embed = self.midi_embed(ph_midi)
dur_cond = encoder_out + midi_embed
if hparams['use_spk_id'] and spk_embed is not None:
dur_cond += spk_embed
ph_dur = self.dur_predictor(dur_cond, x_masks=x_masks)
return ph_dur
def view_as_encoder(self):
model = copy.deepcopy(self)
if self.predict_dur:
del model.dur_predictor
model.forward = model.forward_encoder_word
else:
model.forward = model.forward_encoder_phoneme
return model
def view_as_dur_predictor(self):
model = copy.deepcopy(self)
del model.encoder
model.forward = model.forward_dur_predictor
return model
+16
View File
@@ -0,0 +1,16 @@
import torch
from modules.nsf_hifigan.env import AttrDict
from modules.nsf_hifigan.models import Generator
# noinspection SpellCheckingInspection
class NSFHiFiGANONNX(torch.nn.Module):
def __init__(self, attrs: dict):
super().__init__()
self.generator = Generator(AttrDict(attrs))
def forward(self, mel: torch.Tensor, f0: torch.Tensor):
mel = mel.transpose(1, 2)
wav = self.generator(mel, f0)
return wav.squeeze(1)
+123
View File
@@ -0,0 +1,123 @@
from __future__ import annotations
from typing import List, Tuple
import torch
from modules.core import (
RectifiedFlow, PitchRectifiedFlow, MultiVarianceRectifiedFlow
)
class RectifiedFlowONNX(RectifiedFlow):
@property
def backbone(self):
return self.velocity_fn
# We give up the setter for the property `backbone` because this will cause TorchScript to fail
# @backbone.setter
@torch.jit.unused
def set_backbone(self, value):
self.velocity_fn = value
def sample_euler(self, x, t, dt: float, cond):
x += self.velocity_fn(x, t * self.time_scale_factor, cond) * dt
return x
def norm_spec(self, x):
k = (self.spec_max - self.spec_min) / 2.
b = (self.spec_max + self.spec_min) / 2.
return (x - b) / k
def denorm_spec(self, x):
k = (self.spec_max - self.spec_min) / 2.
b = (self.spec_max + self.spec_min) / 2.
return x * k + b
def forward(self, condition, x_end=None, depth=None, steps: int = 10):
condition = condition.transpose(1, 2) # [1, T, H] => [1, H, T]
device = condition.device
n_frames = condition.shape[2]
noise = torch.randn((1, self.num_feats, self.out_dims, n_frames), device=device)
if x_end is None:
t_start = 0.
x = noise
else:
t_start = torch.max(1 - depth, torch.tensor(self.t_start, dtype=torch.float32, device=device))
x_end = self.norm_spec(x_end).transpose(-2, -1)
if self.num_feats == 1:
x_end = x_end[:, None, :, :]
if t_start <= 0.:
x = noise
elif t_start >= 1.:
x = x_end
else:
x = t_start * x_end + (1 - t_start) * noise
t_width = 1. - t_start
if t_width >= 0.:
dt = t_width / max(1, steps)
for t in torch.arange(steps, dtype=torch.long, device=device)[:, None].float() * dt + t_start:
x = self.sample_euler(x, t, dt, condition)
if self.num_feats == 1:
x = x.squeeze(1).permute(0, 2, 1) # [B, 1, M, T] => [B, T, M]
else:
x = x.permute(0, 1, 3, 2) # [B, F, M, T] => [B, F, T, M]
x = self.denorm_spec(x)
return x
class PitchRectifiedFlowONNX(RectifiedFlowONNX, PitchRectifiedFlow):
def __init__(self, vmin: float, vmax: float,
cmin: float, cmax: float, repeat_bins,
time_scale_factor=1000,
backbone_type=None, backbone_args=None):
self.vmin = vmin
self.vmax = vmax
self.cmin = cmin
self.cmax = cmax
super(PitchRectifiedFlow, self).__init__(
vmin=vmin, vmax=vmax, repeat_bins=repeat_bins,
time_scale_factor=time_scale_factor,
backbone_type=backbone_type, backbone_args=backbone_args
)
def clamp_spec(self, x):
return x.clamp(min=self.cmin, max=self.cmax)
def denorm_spec(self, x):
d = (self.spec_max - self.spec_min) / 2.
m = (self.spec_max + self.spec_min) / 2.
x = x * d + m
x = x.mean(dim=-1)
return x
class MultiVarianceRectifiedFlowONNX(RectifiedFlowONNX, MultiVarianceRectifiedFlow):
def __init__(
self, ranges: List[Tuple[float, float]],
clamps: List[Tuple[float | None, float | None] | None],
repeat_bins, time_scale_factor=1000,
backbone_type=None, backbone_args=None
):
assert len(ranges) == len(clamps)
self.clamps = clamps
vmin = [r[0] for r in ranges]
vmax = [r[1] for r in ranges]
if len(vmin) == 1:
vmin = vmin[0]
if len(vmax) == 1:
vmax = vmax[0]
super(MultiVarianceRectifiedFlow, self).__init__(
vmin=vmin, vmax=vmax, repeat_bins=repeat_bins,
time_scale_factor=time_scale_factor,
backbone_type=backbone_type, backbone_args=backbone_args
)
def denorm_spec(self, x):
d = (self.spec_max - self.spec_min) / 2.
m = (self.spec_max + self.spec_min) / 2.
x = x * d + m
x = x.mean(dim=-1)
return x
+413
View File
@@ -0,0 +1,413 @@
import copy
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch import Tensor
from deployment.modules.diffusion import (
GaussianDiffusionONNX, PitchDiffusionONNX, MultiVarianceDiffusionONNX
)
from deployment.modules.rectified_flow import (
RectifiedFlowONNX, PitchRectifiedFlowONNX, MultiVarianceRectifiedFlowONNX
)
from deployment.modules.fastspeech2 import FastSpeech2AcousticONNX, FastSpeech2VarianceONNX
from modules.toplevel import DiffSingerAcoustic, DiffSingerVariance
from utils.hparams import hparams
class DiffSingerAcousticONNX(DiffSingerAcoustic):
def __init__(self, vocab_size, out_dims, cross_lingual_token_idx=None):
super().__init__(vocab_size, out_dims)
del self.fs2
del self.diffusion
self.fs2 = FastSpeech2AcousticONNX(
vocab_size=vocab_size,
cross_lingual_token_idx=cross_lingual_token_idx
)
if self.diffusion_type == 'ddpm':
self.diffusion = GaussianDiffusionONNX(
out_dims=out_dims,
num_feats=1,
timesteps=hparams['timesteps'],
k_step=hparams['K_step'],
backbone_type=self.backbone_type,
backbone_args=self.backbone_args,
spec_min=hparams['spec_min'],
spec_max=hparams['spec_max']
)
elif self.diffusion_type == 'reflow':
self.diffusion = RectifiedFlowONNX(
out_dims=out_dims,
num_feats=1,
t_start=hparams['T_start'],
time_scale_factor=hparams['time_scale_factor'],
backbone_type=self.backbone_type,
backbone_args=self.backbone_args,
spec_min=hparams['spec_min'],
spec_max=hparams['spec_max']
)
else:
raise ValueError(f"Invalid diffusion type: {self.diffusion_type}")
self.mel_base = hparams.get('mel_base', '10')
def ensure_mel_base(self, mel):
if self.mel_base != 'e':
# log10 mel to log mel
mel = mel * 2.30259
return mel
def forward_fs2_aux(
self,
tokens: Tensor,
durations: Tensor,
f0: Tensor,
variances: dict,
gender: Tensor = None,
velocity: Tensor = None,
spk_embed: Tensor = None,
languages: Tensor = None
):
condition = self.fs2(
tokens, durations, f0, variances=variances,
gender=gender, velocity=velocity, spk_embed=spk_embed,
languages=languages
)
if self.use_shallow_diffusion:
aux_mel_pred = self.aux_decoder(condition, infer=True)
return condition, aux_mel_pred
else:
return condition
def forward_shallow_diffusion(
self, condition: Tensor, x_start: Tensor,
depth, steps: int
) -> Tensor:
mel_pred = self.diffusion(condition, x_start=x_start, depth=depth, steps=steps)
return self.ensure_mel_base(mel_pred)
def forward_diffusion(self, condition: Tensor, steps: int):
mel_pred = self.diffusion(condition, steps=steps)
return self.ensure_mel_base(mel_pred)
def forward_shallow_reflow(
self, condition: Tensor, x_end: Tensor,
depth, steps: int
):
mel_pred = self.diffusion(condition, x_end=x_end, depth=depth, steps=steps)
return self.ensure_mel_base(mel_pred)
def forward_reflow(self, condition: Tensor, steps: int):
mel_pred = self.diffusion(condition, steps=steps)
return self.ensure_mel_base(mel_pred)
def view_as_fs2_aux(self) -> nn.Module:
model = copy.deepcopy(self)
del model.diffusion
model.forward = model.forward_fs2_aux
return model
def view_as_diffusion(self) -> nn.Module:
model = copy.deepcopy(self)
del model.fs2
if self.use_shallow_diffusion:
del model.aux_decoder
model.forward = model.forward_shallow_diffusion
else:
model.forward = model.forward_diffusion
return model
def view_as_reflow(self) -> nn.Module:
model = copy.deepcopy(self)
del model.fs2
if self.use_shallow_diffusion:
del model.aux_decoder
model.forward = model.forward_shallow_reflow
else:
model.forward = model.forward_reflow
return model
class DiffSingerVarianceONNX(DiffSingerVariance):
def __init__(self, vocab_size, cross_lingual_token_idx=None):
super().__init__(vocab_size=vocab_size)
del self.fs2
self.fs2 = FastSpeech2VarianceONNX(
vocab_size=vocab_size,
cross_lingual_token_idx=cross_lingual_token_idx
)
self.hidden_size = hparams['hidden_size']
if self.predict_pitch:
del self.pitch_predictor
self.smooth: nn.Conv1d = None
pitch_hparams = hparams['pitch_prediction_args']
if self.diffusion_type == 'ddpm':
self.pitch_predictor = PitchDiffusionONNX(
vmin=pitch_hparams['pitd_norm_min'],
vmax=pitch_hparams['pitd_norm_max'],
cmin=pitch_hparams['pitd_clip_min'],
cmax=pitch_hparams['pitd_clip_max'],
repeat_bins=pitch_hparams['repeat_bins'],
timesteps=hparams['timesteps'],
k_step=hparams['K_step'],
backbone_type=self.pitch_backbone_type,
backbone_args=self.pitch_backbone_args
)
elif self.diffusion_type == 'reflow':
self.pitch_predictor = PitchRectifiedFlowONNX(
vmin=pitch_hparams['pitd_norm_min'],
vmax=pitch_hparams['pitd_norm_max'],
cmin=pitch_hparams['pitd_clip_min'],
cmax=pitch_hparams['pitd_clip_max'],
repeat_bins=pitch_hparams['repeat_bins'],
time_scale_factor=hparams['time_scale_factor'],
backbone_type=self.pitch_backbone_type,
backbone_args=self.pitch_backbone_args
)
else:
raise ValueError(f"Invalid diffusion type: {self.diffusion_type}")
if self.predict_variances:
del self.variance_predictor
if self.diffusion_type == 'ddpm':
self.variance_predictor = self.build_adaptor(cls=MultiVarianceDiffusionONNX)
elif self.diffusion_type == 'reflow':
self.variance_predictor = self.build_adaptor(cls=MultiVarianceRectifiedFlowONNX)
else:
raise NotImplementedError(self.diffusion_type)
def build_smooth_op(self, device):
smooth_kernel_size = round(hparams['midi_smooth_width'] * hparams['audio_sample_rate'] / hparams['hop_size'])
smooth = nn.Conv1d(
in_channels=1,
out_channels=1,
kernel_size=smooth_kernel_size,
bias=False,
padding='same',
padding_mode='replicate'
).eval()
smooth_kernel = torch.sin(torch.from_numpy(
np.linspace(0, 1, smooth_kernel_size).astype(np.float32) * np.pi
))
smooth_kernel /= smooth_kernel.sum()
smooth.weight.data = smooth_kernel[None, None]
self.smooth = smooth.to(device)
def embed_frozen_spk(self, encoder_out):
if hparams['use_spk_id'] and hasattr(self, 'frozen_spk_embed'):
encoder_out += self.frozen_spk_embed
return encoder_out
def forward_linguistic_encoder_word(self, tokens, word_div, word_dur, languages=None):
encoder_out, x_masks = self.fs2.forward_encoder_word(tokens, word_div, word_dur, languages=languages)
encoder_out = self.embed_frozen_spk(encoder_out)
return encoder_out, x_masks
def forward_linguistic_encoder_phoneme(self, tokens, ph_dur, languages=None):
encoder_out, x_masks = self.fs2.forward_encoder_phoneme(tokens, ph_dur, languages=languages)
encoder_out = self.embed_frozen_spk(encoder_out)
return encoder_out, x_masks
def forward_dur_predictor(self, encoder_out, x_masks, ph_midi, spk_embed=None):
return self.fs2.forward_dur_predictor(encoder_out, x_masks, ph_midi, spk_embed=spk_embed)
def forward_mel2x_gather(self, x_src, x_dur, x_dim=None, check_stretch_embed=False):
mel2x = self.lr(x_dur)
_mel2x = mel2x
if x_dim is not None:
x_src = F.pad(x_src, [0, 0, 1, 0])
mel2x = mel2x[..., None].repeat([1, 1, x_dim])
else:
x_src = F.pad(x_src, [1, 0])
x_cond = torch.gather(x_src, 1, mel2x)
if self.use_stretch_embed and check_stretch_embed:
stretch = torch.round(1000 * self.sr(_mel2x, x_dur))
table = self.stretch_embed(torch.arange(0, 1001, device=stretch.device))
stretch_embed = torch.index_select(table, 0, stretch.view(-1).long()).view_as(x_cond)
x_cond += stretch_embed
stretch_embed_rnn_out, _ = self.stretch_embed_rnn(x_cond)
x_cond += stretch_embed_rnn_out
return x_cond
def forward_pitch_preprocess(
self, encoder_out, ph_dur,
note_midi=None, note_rest=None, note_dur=None, note_glide=None,
pitch=None, expr=None, retake=None, spk_embed=None
):
condition = self.forward_mel2x_gather(encoder_out, ph_dur, x_dim=self.hidden_size, check_stretch_embed=True)
if self.use_melody_encoder:
if self.melody_encoder.use_glide_embed and note_glide is None:
note_glide = torch.LongTensor([[0]]).to(encoder_out.device)
melody_encoder_out = self.melody_encoder(
note_midi, note_rest, note_dur,
glide=note_glide
)
melody_encoder_out = self.forward_mel2x_gather(melody_encoder_out, note_dur, x_dim=self.hidden_size)
condition += melody_encoder_out
if expr is None:
retake_embed = self.pitch_retake_embed(retake.long())
else:
retake_true_embed = self.pitch_retake_embed(
torch.ones(1, 1, dtype=torch.long, device=encoder_out.device)
) # [B=1, T=1] => [B=1, T=1, H]
retake_false_embed = self.pitch_retake_embed(
torch.zeros(1, 1, dtype=torch.long, device=encoder_out.device)
) # [B=1, T=1] => [B=1, T=1, H]
expr = (expr * retake)[:, :, None] # [B, T, 1]
retake_embed = expr * retake_true_embed + (1. - expr) * retake_false_embed
pitch_cond = condition + retake_embed
frame_midi_pitch = self.forward_mel2x_gather(note_midi, note_dur, x_dim=None)
base_pitch = self.smooth(frame_midi_pitch)
if self.use_melody_encoder:
delta_pitch = (pitch - base_pitch) * ~retake
if self.use_variance_scaling:
pitch_cond += self.delta_pitch_embed(delta_pitch[:, :, None] / 12)
else:
pitch_cond += self.delta_pitch_embed(delta_pitch[:, :, None])
else:
base_pitch = base_pitch * retake + pitch * ~retake
if self.use_variance_scaling:
pitch_cond += self.base_pitch_embed(base_pitch[:, :, None] / 128)
else:
pitch_cond += self.base_pitch_embed(base_pitch[:, :, None])
if hparams['use_spk_id'] and spk_embed is not None:
pitch_cond += spk_embed
return pitch_cond, base_pitch
def forward_pitch_reflow(
self, pitch_cond, steps: int = 10
):
x_pred = self.pitch_predictor(pitch_cond, steps=steps)
return x_pred
def forward_pitch_postprocess(self, x_pred, base_pitch):
pitch_pred = self.pitch_predictor.clamp_spec(x_pred) + base_pitch
return pitch_pred
def forward_variance_preprocess(
self, encoder_out, ph_dur, pitch,
variances: dict = None, retake=None, spk_embed=None
):
condition = self.forward_mel2x_gather(encoder_out, ph_dur, x_dim=self.hidden_size, check_stretch_embed=True)
if self.use_variance_scaling:
variance_cond = condition + self.pitch_embed(pitch[:, :, None] / 12)
else:
variance_cond = condition + self.pitch_embed(pitch[:, :, None])
non_retake_masks = [
v_retake.float() # [B, T, 1]
for v_retake in (~retake).split(1, dim=2)
]
variance_embeds = [
self.variance_embeds[v_name](variances[v_name][:, :, None] * self.variance_retake_scaling[v_name]) * v_masks
for v_name, v_masks in zip(self.variance_prediction_list, non_retake_masks)
]
variance_cond += torch.stack(variance_embeds, dim=-1).sum(-1)
if hparams['use_spk_id'] and spk_embed is not None:
variance_cond += spk_embed
return variance_cond
def forward_variance_reflow(self, variance_cond, steps: int = 10):
xs_pred = self.variance_predictor(variance_cond, steps=steps)
return xs_pred
def forward_variance_postprocess(self, xs_pred):
if self.variance_predictor.num_feats == 1:
xs_pred = [xs_pred]
else:
xs_pred = xs_pred.unbind(dim=1)
variance_pred = self.variance_predictor.clamp_spec(xs_pred)
return tuple(variance_pred)
def view_as_linguistic_encoder(self):
model = copy.deepcopy(self)
if self.predict_pitch:
del model.pitch_predictor
if self.use_melody_encoder:
del model.melody_encoder
if self.predict_variances:
del model.variance_predictor
model.fs2 = model.fs2.view_as_encoder()
if self.predict_dur:
model.forward = model.forward_linguistic_encoder_word
else:
model.forward = model.forward_linguistic_encoder_phoneme
return model
def view_as_dur_predictor(self):
assert self.predict_dur
model = copy.deepcopy(self)
if self.predict_pitch:
del model.pitch_predictor
if self.use_melody_encoder:
del model.melody_encoder
if self.predict_variances:
del model.variance_predictor
model.fs2 = model.fs2.view_as_dur_predictor()
model.forward = model.forward_dur_predictor
return model
def view_as_pitch_preprocess(self):
model = copy.deepcopy(self)
del model.fs2
if self.predict_pitch:
del model.pitch_predictor
if self.predict_variances:
del model.variance_predictor
model.forward = model.forward_pitch_preprocess
return model
def view_as_pitch_predictor(self):
assert self.predict_pitch
model = copy.deepcopy(self)
del model.fs2
del model.lr
if self.use_melody_encoder:
del model.melody_encoder
if self.predict_variances:
del model.variance_predictor
model.forward = model.forward_pitch_reflow
return model
def view_as_pitch_postprocess(self):
model = copy.deepcopy(self)
del model.fs2
if self.use_melody_encoder:
del model.melody_encoder
if self.predict_variances:
del model.variance_predictor
model.forward = model.forward_pitch_postprocess
return model
def view_as_variance_preprocess(self):
model = copy.deepcopy(self)
del model.fs2
if self.predict_pitch:
del model.pitch_predictor
if self.use_melody_encoder:
del model.melody_encoder
if self.predict_variances:
del model.variance_predictor
model.forward = model.forward_variance_preprocess
return model
def view_as_variance_predictor(self):
assert self.predict_variances
model = copy.deepcopy(self)
del model.fs2
del model.lr
if self.predict_pitch:
del model.pitch_predictor
if self.use_melody_encoder:
del model.melody_encoder
model.forward = model.forward_variance_reflow
return model
def view_as_variance_postprocess(self):
model = copy.deepcopy(self)
del model.fs2
if self.predict_pitch:
del model.pitch_predictor
if self.use_melody_encoder:
del model.melody_encoder
model.forward = model.forward_variance_postprocess
return model