init commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
@@ -1,2 +1,11 @@
|
||||
# FireRedTTS-ComfyUI
|
||||
a custom node for [FireRedTTS](https://github.com/FireRedTeam/FireRedTTS)
|
||||
a custom node for [FireRedTTS](https://github.com/FireRedTeam/FireRedTTS),you can find [workflow here](./doc/base_workflow.json)
|
||||
|
||||
## Example
|
||||
|text|prompt_wav|out_wav|
|
||||
|--|--|--|
|
||||
|`《三体》是刘慈欣创作的长篇科幻小说系列,由《三体》《三体2:黑暗森林》《三体3:死神永生》组成,第一部于2006年5月起在《科幻世界》杂志上连载,第二部于2008年5月首次出版,第三部则于2010年11月出版。作品讲述了地球人类文明和三体文明的信息交流、生死搏杀及两个文明在宇宙中的兴衰历程。其第一部经过刘宇昆翻译后获得了第73届雨果奖最佳长篇小说奖,第三部英文版获得2017年轨迹奖最佳长篇科幻小说奖。2019年,列入“新中国70年70部长篇小说典藏”。2022年9月,《三体》入选2021十大年度国家IP。`|<video src=""/> |<video src=""/>|
|
||||
|
||||
## Features
|
||||
- speed control
|
||||
- auto split text
|
||||
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
import os,sys
|
||||
import os.path as osp
|
||||
now_dir = osp.dirname(osp.abspath(__file__))
|
||||
sys.path.append(now_dir)
|
||||
tmp_dir = osp.join(now_dir, "tmp")
|
||||
import re
|
||||
import torch
|
||||
import ffmpeg
|
||||
import shutil
|
||||
import tempfile
|
||||
import torchaudio
|
||||
import folder_paths
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from comfy.utils import ProgressBar
|
||||
from pydub import AudioSegment
|
||||
from pydub.silence import split_on_silence
|
||||
from huggingface_hub import snapshot_download
|
||||
from fireredtts.fireredtts import FireRedTTS
|
||||
from zhon.hanzi import punctuation
|
||||
SPLIT_WORDS = [
|
||||
"but", "however", "nevertheless", "yet", "still",
|
||||
"therefore", "thus", "hence", "consequently",
|
||||
"moreover", "furthermore", "additionally",
|
||||
"meanwhile", "alternatively", "otherwise",
|
||||
"namely", "specifically", "for example", "such as",
|
||||
"in fact", "indeed", "notably",
|
||||
"in contrast", "on the other hand", "conversely",
|
||||
"in conclusion", "to summarize", "finally"
|
||||
]
|
||||
device = (
|
||||
"cuda"
|
||||
if torch.cuda.is_available()
|
||||
else "mps" if torch.backends.mps.is_available() else "cpu"
|
||||
)
|
||||
aifsh_models = osp.join(folder_paths.models_dir,"AIFSH")
|
||||
fireredtss_dir = osp.join(aifsh_models,"FireRedTTS")
|
||||
|
||||
def speed_change(input_audio, speed, sr):
|
||||
# 检查输入数据类型和声道数
|
||||
if input_audio.dtype != np.int16:
|
||||
raise ValueError("输入音频数据类型必须为 np.int16")
|
||||
|
||||
|
||||
# 转换为字节流
|
||||
raw_audio = input_audio.astype(np.int16).tobytes()
|
||||
|
||||
# 设置 ffmpeg 输入流
|
||||
input_stream = ffmpeg.input('pipe:', format='s16le', acodec='pcm_s16le', ar=str(sr), ac=1)
|
||||
|
||||
# 变速处理
|
||||
output_stream = input_stream.filter('atempo', speed)
|
||||
|
||||
# 输出流到管道
|
||||
out, _ = (
|
||||
output_stream.output('pipe:', format='s16le', acodec='pcm_s16le')
|
||||
.run(input=raw_audio, capture_stdout=True, capture_stderr=True)
|
||||
)
|
||||
|
||||
# 将管道输出解码为 NumPy 数组
|
||||
processed_audio = np.frombuffer(out, np.int16)
|
||||
|
||||
return processed_audio
|
||||
|
||||
class FireRedTTSNode:
|
||||
def __init__(self):
|
||||
if not osp.exists(osp.join(fireredtss_dir,"fireredtts_gpt.pt")):
|
||||
snapshot_download(repo_id="fireredteam/FireRedTTS",local_dir=fireredtss_dir)
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
return {
|
||||
"required":{
|
||||
"text":("TEXT",),
|
||||
"prompt_wav":("AUDIO",),
|
||||
"remove_slience":("BOOLEAN",{
|
||||
"default": True
|
||||
}),
|
||||
"speed":("FLOAT",{
|
||||
"default":1.0,
|
||||
"min":0.5,
|
||||
"max":2.0,
|
||||
"step":0.05,
|
||||
"round":0.001,
|
||||
"display":"slider"
|
||||
}),
|
||||
"split_words":("STRING",{
|
||||
"default":",".join(SPLIT_WORDS),
|
||||
"multiline": True,
|
||||
"dynamicPrompts": True,
|
||||
"tooltip":"Enter custom words to split on, separated by commas. Leave blank to use default list.",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO",)
|
||||
#RETURN_NAMES = ("image_output_name",)
|
||||
|
||||
FUNCTION = "gen_audio"
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = "AIFSH_FireRedTSS"
|
||||
|
||||
def gen_audio(self,text,prompt_wav,remove_slience,speed,split_words):
|
||||
os.makedirs(tmp_dir, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav",dir=tmp_dir) as f:
|
||||
waveform = prompt_wav["waveform"].squeeze(0)
|
||||
torchaudio.save(f.name,waveform,prompt_wav["sample_rate"])
|
||||
if remove_slience:
|
||||
combined = slice(f.name)
|
||||
combined.export(f.name, format="wav")
|
||||
prompt_wav = f.name
|
||||
# Split the input text into batches
|
||||
if len(text.encode('utf-8')) == len(text):
|
||||
max_chars = 400-len(text.encode('utf-8'))
|
||||
else:
|
||||
max_chars = 300-len(text.encode('utf-8'))
|
||||
|
||||
if not split_words.strip():
|
||||
custom_words = [word.strip() for word in split_words.split(',')]
|
||||
global SPLIT_WORDS
|
||||
SPLIT_WORDS = custom_words
|
||||
gen_text_batches = split_text_into_batches(text, max_chars=max_chars)
|
||||
comfy_par = ProgressBar(len(gen_text_batches))
|
||||
tts = FireRedTTS(config_path=osp.join(now_dir,"config_24k.json"),
|
||||
pretrained_path=fireredtss_dir,device=device)
|
||||
rec_wavs_list = []
|
||||
for i,i_text in enumerate(tqdm(gen_text_batches,total=len(gen_text_batches),desc="TTS ...")):
|
||||
for dot in punctuation:
|
||||
i_text = i_text.replace(dot,"")
|
||||
print(f"sentence {i+1}, synthesize text:{i_text}")
|
||||
rec_wavs = tts.synthesize(prompt_wav=prompt_wav,text=i_text)
|
||||
rec_wavs = rec_wavs.detach().cpu().numpy()
|
||||
rec_wavs_list.append(rec_wavs)
|
||||
comfy_par.update(1)
|
||||
res_np = np.concatenate(rec_wavs_list,axis=1)
|
||||
if speed > 1.0 or speed < 1.0:
|
||||
res_np = res_np * 32768
|
||||
res_np = res_np.astype(np.int16)
|
||||
res_np = speed_change(res_np,speed,sr=24000)
|
||||
waveform = torch.from_numpy(res_np/32768).unsqueeze(0).unsqueeze(0)
|
||||
else:
|
||||
waveform = torch.from_numpy(res_np).unsqueeze(0)
|
||||
print(waveform.shape)
|
||||
res_audio = {
|
||||
"waveform": waveform,
|
||||
"sample_rate": 24000
|
||||
}
|
||||
shutil.rmtree(tmp_dir)
|
||||
return (res_audio, )
|
||||
|
||||
def split_text_into_batches(text, max_chars=200, split_words=SPLIT_WORDS):
|
||||
if len(text.encode('utf-8')) <= max_chars:
|
||||
return [text]
|
||||
if text[-1] not in ['。', '.', '!', '!', '?', '?']:
|
||||
text += '.'
|
||||
|
||||
sentences = re.split('([。.!?!?])', text)
|
||||
sentences = [''.join(i) for i in zip(sentences[0::2], sentences[1::2])]
|
||||
|
||||
batches = []
|
||||
current_batch = ""
|
||||
|
||||
def split_by_words(text):
|
||||
words = text.split()
|
||||
current_word_part = ""
|
||||
word_batches = []
|
||||
for word in words:
|
||||
if len(current_word_part.encode('utf-8')) + len(word.encode('utf-8')) + 1 <= max_chars:
|
||||
current_word_part += word + ' '
|
||||
else:
|
||||
if current_word_part:
|
||||
# Try to find a suitable split word
|
||||
for split_word in split_words:
|
||||
split_index = current_word_part.rfind(' ' + split_word + ' ')
|
||||
if split_index != -1:
|
||||
word_batches.append(current_word_part[:split_index].strip())
|
||||
current_word_part = current_word_part[split_index:].strip() + ' '
|
||||
break
|
||||
else:
|
||||
# If no suitable split word found, just append the current part
|
||||
word_batches.append(current_word_part.strip())
|
||||
current_word_part = ""
|
||||
current_word_part += word + ' '
|
||||
if current_word_part:
|
||||
word_batches.append(current_word_part.strip())
|
||||
return word_batches
|
||||
|
||||
for sentence in sentences:
|
||||
if len(current_batch.encode('utf-8')) + len(sentence.encode('utf-8')) <= max_chars:
|
||||
current_batch += sentence
|
||||
else:
|
||||
# If adding this sentence would exceed the limit
|
||||
if current_batch:
|
||||
batches.append(current_batch)
|
||||
current_batch = ""
|
||||
|
||||
# If the sentence itself is longer than max_chars, split it
|
||||
if len(sentence.encode('utf-8')) > max_chars:
|
||||
# First, try to split by colon
|
||||
colon_parts = sentence.split(':')
|
||||
if len(colon_parts) > 1:
|
||||
for part in colon_parts:
|
||||
if len(part.encode('utf-8')) <= max_chars:
|
||||
batches.append(part)
|
||||
else:
|
||||
# If colon part is still too long, split by comma
|
||||
comma_parts = re.split('[,,]', part)
|
||||
if len(comma_parts) > 1:
|
||||
current_comma_part = ""
|
||||
for comma_part in comma_parts:
|
||||
if len(current_comma_part.encode('utf-8')) + len(comma_part.encode('utf-8')) <= max_chars:
|
||||
current_comma_part += comma_part + ','
|
||||
else:
|
||||
if current_comma_part:
|
||||
batches.append(current_comma_part.rstrip(','))
|
||||
current_comma_part = comma_part + ','
|
||||
if current_comma_part:
|
||||
batches.append(current_comma_part.rstrip(','))
|
||||
else:
|
||||
# If no comma, split by words
|
||||
batches.extend(split_by_words(part))
|
||||
else:
|
||||
# If no colon, split by comma
|
||||
comma_parts = re.split('[,,]', sentence)
|
||||
if len(comma_parts) > 1:
|
||||
current_comma_part = ""
|
||||
for comma_part in comma_parts:
|
||||
if len(current_comma_part.encode('utf-8')) + len(comma_part.encode('utf-8')) <= max_chars:
|
||||
current_comma_part += comma_part + ','
|
||||
else:
|
||||
if current_comma_part:
|
||||
batches.append(current_comma_part.rstrip(','))
|
||||
current_comma_part = comma_part + ','
|
||||
if current_comma_part:
|
||||
batches.append(current_comma_part.rstrip(','))
|
||||
else:
|
||||
# If no comma, split by words
|
||||
batches.extend(split_by_words(sentence))
|
||||
else:
|
||||
current_batch = sentence
|
||||
|
||||
if current_batch:
|
||||
batches.append(current_batch)
|
||||
|
||||
return batches
|
||||
|
||||
|
||||
def slice(audio_path):
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
audio_path (_type_): audio path
|
||||
"""
|
||||
try:
|
||||
audio = AudioSegment.from_file(audio_path)
|
||||
except:
|
||||
print(audio_path)
|
||||
return 0
|
||||
|
||||
segments = split_on_silence(
|
||||
audio, min_silence_len=200, silence_thresh=-50, seek_step=100, keep_silence=100
|
||||
)
|
||||
|
||||
print("---segments:\n", segments)
|
||||
|
||||
combined = segments[0]
|
||||
for i in range(1, len(segments)):
|
||||
combined += segments[i]
|
||||
return combined
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FireRedTTSNode": FireRedTTSNode
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"gpt": {
|
||||
"gpt_max_audio_tokens": 630,
|
||||
"gpt_max_text_tokens": 402,
|
||||
"gpt_max_prompt_tokens": 70,
|
||||
"gpt_layers": 30,
|
||||
"gpt_n_model_channels": 1024,
|
||||
"gpt_n_heads": 16,
|
||||
"gpt_number_text_tokens": 57341,
|
||||
"gpt_start_text_token": 57187,
|
||||
"gpt_stop_text_token": 57184,
|
||||
"gpt_num_audio_tokens": 16386,
|
||||
"gpt_start_audio_token": 16384,
|
||||
"gpt_stop_audio_token": 16385,
|
||||
"gpt_code_stride_len": 640,
|
||||
"duration_const": 102400,
|
||||
"min_conditioning_length": 48000,
|
||||
"max_conditioning_length": 128000,
|
||||
"max_wav_length": 320000,
|
||||
"max_text_length": 200
|
||||
},
|
||||
"flow": {
|
||||
"output_size": 100,
|
||||
"input_embedding": {
|
||||
"out_channels": 512,
|
||||
"codebook_path": "fireredtts/modules/flow/codebook.npy",
|
||||
"freeze": true
|
||||
},
|
||||
"encoder": {
|
||||
"input_size": 512,
|
||||
"output_size": 512,
|
||||
"attention_heads": 8,
|
||||
"linear_units": 2048,
|
||||
"num_blocks": 6,
|
||||
"dropout_rate": 0.01,
|
||||
"srcattention_start_index": 0,
|
||||
"srcattention_end_index": 2,
|
||||
"attention_dropout_rate": 0.01,
|
||||
"positional_dropout_rate": 0.01,
|
||||
"key_bias": true,
|
||||
"normalize_before": true
|
||||
},
|
||||
"length_regulator": {
|
||||
"channels": 512,
|
||||
"num_blocks": 4
|
||||
},
|
||||
"mel_encoder": {
|
||||
"in_channels": 100,
|
||||
"out_channels": 512,
|
||||
"hidden_channels": 384,
|
||||
"reduction_rate": 4,
|
||||
"n_layers": 2,
|
||||
"n_blocks": 5,
|
||||
"kernel_size": 3
|
||||
},
|
||||
"decoder": {
|
||||
"t_scheduler": "cosine",
|
||||
"inference_cfg_rate": 0.7,
|
||||
"estimator": {
|
||||
"in_channels": 200,
|
||||
"out_channels": 100,
|
||||
"channels": [
|
||||
256,
|
||||
256
|
||||
],
|
||||
"dropout": 0,
|
||||
"attention_head_dim": 64,
|
||||
"n_blocks": 4,
|
||||
"num_mid_blocks": 12,
|
||||
"num_heads": 8,
|
||||
"act_fn": "gelu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bigvgan": {
|
||||
"num_mels": 100,
|
||||
"upsample_initial_channel": 1536,
|
||||
"upsample_rates": [
|
||||
5,
|
||||
3,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
"upsample_kernel_sizes": [
|
||||
11,
|
||||
7,
|
||||
4,
|
||||
4,
|
||||
4,
|
||||
4
|
||||
],
|
||||
"resblock_kernel_sizes": [
|
||||
3,
|
||||
7,
|
||||
11
|
||||
],
|
||||
"resblock_dilation_sizes": [
|
||||
[
|
||||
1,
|
||||
3,
|
||||
5
|
||||
],
|
||||
[
|
||||
1,
|
||||
3,
|
||||
5
|
||||
],
|
||||
[
|
||||
1,
|
||||
3,
|
||||
5
|
||||
]
|
||||
],
|
||||
"resblock_type": "1",
|
||||
"snake_logscale": true,
|
||||
"activation": "snakebeta",
|
||||
"use_tanh_at_final": false,
|
||||
"use_bias_at_final": false,
|
||||
"use_cuda_kernel": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"last_node_id": 4,
|
||||
"last_link_id": 3,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "LoadAudio",
|
||||
"pos": {
|
||||
"0": 94,
|
||||
"1": 383
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 124
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadAudio"
|
||||
},
|
||||
"widgets_values": [
|
||||
"test_zh_1_ref_short.wav",
|
||||
null,
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "PromptTextNode",
|
||||
"pos": {
|
||||
"0": 26,
|
||||
"1": 73
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "TEXT",
|
||||
"type": "TEXT",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptTextNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"《三体》是刘慈欣创作的长篇科幻小说系列,由《三体》《三体2:黑暗森林》《三体3:死神永生》组成,第一部于2006年5月起在《科幻世界》杂志上连载,第二部于2008年5月首次出版,第三部则于2010年11月出版。\n作品讲述了地球人类文明和三体文明的信息交流、生死搏杀及两个文明在宇宙中的兴衰历程。其第一部经过刘宇昆翻译后获得了第73届雨果奖最佳长篇小说奖,第三部英文版获得2017年轨迹奖最佳长篇科幻小说奖。2019年,列入“新中国70年70部长篇小说典藏”。2022年9月,《三体》入选2021十大年度国家IP。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "FireRedTTSNode",
|
||||
"pos": {
|
||||
"0": 504,
|
||||
"1": 86
|
||||
},
|
||||
"size": {
|
||||
"0": 309.60003662109375,
|
||||
"1": 210.80001831054688
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "TEXT",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "prompt_wav",
|
||||
"type": "AUDIO",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FireRedTTSNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
true,
|
||||
"but,however,nevertheless,yet,still,therefore,thus,hence,consequently,moreover,furthermore,additionally,meanwhile,alternatively,otherwise,namely,specifically,for example,such as,in fact,indeed,notably,in contrast,on the other hand,conversely,in conclusion,to summarize,finally"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewAudio",
|
||||
"pos": {
|
||||
"0": 865,
|
||||
"1": 93
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 76
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewAudio"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"TEXT"
|
||||
],
|
||||
[
|
||||
2,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
3,
|
||||
1,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"AUDIO"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
from fireredtts.modules.gpt.gpt import GPT
|
||||
from fireredtts.modules import Token2Wav, MelSpectrogramExtractor
|
||||
from fireredtts.modules.tokenizer.tokenizer import VoiceBpeTokenizer
|
||||
from fireredtts.modules.codec.speaker import SpeakerEmbedddingExtractor
|
||||
from fireredtts.utils.utils import load_audio
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class FireRedTTS:
|
||||
def __init__(self, config_path, pretrained_path, device="cuda"):
|
||||
self.device = device
|
||||
self.config = json.load(open(config_path))
|
||||
self.gpt_path = os.path.join(pretrained_path, "fireredtts_gpt.pt")
|
||||
self.token2wav_path = os.path.join(pretrained_path, "fireredtts_token2wav.pt")
|
||||
self.speaker_extractor_path = os.path.join(
|
||||
pretrained_path, "fireredtts_speaker.bin"
|
||||
)
|
||||
assert os.path.exists(self.token2wav_path)
|
||||
assert os.path.exists(self.gpt_path)
|
||||
assert os.path.exists(self.speaker_extractor_path)
|
||||
|
||||
# tokenizer;
|
||||
self.text_tokenizer = VoiceBpeTokenizer()
|
||||
|
||||
# speaker ectractor
|
||||
self.speaker_extractor = SpeakerEmbedddingExtractor(
|
||||
ckpt_path=self.speaker_extractor_path, device=device
|
||||
)
|
||||
|
||||
# load gpt model
|
||||
self.gpt = GPT(
|
||||
start_text_token=self.config["gpt"]["gpt_start_text_token"],
|
||||
stop_text_token=self.config["gpt"]["gpt_stop_text_token"],
|
||||
layers=self.config["gpt"]["gpt_layers"],
|
||||
model_dim=self.config["gpt"]["gpt_n_model_channels"],
|
||||
heads=self.config["gpt"]["gpt_n_heads"],
|
||||
max_text_tokens=self.config["gpt"]["gpt_max_text_tokens"],
|
||||
max_mel_tokens=self.config["gpt"]["gpt_max_audio_tokens"],
|
||||
max_prompt_tokens=self.config["gpt"]["gpt_max_prompt_tokens"],
|
||||
code_stride_len=self.config["gpt"]["gpt_code_stride_len"],
|
||||
number_text_tokens=self.config["gpt"]["gpt_number_text_tokens"],
|
||||
num_audio_tokens=self.config["gpt"]["gpt_num_audio_tokens"],
|
||||
start_audio_token=self.config["gpt"]["gpt_start_audio_token"],
|
||||
stop_audio_token=self.config["gpt"]["gpt_stop_audio_token"],
|
||||
)
|
||||
|
||||
sd = torch.load(self.gpt_path, map_location=device)["model"]
|
||||
self.gpt.load_state_dict(sd, strict=True)
|
||||
self.gpt = self.gpt.to(device=device)
|
||||
self.gpt.eval()
|
||||
self.gpt.init_gpt_for_inference(kv_cache=True)
|
||||
|
||||
# mel-spectrogram extractor
|
||||
self.mel_extractor = MelSpectrogramExtractor()
|
||||
|
||||
# load token2wav model
|
||||
self.token2wav = Token2Wav.init_from_config(self.config)
|
||||
sd = torch.load(self.token2wav_path, map_location="cpu")
|
||||
self.token2wav.load_state_dict(sd, strict=True)
|
||||
self.token2wav.generator.remove_weight_norm()
|
||||
self.token2wav.eval()
|
||||
self.token2wav = self.token2wav.to(device)
|
||||
|
||||
def extract_spk_embeddings(self, prompt_wav):
|
||||
_, _, audio_resampled = load_audio(audiopath=prompt_wav, sampling_rate=16000)
|
||||
audio_len = torch.tensor(
|
||||
data=[audio_resampled.shape[1]], dtype=torch.long, requires_grad=False
|
||||
)
|
||||
|
||||
# speaker embeddings [1,512]
|
||||
spk_embeddings = self.speaker_extractor(
|
||||
audio_resampled.to(device="cuda")
|
||||
).unsqueeze(0)
|
||||
|
||||
return spk_embeddings
|
||||
|
||||
def do_gpt_inference(self, spk_gpt, text_tokens):
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
spk_gpt (_type_): speaker embeddidng in gpt
|
||||
text_tokens (_type_): text tokens
|
||||
"""
|
||||
with torch.no_grad():
|
||||
gpt_codes = self.gpt.generate(
|
||||
cond_latents=spk_gpt,
|
||||
text_inputs=text_tokens,
|
||||
input_tokens=None,
|
||||
do_sample=True,
|
||||
top_p=0.85,
|
||||
top_k=30,
|
||||
temperature=0.75,
|
||||
num_return_sequences=9,
|
||||
num_beams=1,
|
||||
length_penalty=1.0,
|
||||
repetition_penalty=2.0,
|
||||
output_attentions=False,
|
||||
)
|
||||
|
||||
seqs = []
|
||||
EOS_TOKEN = self.config["gpt"]["gpt_stop_audio_token"]
|
||||
for seq in gpt_codes:
|
||||
index = (seq == EOS_TOKEN).nonzero(as_tuple=True)[0][0]
|
||||
seq = seq[:index]
|
||||
seqs.append(seq)
|
||||
|
||||
sorted_seqs = sorted(seqs, key=lambda i: len(i), reverse=False)
|
||||
gpt_codes = sorted_seqs[2].unsqueeze(0) # [1, len]
|
||||
# sorted_len = [len(l) for l in sorted_seqs]
|
||||
# print("---sorted_len:", sorted_len)
|
||||
|
||||
return gpt_codes
|
||||
|
||||
def synthesize(self, prompt_wav, text, lang="auto"):
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
prompts_wav (_type_): prompts_wav path
|
||||
text (_type_): text
|
||||
lang (_type_): language of text
|
||||
"""
|
||||
# Currently only supports Chinese and English
|
||||
assert lang in ["zh", "en", "auto"]
|
||||
assert os.path.exists(prompt_wav)
|
||||
|
||||
# text to tokens
|
||||
text_tokens = self.text_tokenizer.encode(text=text, lang=lang)
|
||||
text_tokens = torch.IntTensor(text_tokens).unsqueeze(0).to(self.device)
|
||||
assert text_tokens.shape[-1] < 400
|
||||
|
||||
# extract speaker embedding
|
||||
spk_embeddings = self.extract_spk_embeddings(prompt_wav=prompt_wav).unsqueeze(0)
|
||||
with torch.no_grad():
|
||||
spk_gpt = self.gpt.reference_embedding(spk_embeddings)
|
||||
|
||||
# gpt inference
|
||||
gpt_start_time = time.time()
|
||||
gpt_codes = self.do_gpt_inference(spk_gpt=spk_gpt, text_tokens=text_tokens)
|
||||
gpt_end_time = time.time()
|
||||
gpt_dur = gpt_end_time - gpt_start_time
|
||||
|
||||
# prompt mel-spectrogram compute
|
||||
prompt_mel = (
|
||||
self.mel_extractor(wav_path=prompt_wav).unsqueeze(0).to(self.device)
|
||||
)
|
||||
# convert token to waveform (b=1, t)
|
||||
voc_start_time = time.time()
|
||||
rec_wavs = self.token2wav.inference(gpt_codes, prompt_mel, n_timesteps=10)
|
||||
voc_end_time = time.time()
|
||||
voc_dur = voc_end_time - voc_start_time
|
||||
all_dur = voc_end_time - gpt_start_time
|
||||
|
||||
# rtf compute
|
||||
# audio_dur = rec_wavs.shape[-1] / 24000
|
||||
# rtf_gpt = gpt_dur / audio_dur
|
||||
# rtf_voc = voc_dur / audio_dur
|
||||
# rtf_all = all_dur / audio_dur
|
||||
|
||||
return rec_wavs
|
||||
@@ -0,0 +1,42 @@
|
||||
import json
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from fireredtts.modules.bigvgan import get_bigvgan_backend
|
||||
from fireredtts.modules.flow import get_flow_frontend, MelSpectrogramExtractor
|
||||
|
||||
|
||||
class Token2Wav(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
flow: nn.Module,
|
||||
generator: nn.Module,
|
||||
):
|
||||
super().__init__()
|
||||
self.flow = flow
|
||||
self.generator = generator
|
||||
|
||||
@torch.no_grad()
|
||||
def inference(
|
||||
self, tokens: torch.Tensor, prompt_mel: torch.Tensor, n_timesteps: int = 10
|
||||
) -> torch.Tensor:
|
||||
token_len = torch.tensor([tokens.shape[1]], dtype=torch.long).to(tokens.device)
|
||||
prompt_mel_len = torch.tensor([prompt_mel.shape[1]], dtype=torch.long).to(
|
||||
prompt_mel.device
|
||||
)
|
||||
# flow
|
||||
mel = self.flow.inference(
|
||||
token=tokens,
|
||||
token_len=token_len,
|
||||
prompt_mel=prompt_mel,
|
||||
prompt_mel_len=prompt_mel_len,
|
||||
n_timesteps=n_timesteps,
|
||||
)
|
||||
# bigvgan
|
||||
audio = self.generator(mel) # (b=1, 1, t)
|
||||
return audio.squeeze(1)
|
||||
|
||||
@classmethod
|
||||
def init_from_config(cls, config) -> "Token2Wav":
|
||||
flow = get_flow_frontend(config["flow"])
|
||||
bigvgan = get_bigvgan_backend(config["bigvgan"])
|
||||
return cls(flow, bigvgan)
|
||||
@@ -0,0 +1,6 @@
|
||||
from fireredtts.modules.bigvgan.bigvgan import BigVGAN
|
||||
|
||||
|
||||
def get_bigvgan_backend(bigvgan_config):
|
||||
generator = BigVGAN(**bigvgan_config)
|
||||
return generator
|
||||
@@ -0,0 +1,126 @@
|
||||
# Implementation adapted from https://github.com/EdwardDixon/snake under the MIT license.
|
||||
|
||||
|
||||
import torch
|
||||
from torch import nn, sin, pow
|
||||
from torch.nn import Parameter
|
||||
|
||||
|
||||
class Snake(nn.Module):
|
||||
"""
|
||||
Implementation of a sine-based periodic activation function
|
||||
Shape:
|
||||
- Input: (B, C, T)
|
||||
- Output: (B, C, T), same shape as the input
|
||||
Parameters:
|
||||
- alpha - trainable parameter
|
||||
References:
|
||||
- This activation function is from this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda:
|
||||
https://arxiv.org/abs/2006.08195
|
||||
Examples:
|
||||
>>> a1 = snake(256)
|
||||
>>> x = torch.randn(256)
|
||||
>>> x = a1(x)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False
|
||||
):
|
||||
"""
|
||||
Initialization.
|
||||
INPUT:
|
||||
- in_features: shape of the input
|
||||
- alpha: trainable parameter
|
||||
alpha is initialized to 1 by default, higher values = higher-frequency.
|
||||
alpha will be trained along with the rest of your model.
|
||||
"""
|
||||
super(Snake, self).__init__()
|
||||
self.in_features = in_features
|
||||
|
||||
# initialize alpha
|
||||
self.alpha_logscale = alpha_logscale
|
||||
if self.alpha_logscale: # log scale alphas initialized to zeros
|
||||
self.alpha = Parameter(torch.zeros(in_features) * alpha)
|
||||
else: # linear scale alphas initialized to ones
|
||||
self.alpha = Parameter(torch.ones(in_features) * alpha)
|
||||
|
||||
self.alpha.requires_grad = alpha_trainable
|
||||
|
||||
self.no_div_by_zero = 0.000000001
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Forward pass of the function.
|
||||
Applies the function to the input elementwise.
|
||||
Snake ∶= x + 1/a * sin^2 (xa)
|
||||
"""
|
||||
alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T]
|
||||
if self.alpha_logscale:
|
||||
alpha = torch.exp(alpha)
|
||||
x = x + (1.0 / (alpha + self.no_div_by_zero)) * pow(sin(x * alpha), 2)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class SnakeBeta(nn.Module):
|
||||
"""
|
||||
A modified Snake function which uses separate parameters for the magnitude of the periodic components
|
||||
Shape:
|
||||
- Input: (B, C, T)
|
||||
- Output: (B, C, T), same shape as the input
|
||||
Parameters:
|
||||
- alpha - trainable parameter that controls frequency
|
||||
- beta - trainable parameter that controls magnitude
|
||||
References:
|
||||
- This activation function is a modified version based on this paper by Liu Ziyin, Tilman Hartwig, Masahito Ueda:
|
||||
https://arxiv.org/abs/2006.08195
|
||||
Examples:
|
||||
>>> a1 = snakebeta(256)
|
||||
>>> x = torch.randn(256)
|
||||
>>> x = a1(x)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, in_features, alpha=1.0, alpha_trainable=True, alpha_logscale=False
|
||||
):
|
||||
"""
|
||||
Initialization.
|
||||
INPUT:
|
||||
- in_features: shape of the input
|
||||
- alpha - trainable parameter that controls frequency
|
||||
- beta - trainable parameter that controls magnitude
|
||||
alpha is initialized to 1 by default, higher values = higher-frequency.
|
||||
beta is initialized to 1 by default, higher values = higher-magnitude.
|
||||
alpha will be trained along with the rest of your model.
|
||||
"""
|
||||
super(SnakeBeta, self).__init__()
|
||||
self.in_features = in_features
|
||||
|
||||
# initialize alpha
|
||||
self.alpha_logscale = alpha_logscale
|
||||
if self.alpha_logscale: # log scale alphas initialized to zeros
|
||||
self.alpha = Parameter(torch.zeros(in_features) * alpha)
|
||||
self.beta = Parameter(torch.zeros(in_features) * alpha)
|
||||
else: # linear scale alphas initialized to ones
|
||||
self.alpha = Parameter(torch.ones(in_features) * alpha)
|
||||
self.beta = Parameter(torch.ones(in_features) * alpha)
|
||||
|
||||
self.alpha.requires_grad = alpha_trainable
|
||||
self.beta.requires_grad = alpha_trainable
|
||||
|
||||
self.no_div_by_zero = 0.000000001
|
||||
|
||||
def forward(self, x):
|
||||
"""
|
||||
Forward pass of the function.
|
||||
Applies the function to the input elementwise.
|
||||
SnakeBeta ∶= x + 1/b * sin^2 (xa)
|
||||
"""
|
||||
alpha = self.alpha.unsqueeze(0).unsqueeze(-1) # line up with x to [B, C, T]
|
||||
beta = self.beta.unsqueeze(0).unsqueeze(-1)
|
||||
if self.alpha_logscale:
|
||||
alpha = torch.exp(alpha)
|
||||
beta = torch.exp(beta)
|
||||
x = x + (1.0 / (beta + self.no_div_by_zero)) * pow(sin(x * alpha), 2)
|
||||
|
||||
return x
|
||||
@@ -0,0 +1,75 @@
|
||||
# Copyright (c) 2024 NVIDIA CORPORATION.
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from token2wav.alias_free_torch.resample import UpSample1d, DownSample1d
|
||||
|
||||
# load fused CUDA kernel: this enables importing anti_alias_activation_cuda
|
||||
from token2wav.alias_free_cuda import load
|
||||
|
||||
load.load()
|
||||
|
||||
|
||||
class FusedAntiAliasActivation(torch.autograd.Function):
|
||||
"""
|
||||
Assumes filter size 12, replication padding on upsampling, and logscale alpha/beta parameters as inputs
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, inputs, ftr, alpha, beta):
|
||||
import anti_alias_activation_cuda
|
||||
|
||||
activation_results = anti_alias_activation_cuda.forward(
|
||||
inputs, ftr, alpha, beta
|
||||
)
|
||||
return activation_results
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, output_grads):
|
||||
# TODO: implement bwd pass
|
||||
raise NotImplementedError
|
||||
return output_grads, None, None
|
||||
|
||||
|
||||
class Activation1d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
activation,
|
||||
up_ratio: int = 2,
|
||||
down_ratio: int = 2,
|
||||
up_kernel_size: int = 12,
|
||||
down_kernel_size: int = 12,
|
||||
fused: bool = True,
|
||||
):
|
||||
super().__init__()
|
||||
self.up_ratio = up_ratio
|
||||
self.down_ratio = down_ratio
|
||||
self.act = activation
|
||||
self.upsample = UpSample1d(up_ratio, up_kernel_size)
|
||||
self.downsample = DownSample1d(down_ratio, down_kernel_size)
|
||||
|
||||
self.fused = fused # whether to use fused CUDA kernel or not
|
||||
|
||||
def forward(self, x):
|
||||
if not self.fused:
|
||||
x = self.upsample(x)
|
||||
x = self.act(x)
|
||||
x = self.downsample(x)
|
||||
return x
|
||||
else:
|
||||
if self.act.__class__.__name__ == "Snake":
|
||||
beta = self.act.alpha.data # snake uses same params for alpha and beta
|
||||
else:
|
||||
beta = (
|
||||
self.act.beta.data
|
||||
) # snakebeta uses different params for alpha and beta
|
||||
alpha = self.act.alpha.data
|
||||
if (
|
||||
not self.act.alpha_logscale
|
||||
): # exp baked into cuda kernel, cancel it out with a log
|
||||
alpha = torch.log(alpha)
|
||||
beta = torch.log(beta)
|
||||
x = FusedAntiAliasActivation.apply(x, self.upsample.filter, alpha, beta)
|
||||
x = self.downsample(x)
|
||||
return x
|
||||
@@ -0,0 +1,48 @@
|
||||
/* coding=utf-8
|
||||
* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cuda_fp16.h>
|
||||
#include <torch/extension.h>
|
||||
#include <vector>
|
||||
|
||||
namespace anti_alias_activation {
|
||||
|
||||
torch::Tensor fwd_cuda(torch::Tensor const& input,
|
||||
torch::Tensor const& filter,
|
||||
torch::Tensor const& alpha,
|
||||
torch::Tensor const& beta
|
||||
);
|
||||
|
||||
torch::Tensor fwd(torch::Tensor const& input,
|
||||
torch::Tensor const& filter,
|
||||
torch::Tensor const& alpha,
|
||||
torch::Tensor const& beta
|
||||
) {
|
||||
AT_ASSERTM(input.dim() == 3, "expected 3D tensor");
|
||||
//AT_ASSERTM((input.scalar_type() == at::ScalarType::Half) ||
|
||||
// (input.scalar_type() == at::ScalarType::BFloat16),
|
||||
// "Only fp16 and bf16 are supported");
|
||||
|
||||
return fwd_cuda(input, filter, alpha, beta);
|
||||
}
|
||||
|
||||
} // end namespace anti_alias_activation
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("forward",
|
||||
&anti_alias_activation::fwd,
|
||||
"Anti Alias Activation -- Forward.");
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/* coding=utf-8
|
||||
* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_profiler_api.h>
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
#include <torch/extension.h>
|
||||
#include "type_shim.h"
|
||||
#include <assert.h>
|
||||
#include <cfloat>
|
||||
#include <limits>
|
||||
#include <stdint.h>
|
||||
#include <c10/macros/Macros.h>
|
||||
|
||||
namespace {
|
||||
|
||||
/*
|
||||
template <typename Datatype, int ELEMENTS_PER_LDG>
|
||||
__device__ __inline__ void copy_vector(Datatype *dst, const Datatype *src);
|
||||
|
||||
template <>
|
||||
__device__ __inline__ void copy_vector<c10::BFloat16, 1>(c10::BFloat16 *dst, const c10::BFloat16 *src) { *dst = *src; }
|
||||
|
||||
template <>
|
||||
__device__ __inline__ void copy_vector<c10::BFloat16, 4>(c10::BFloat16 *dst, const c10::BFloat16 *src) { *((float2*) dst) = *((float2*) src); }
|
||||
|
||||
template <>
|
||||
__device__ __inline__ void copy_vector<c10::Half, 1>(c10::Half *dst, const c10::Half *src) { *dst = *src; }
|
||||
|
||||
template <>
|
||||
__device__ __inline__ void copy_vector<c10::Half, 4>(c10::Half *dst, const c10::Half *src) { *((float2*) dst) = *((float2*) src); }
|
||||
|
||||
template <>
|
||||
__device__ __inline__ void copy_vector<uint8_t, 1>(uint8_t *dst, const uint8_t *src) { *dst = *src; }
|
||||
|
||||
template <>
|
||||
__device__ __inline__ void copy_vector<uint8_t, 4>(uint8_t *dst, const uint8_t *src) {*((half2*) dst) = *((half2*) src); }
|
||||
|
||||
int log2_ceil(int value) {
|
||||
int log2_value = 0;
|
||||
while ((1 << log2_value) < value) ++log2_value;
|
||||
return log2_value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct Add {
|
||||
__device__ __forceinline__ T operator()(T a, T b) const {
|
||||
return a + b;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Max {
|
||||
__device__ __forceinline__ T operator()(T a, T b) const {
|
||||
return a < b ? b : a;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
__device__ __forceinline__ T WARP_SHFL_XOR_NATIVE(T value, int laneMask, int width = warpSize, unsigned int mask = 0xffffffff)
|
||||
{
|
||||
#if CUDA_VERSION >= 9000
|
||||
return __shfl_xor_sync(mask, value, laneMask, width);
|
||||
#else
|
||||
return __shfl_xor(value, laneMask, width);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename acc_t, int WARP_BATCH, int WARP_SIZE, template<typename> class ReduceOp>
|
||||
__device__ __forceinline__ void warp_reduce(acc_t* sum) {
|
||||
ReduceOp<acc_t> r;
|
||||
#pragma unroll
|
||||
for (int offset = WARP_SIZE / 2; offset > 0; offset /= 2) {
|
||||
#pragma unroll
|
||||
for (int i = 0; i < WARP_BATCH; ++i) {
|
||||
acc_t b = WARP_SHFL_XOR_NATIVE(sum[i], offset, WARP_SIZE);
|
||||
sum[i] = r(sum[i], b);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
template <typename input_t, typename output_t, typename acc_t>
|
||||
__global__ void anti_alias_activation_forward(
|
||||
output_t *dst,
|
||||
const input_t *src,
|
||||
const input_t *ftr,
|
||||
const input_t *alpha,
|
||||
const input_t *beta,
|
||||
int batch_size,
|
||||
int channels,
|
||||
int seq_len)
|
||||
{
|
||||
// WARP_SIZE and WARP_BATCH must match the return values batches_per_warp and
|
||||
constexpr int ELEMENTS_PER_LDG_STG = 1; //(WARP_ITERATIONS < 4) ? 1 : 4;
|
||||
constexpr int BUFFER_SIZE = 32;
|
||||
constexpr int FILTER_SIZE = 12;
|
||||
constexpr int HALF_FILTER_SIZE = 6;
|
||||
constexpr int REPLICATION_PAD = 5; // 5 on each side
|
||||
|
||||
// blockDim/threadIdx = (128, 1, 1)
|
||||
// gridDim/blockIdx = (seq_blocks, channels, batches)
|
||||
int block_offset = (blockIdx.x * 128 * BUFFER_SIZE + seq_len * (blockIdx.y + gridDim.y * blockIdx.z));
|
||||
int local_offset = threadIdx.x * BUFFER_SIZE;
|
||||
int seq_offset = blockIdx.x * 128 * BUFFER_SIZE + local_offset;
|
||||
|
||||
|
||||
//int intermediate_seq_len = seq_len * 2 - 1 + 4 * REPLICATION_PAD;
|
||||
//int intermediate_block_offset = (blockIdx.x * 128 * BUFFER_SIZE * 2 + intermediate_seq_len * (blockIdx.y + gridDim.y * blockIdx.z));
|
||||
//int intermediate_local_offset = threadIdx.x * BUFFER_SIZE * 2;
|
||||
|
||||
int output_seq_len = seq_len * 2 ; //
|
||||
int output_block_offset = (blockIdx.x * 128 * BUFFER_SIZE * 2 + output_seq_len * (blockIdx.y + gridDim.y * blockIdx.z));
|
||||
int output_local_offset = threadIdx.x * BUFFER_SIZE * 2;
|
||||
int output_seq_offset = blockIdx.x * 128 * BUFFER_SIZE *2 + output_local_offset;
|
||||
// get values needed for replication padding before moving pointer
|
||||
const input_t *right_most_pntr = src + (seq_len * (blockIdx.y + gridDim.y * blockIdx.z));
|
||||
input_t seq_left_most_value = right_most_pntr[0];
|
||||
input_t seq_right_most_value = right_most_pntr[seq_len - 1];
|
||||
|
||||
src += block_offset + local_offset;
|
||||
dst += output_block_offset + output_local_offset ;
|
||||
alpha = alpha + blockIdx.y;
|
||||
input_t alpha_val = expf(alpha[0]);
|
||||
beta = beta + blockIdx.y;
|
||||
input_t beta_val = expf(beta[0]);
|
||||
// load data from global memory
|
||||
input_t elements[2*FILTER_SIZE+2*BUFFER_SIZE] = {0};
|
||||
input_t intermediates[2*FILTER_SIZE+2*BUFFER_SIZE] = {0};
|
||||
//output_t output[2*BUFFER_SIZE];
|
||||
input_t filter[FILTER_SIZE];
|
||||
//input_t temp_data[ELEMENTS_PER_LDG_STG];
|
||||
//uint8_t temp_mask[ELEMENTS_PER_LDG_STG];
|
||||
|
||||
#pragma unroll
|
||||
for (int it = 0; it < FILTER_SIZE; it+=1) {
|
||||
filter[it] = ftr[it];
|
||||
}
|
||||
|
||||
|
||||
#pragma unroll
|
||||
for (int it = -HALF_FILTER_SIZE; it < BUFFER_SIZE + HALF_FILTER_SIZE ; it+=1) {
|
||||
int element_index = seq_offset + it;
|
||||
if ((element_index < 0) && (element_index >= -REPLICATION_PAD)) {
|
||||
elements[2*(HALF_FILTER_SIZE+it)] = 2*seq_left_most_value;
|
||||
}
|
||||
if ((element_index >= seq_len) && (element_index < seq_len + REPLICATION_PAD)) {
|
||||
elements[2*(HALF_FILTER_SIZE+it)] = 2*seq_right_most_value;
|
||||
}
|
||||
if ((element_index >= 0) && (element_index < seq_len)) {
|
||||
elements[2*(HALF_FILTER_SIZE+it)] = 2*src[it];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// apply filter
|
||||
#pragma unroll
|
||||
for (int it = 0; it < (2 * BUFFER_SIZE + 2*FILTER_SIZE); it+=1) {
|
||||
input_t acc = 0.0;
|
||||
|
||||
int element_index = output_seq_offset + it; // index for output
|
||||
#pragma unroll
|
||||
for (int f_idx = 0; f_idx < FILTER_SIZE; f_idx+=1){
|
||||
if ((element_index + f_idx) >= 0){
|
||||
acc += filter[f_idx] * elements[it+f_idx];
|
||||
}
|
||||
}
|
||||
intermediates[it] = acc;
|
||||
}
|
||||
|
||||
double no_div_by_zero = 0.000000001;
|
||||
#pragma unroll
|
||||
for (int it = 0; it < 12 + 2 * BUFFER_SIZE; it++) {
|
||||
intermediates[it] += (1.0/(beta_val + no_div_by_zero)) * sinf(intermediates[it] * alpha_val) * sinf(intermediates[it] * alpha_val);
|
||||
}
|
||||
|
||||
|
||||
// now copy to output
|
||||
#pragma unroll
|
||||
for (int it = 0; it < 2*BUFFER_SIZE; it+=1){
|
||||
int element_index = output_seq_offset + it;
|
||||
if (element_index < output_seq_len) {
|
||||
dst[it] = intermediates[it+6];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// for (int it = 0; it < BUFFER_SIZE; it+=ELEMENTS_PER_LDG_STG) {
|
||||
// int element_index = seq_offset + it;
|
||||
// if (element_index < seq_len) {
|
||||
// dst[it] = output[it];
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// // Upsample convolution
|
||||
// for (int it = 0; it < 2 * BUFFER_SIZE + 12; it+=1) {
|
||||
// input_t acc = 0.0;
|
||||
|
||||
// for (int f_idx = 0; f_idx < FILTER_SIZE; f_idx+=1){
|
||||
// acc += filter[f_idx] * elements[it+f_idx];
|
||||
// }
|
||||
// intermediates[it] = acc;
|
||||
// }
|
||||
|
||||
// // correct the corners of intermediates
|
||||
// if (seq_offset == 0) {
|
||||
// for (int it = 0; it < 6; it+=1)
|
||||
// intermediates[it] = 0;
|
||||
// }
|
||||
|
||||
// if (seq_offset + 32 >= seq_len) {
|
||||
// int offset = seq_len % 32 == 0 ? 32 : seq_len % 32;
|
||||
|
||||
// for (int it = 0; it < 6; it++) {
|
||||
// intermediates[6+2*offset+it] = 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// for (int it = 0; it < BUFFER_SIZE; it+=ELEMENTS_PER_LDG_STG) {
|
||||
// int element_index = seq_offset + it;
|
||||
// if (element_index < seq_len) {
|
||||
// dst[it] = output[it];
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
template<typename input_t, typename output_t, typename acc_t>
|
||||
void dispatch_anti_alias_activation_forward(
|
||||
output_t *dst,
|
||||
const input_t *src,
|
||||
const input_t *ftr,
|
||||
const input_t *alpha,
|
||||
const input_t *beta,
|
||||
int batch_size,
|
||||
int channels,
|
||||
int seq_len)
|
||||
{
|
||||
if (seq_len == 0) {
|
||||
return;
|
||||
} else {
|
||||
// use 128 threads per block to maximimize gpu utilization
|
||||
constexpr int threads_per_block = 128;
|
||||
constexpr int seq_len_per_block = 4096;
|
||||
int blocks_per_seq_len = (seq_len + seq_len_per_block - 1) / seq_len_per_block;
|
||||
dim3 blocks(blocks_per_seq_len, channels, batch_size);
|
||||
dim3 threads(threads_per_block, 1, 1);
|
||||
|
||||
anti_alias_activation_forward<input_t, output_t, acc_t>
|
||||
<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(dst, src, ftr, alpha, beta, batch_size, channels, seq_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace anti_alias_activation {
|
||||
|
||||
torch::Tensor fwd_cuda(torch::Tensor const& input, torch::Tensor const& filter, torch::Tensor const& alpha, torch::Tensor const& beta)
|
||||
{
|
||||
// input is a 4d tensor with dimensions [batches, attn_heads, seq_len, seq_len]
|
||||
const int batches = input.size(0);
|
||||
const int channels = input.size(1);
|
||||
const int seq_len = input.size(2);
|
||||
|
||||
// Output
|
||||
auto act_options = input.options().requires_grad(false);
|
||||
int output_seq_len = seq_len*2; // we'll be dilating between each element by interspersing with zeros
|
||||
|
||||
torch::Tensor anti_alias_activation_results =
|
||||
torch::empty({batches, channels, output_seq_len}, act_options);
|
||||
|
||||
// Softmax Intermediate Result Ptr
|
||||
void* input_ptr = static_cast<void*>(input.data_ptr());
|
||||
void* filter_ptr = static_cast<void*>(filter.data_ptr());
|
||||
void* alpha_ptr = static_cast<void*>(alpha.data_ptr());
|
||||
void* beta_ptr = static_cast<void*>(beta.data_ptr());
|
||||
void* anti_alias_activation_results_ptr = static_cast<void*>(anti_alias_activation_results.data_ptr());
|
||||
|
||||
DISPATCH_FLOAT_HALF_AND_BFLOAT(
|
||||
input.scalar_type(),
|
||||
"dispatch anti alias activation_forward",
|
||||
dispatch_anti_alias_activation_forward<scalar_t, scalar_t, float>(
|
||||
reinterpret_cast<scalar_t*>(anti_alias_activation_results_ptr),
|
||||
reinterpret_cast<const scalar_t*>(input_ptr),
|
||||
reinterpret_cast<const scalar_t*>(filter_ptr),
|
||||
reinterpret_cast<const scalar_t*>(alpha_ptr),
|
||||
reinterpret_cast<const scalar_t*>(beta_ptr),
|
||||
batches,
|
||||
channels,
|
||||
seq_len);
|
||||
);
|
||||
return anti_alias_activation_results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* coding=utf-8
|
||||
* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*This code is copied fron NVIDIA apex:
|
||||
* https://github.com/NVIDIA/apex
|
||||
* with minor changes. */
|
||||
|
||||
|
||||
|
||||
#ifndef TORCH_CHECK
|
||||
#define TORCH_CHECK AT_CHECK
|
||||
#endif
|
||||
|
||||
#ifdef VERSION_GE_1_3
|
||||
#define DATA_PTR data_ptr
|
||||
#else
|
||||
#define DATA_PTR data
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
# Copyright (c) 2024 NVIDIA CORPORATION.
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
from torch.utils import cpp_extension
|
||||
|
||||
# Setting this param to a list has a problem of generating different
|
||||
# compilation commands (with diferent order of architectures) and
|
||||
# leading to recompilation of fused kernels. Set it to empty string
|
||||
# to avoid recompilation and assign arch flags explicity in
|
||||
# extra_cuda_cflags below
|
||||
os.environ["TORCH_CUDA_ARCH_LIST"] = ""
|
||||
|
||||
|
||||
def load():
|
||||
# Check if cuda 11 is installed for compute capability 8.0
|
||||
cc_flag = []
|
||||
_, bare_metal_major, _ = _get_cuda_bare_metal_version(cpp_extension.CUDA_HOME)
|
||||
if int(bare_metal_major) >= 11:
|
||||
cc_flag.append("-gencode")
|
||||
cc_flag.append("arch=compute_80,code=sm_80")
|
||||
|
||||
# Build path
|
||||
srcpath = pathlib.Path(__file__).parent.absolute()
|
||||
buildpath = srcpath / "build"
|
||||
_create_build_dir(buildpath)
|
||||
|
||||
# Helper function to build the kernels.
|
||||
def _cpp_extention_load_helper(name, sources, extra_cuda_flags):
|
||||
return cpp_extension.load(
|
||||
name=name,
|
||||
sources=sources,
|
||||
build_directory=buildpath,
|
||||
extra_cflags=[
|
||||
"-O3",
|
||||
],
|
||||
extra_cuda_cflags=[
|
||||
"-O3",
|
||||
"-gencode",
|
||||
"arch=compute_70,code=sm_70",
|
||||
"--use_fast_math",
|
||||
]
|
||||
+ extra_cuda_flags
|
||||
+ cc_flag,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
extra_cuda_flags = [
|
||||
"-U__CUDA_NO_HALF_OPERATORS__",
|
||||
"-U__CUDA_NO_HALF_CONVERSIONS__",
|
||||
"--expt-relaxed-constexpr",
|
||||
"--expt-extended-lambda",
|
||||
]
|
||||
|
||||
sources = [
|
||||
srcpath / "anti_alias_activation.cpp",
|
||||
srcpath / "anti_alias_activation_cuda.cu",
|
||||
]
|
||||
anti_alias_activation_cuda = _cpp_extention_load_helper(
|
||||
"anti_alias_activation_cuda", sources, extra_cuda_flags
|
||||
)
|
||||
|
||||
|
||||
def _get_cuda_bare_metal_version(cuda_dir):
|
||||
raw_output = subprocess.check_output(
|
||||
[cuda_dir + "/bin/nvcc", "-V"], universal_newlines=True
|
||||
)
|
||||
output = raw_output.split()
|
||||
release_idx = output.index("release") + 1
|
||||
release = output[release_idx].split(".")
|
||||
bare_metal_major = release[0]
|
||||
bare_metal_minor = release[1][0]
|
||||
|
||||
return raw_output, bare_metal_major, bare_metal_minor
|
||||
|
||||
|
||||
def _create_build_dir(buildpath):
|
||||
try:
|
||||
os.mkdir(buildpath)
|
||||
except OSError:
|
||||
if not os.path.isdir(buildpath):
|
||||
print(f"Creation of the build directory {buildpath} failed")
|
||||
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) 2024 NVIDIA CORPORATION.
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import math
|
||||
import torch
|
||||
import alias_free_cuda
|
||||
from alias_free_cuda import activation1d
|
||||
from activations import Snake, SnakeBeta
|
||||
|
||||
|
||||
def test_load_fused_kernels():
|
||||
try:
|
||||
import alias_free_cuda
|
||||
import torch
|
||||
|
||||
print("[Success] load_fused_kernels")
|
||||
except ImportError as e:
|
||||
print("[Fail] load_fused_kernels")
|
||||
raise e
|
||||
|
||||
|
||||
def test_anti_alias_activation():
|
||||
data = torch.rand((10, 10, 50000), device="cuda")
|
||||
|
||||
# check activations.Snake cuda vs. torch
|
||||
fused_anti_alias_activation = activation1d.Activation1d(
|
||||
activation=Snake(10), fused=True
|
||||
).cuda()
|
||||
fused_activation_output = fused_anti_alias_activation(data)
|
||||
|
||||
torch_anti_alias_activation = activation1d.Activation1d(
|
||||
activation=Snake(10), fused=False
|
||||
).cuda()
|
||||
torch_activation_output = torch_anti_alias_activation(data)
|
||||
|
||||
test_result = (fused_activation_output - torch_activation_output).abs()
|
||||
|
||||
while test_result.dim() != 1:
|
||||
test_result = test_result.mean(dim=-1)
|
||||
|
||||
diff = test_result.mean(dim=-1)
|
||||
|
||||
if diff <= 1e-3:
|
||||
print(
|
||||
f"\n[Success] test_fused_anti_alias_activation"
|
||||
f"\n > mean_difference={diff}"
|
||||
f"\n > fused_values={fused_activation_output[-1][-1][-100:].tolist()}"
|
||||
f"\n > torch_values={torch_activation_output[-1][-1][-100:].tolist()}"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"\n[Fail] test_fused_anti_alias_activation"
|
||||
f"\n > mean_difference={diff}, "
|
||||
f"\n > fused_values={fused_activation_output[-1][-1][-30:].tolist()}, "
|
||||
f"\n > torch_values={torch_activation_output[-1][-1][-30:].tolist()}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from alias_free_cuda import load
|
||||
|
||||
load.load()
|
||||
test_load_fused_kernels()
|
||||
test_anti_alias_activation()
|
||||
@@ -0,0 +1,64 @@
|
||||
# Copyright (c) 2024 NVIDIA CORPORATION.
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import math
|
||||
import torch
|
||||
import alias_free_cuda
|
||||
from alias_free_cuda import activation1d
|
||||
from activations import Snake, SnakeBeta
|
||||
|
||||
|
||||
def test_load_fused_kernels():
|
||||
try:
|
||||
import alias_free_cuda
|
||||
import torch
|
||||
|
||||
print("[Success] load_fused_kernels")
|
||||
except ImportError as e:
|
||||
print("[Fail] load_fused_kernels")
|
||||
raise e
|
||||
|
||||
|
||||
def test_anti_alias_activation():
|
||||
data = torch.rand((10, 10, 50000), device="cuda")
|
||||
|
||||
# check activations.Snake cuda vs. torch
|
||||
fused_anti_alias_activation = activation1d.Activation1d(
|
||||
activation=SnakeBeta(10), fused=True
|
||||
).cuda()
|
||||
fused_activation_output = fused_anti_alias_activation(data)
|
||||
|
||||
torch_anti_alias_activation = activation1d.Activation1d(
|
||||
activation=SnakeBeta(10), fused=False
|
||||
).cuda()
|
||||
torch_activation_output = torch_anti_alias_activation(data)
|
||||
|
||||
test_result = (fused_activation_output - torch_activation_output).abs()
|
||||
|
||||
while test_result.dim() != 1:
|
||||
test_result = test_result.mean(dim=-1)
|
||||
|
||||
diff = test_result.mean(dim=-1)
|
||||
|
||||
if diff <= 1e-3:
|
||||
print(
|
||||
f"\n[Success] test_fused_anti_alias_activation"
|
||||
f"\n > mean_difference={diff}"
|
||||
f"\n > fused_values={fused_activation_output[-1][-1][-100:].tolist()}"
|
||||
f"\n > torch_values={torch_activation_output[-1][-1][-100:].tolist()}"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"\n[Fail] test_fused_anti_alias_activation"
|
||||
f"\n > mean_difference={diff}, "
|
||||
f"\n > fused_values={fused_activation_output[-1][-1][-30:].tolist()}, "
|
||||
f"\n > torch_values={torch_activation_output[-1][-1][-30:].tolist()}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from alias_free_cuda import load
|
||||
|
||||
load.load()
|
||||
test_load_fused_kernels()
|
||||
test_anti_alias_activation()
|
||||
@@ -0,0 +1,97 @@
|
||||
/* coding=utf-8
|
||||
* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include "compat.h"
|
||||
|
||||
|
||||
#define DISPATCH_FLOAT_HALF_AND_BFLOAT(TYPE, NAME, ...) \
|
||||
switch(TYPE) \
|
||||
{ \
|
||||
case at::ScalarType::Float: \
|
||||
{ \
|
||||
using scalar_t = float; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
case at::ScalarType::Half: \
|
||||
{ \
|
||||
using scalar_t = at::Half; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
case at::ScalarType::BFloat16: \
|
||||
{ \
|
||||
using scalar_t = at::BFloat16; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
default: \
|
||||
AT_ERROR(#NAME, " not implemented for '", toString(TYPE), "'"); \
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define DISPATCH_FLOAT_HALF_AND_BFLOAT_INOUT_TYPES(TYPEIN, TYPEOUT, NAME, ...) \
|
||||
switch(TYPEIN) \
|
||||
{ \
|
||||
case at::ScalarType::Float: \
|
||||
{ \
|
||||
using scalar_t_in = float; \
|
||||
switch(TYPEOUT) \
|
||||
{ \
|
||||
case at::ScalarType::Float: \
|
||||
{ \
|
||||
using scalar_t_out = float; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
case at::ScalarType::Half: \
|
||||
{ \
|
||||
using scalar_t_out = at::Half; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
case at::ScalarType::BFloat16: \
|
||||
{ \
|
||||
using scalar_t_out = at::BFloat16; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
default: \
|
||||
AT_ERROR(#NAME, " not implemented for '", toString(TYPEOUT), "'"); \
|
||||
} \
|
||||
break; \
|
||||
} \
|
||||
case at::ScalarType::Half: \
|
||||
{ \
|
||||
using scalar_t_in = at::Half; \
|
||||
using scalar_t_out = at::Half; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
case at::ScalarType::BFloat16: \
|
||||
{ \
|
||||
using scalar_t_in = at::BFloat16; \
|
||||
using scalar_t_out = at::BFloat16; \
|
||||
__VA_ARGS__; \
|
||||
break; \
|
||||
} \
|
||||
default: \
|
||||
AT_ERROR(#NAME, " not implemented for '", toString(TYPEIN), "'"); \
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
|
||||
|
||||
from .filter import *
|
||||
from .resample import *
|
||||
from .act import *
|
||||
@@ -0,0 +1,29 @@
|
||||
# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
|
||||
|
||||
import torch.nn as nn
|
||||
from .resample import UpSample1d, DownSample1d
|
||||
|
||||
|
||||
class Activation1d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
activation,
|
||||
up_ratio: int = 2,
|
||||
down_ratio: int = 2,
|
||||
up_kernel_size: int = 12,
|
||||
down_kernel_size: int = 12,
|
||||
):
|
||||
super().__init__()
|
||||
self.up_ratio = up_ratio
|
||||
self.down_ratio = down_ratio
|
||||
self.act = activation
|
||||
self.upsample = UpSample1d(up_ratio, up_kernel_size)
|
||||
self.downsample = DownSample1d(down_ratio, down_kernel_size)
|
||||
|
||||
# x: [B,C,T]
|
||||
def forward(self, x):
|
||||
x = self.upsample(x)
|
||||
x = self.act(x)
|
||||
x = self.downsample(x)
|
||||
|
||||
return x
|
||||
@@ -0,0 +1,98 @@
|
||||
# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import math
|
||||
|
||||
if "sinc" in dir(torch):
|
||||
sinc = torch.sinc
|
||||
else:
|
||||
# This code is adopted from adefossez's julius.core.sinc under the MIT License
|
||||
# https://adefossez.github.io/julius/julius/core.html
|
||||
# LICENSE is in incl_licenses directory.
|
||||
def sinc(x: torch.Tensor):
|
||||
"""
|
||||
Implementation of sinc, i.e. sin(pi * x) / (pi * x)
|
||||
__Warning__: Different to julius.sinc, the input is multiplied by `pi`!
|
||||
"""
|
||||
return torch.where(
|
||||
x == 0,
|
||||
torch.tensor(1.0, device=x.device, dtype=x.dtype),
|
||||
torch.sin(math.pi * x) / math.pi / x,
|
||||
)
|
||||
|
||||
|
||||
# This code is adopted from adefossez's julius.lowpass.LowPassFilters under the MIT License
|
||||
# https://adefossez.github.io/julius/julius/lowpass.html
|
||||
# LICENSE is in incl_licenses directory.
|
||||
def kaiser_sinc_filter1d(
|
||||
cutoff, half_width, kernel_size
|
||||
): # return filter [1,1,kernel_size]
|
||||
even = kernel_size % 2 == 0
|
||||
half_size = kernel_size // 2
|
||||
|
||||
# For kaiser window
|
||||
delta_f = 4 * half_width
|
||||
A = 2.285 * (half_size - 1) * math.pi * delta_f + 7.95
|
||||
if A > 50.0:
|
||||
beta = 0.1102 * (A - 8.7)
|
||||
elif A >= 21.0:
|
||||
beta = 0.5842 * (A - 21) ** 0.4 + 0.07886 * (A - 21.0)
|
||||
else:
|
||||
beta = 0.0
|
||||
window = torch.kaiser_window(kernel_size, beta=beta, periodic=False)
|
||||
|
||||
# ratio = 0.5/cutoff -> 2 * cutoff = 1 / ratio
|
||||
if even:
|
||||
time = torch.arange(-half_size, half_size) + 0.5
|
||||
else:
|
||||
time = torch.arange(kernel_size) - half_size
|
||||
if cutoff == 0:
|
||||
filter_ = torch.zeros_like(time)
|
||||
else:
|
||||
filter_ = 2 * cutoff * window * sinc(2 * cutoff * time)
|
||||
# Normalize filter to have sum = 1, otherwise we will have a small leakage
|
||||
# of the constant component in the input signal.
|
||||
filter_ /= filter_.sum()
|
||||
filter = filter_.view(1, 1, kernel_size)
|
||||
|
||||
return filter
|
||||
|
||||
|
||||
class LowPassFilter1d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
cutoff=0.5,
|
||||
half_width=0.6,
|
||||
stride: int = 1,
|
||||
padding: bool = True,
|
||||
padding_mode: str = "replicate",
|
||||
kernel_size: int = 12,
|
||||
):
|
||||
# kernel_size should be even number for stylegan3 setup,
|
||||
# in this implementation, odd number is also possible.
|
||||
super().__init__()
|
||||
if cutoff < -0.0:
|
||||
raise ValueError("Minimum cutoff must be larger than zero.")
|
||||
if cutoff > 0.5:
|
||||
raise ValueError("A cutoff above 0.5 does not make sense.")
|
||||
self.kernel_size = kernel_size
|
||||
self.even = kernel_size % 2 == 0
|
||||
self.pad_left = kernel_size // 2 - int(self.even)
|
||||
self.pad_right = kernel_size // 2
|
||||
self.stride = stride
|
||||
self.padding = padding
|
||||
self.padding_mode = padding_mode
|
||||
filter = kaiser_sinc_filter1d(cutoff, half_width, kernel_size)
|
||||
self.register_buffer("filter", filter)
|
||||
|
||||
# input [B, C, T]
|
||||
def forward(self, x):
|
||||
_, C, _ = x.shape
|
||||
|
||||
if self.padding:
|
||||
x = F.pad(x, (self.pad_left, self.pad_right), mode=self.padding_mode)
|
||||
out = F.conv1d(x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C)
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,57 @@
|
||||
# Adapted from https://github.com/junjun3518/alias-free-torch under the Apache License 2.0
|
||||
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
from .filter import LowPassFilter1d
|
||||
from .filter import kaiser_sinc_filter1d
|
||||
|
||||
|
||||
class UpSample1d(nn.Module):
|
||||
def __init__(self, ratio=2, kernel_size=None):
|
||||
super().__init__()
|
||||
self.ratio = ratio
|
||||
self.kernel_size = (
|
||||
int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size
|
||||
)
|
||||
self.stride = ratio
|
||||
self.pad = self.kernel_size // ratio - 1
|
||||
self.pad_left = self.pad * self.stride + (self.kernel_size - self.stride) // 2
|
||||
self.pad_right = (
|
||||
self.pad * self.stride + (self.kernel_size - self.stride + 1) // 2
|
||||
)
|
||||
filter = kaiser_sinc_filter1d(
|
||||
cutoff=0.5 / ratio, half_width=0.6 / ratio, kernel_size=self.kernel_size
|
||||
)
|
||||
self.register_buffer("filter", filter)
|
||||
|
||||
# x: [B, C, T]
|
||||
def forward(self, x):
|
||||
_, C, _ = x.shape
|
||||
|
||||
x = F.pad(x, (self.pad, self.pad), mode="replicate")
|
||||
x = self.ratio * F.conv_transpose1d(
|
||||
x, self.filter.expand(C, -1, -1), stride=self.stride, groups=C
|
||||
)
|
||||
x = x[..., self.pad_left : -self.pad_right]
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class DownSample1d(nn.Module):
|
||||
def __init__(self, ratio=2, kernel_size=None):
|
||||
super().__init__()
|
||||
self.ratio = ratio
|
||||
self.kernel_size = (
|
||||
int(6 * ratio // 2) * 2 if kernel_size is None else kernel_size
|
||||
)
|
||||
self.lowpass = LowPassFilter1d(
|
||||
cutoff=0.5 / ratio,
|
||||
half_width=0.6 / ratio,
|
||||
stride=ratio,
|
||||
kernel_size=self.kernel_size,
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
xx = self.lowpass(x)
|
||||
|
||||
return xx
|
||||
@@ -0,0 +1,399 @@
|
||||
import typing as tp
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn import Conv1d, ConvTranspose1d
|
||||
from torch.nn.utils import weight_norm, remove_weight_norm
|
||||
|
||||
from fireredtts.modules.bigvgan.alias_free_torch import (
|
||||
Activation1d as TorchActivation1d,
|
||||
)
|
||||
from fireredtts.modules.bigvgan.activations import Snake, SnakeBeta
|
||||
|
||||
|
||||
def init_weights(m, mean=0.0, std=0.01):
|
||||
classname = m.__class__.__name__
|
||||
if classname.find("Conv") != -1:
|
||||
m.weight.data.normal_(mean, std)
|
||||
|
||||
|
||||
def get_padding(kernel_size, dilation=1):
|
||||
return int((kernel_size * dilation - dilation) / 2)
|
||||
|
||||
|
||||
class AMPBlock1(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels,
|
||||
kernel_size=3,
|
||||
dilation=(1, 3, 5),
|
||||
activation=None,
|
||||
snake_logscale=True,
|
||||
use_cuda_kernel=False,
|
||||
):
|
||||
super(AMPBlock1, self).__init__()
|
||||
|
||||
self.convs1 = nn.ModuleList(
|
||||
[
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=dilation[0],
|
||||
padding=get_padding(kernel_size, dilation[0]),
|
||||
)
|
||||
),
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=dilation[1],
|
||||
padding=get_padding(kernel_size, dilation[1]),
|
||||
)
|
||||
),
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=dilation[2],
|
||||
padding=get_padding(kernel_size, dilation[2]),
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
self.convs1.apply(init_weights)
|
||||
|
||||
self.convs2 = nn.ModuleList(
|
||||
[
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=1,
|
||||
padding=get_padding(kernel_size, 1),
|
||||
)
|
||||
),
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=1,
|
||||
padding=get_padding(kernel_size, 1),
|
||||
)
|
||||
),
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=1,
|
||||
padding=get_padding(kernel_size, 1),
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
self.convs2.apply(init_weights)
|
||||
|
||||
self.num_layers = len(self.convs1) + len(
|
||||
self.convs2
|
||||
) # total number of conv layers
|
||||
|
||||
# select which Activation1d, lazy-load cuda version to ensure backward compatibility
|
||||
if use_cuda_kernel:
|
||||
from modules.bigvgan.alias_free_cuda.activation1d import (
|
||||
Activation1d as CudaActivation1d,
|
||||
)
|
||||
|
||||
Activation1d = CudaActivation1d
|
||||
else:
|
||||
Activation1d = TorchActivation1d
|
||||
|
||||
if (
|
||||
activation == "snake"
|
||||
): # periodic nonlinearity with snake function and anti-aliasing
|
||||
self.activations = nn.ModuleList(
|
||||
[
|
||||
Activation1d(
|
||||
activation=Snake(channels, alpha_logscale=snake_logscale)
|
||||
)
|
||||
for _ in range(self.num_layers)
|
||||
]
|
||||
)
|
||||
elif (
|
||||
activation == "snakebeta"
|
||||
): # periodic nonlinearity with snakebeta function and anti-aliasing
|
||||
self.activations = nn.ModuleList(
|
||||
[
|
||||
Activation1d(
|
||||
activation=SnakeBeta(channels, alpha_logscale=snake_logscale)
|
||||
)
|
||||
for _ in range(self.num_layers)
|
||||
]
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"activation incorrectly specified. check the config file and look for 'activation'."
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
acts1, acts2 = self.activations[::2], self.activations[1::2]
|
||||
for c1, c2, a1, a2 in zip(self.convs1, self.convs2, acts1, acts2):
|
||||
xt = a1(x)
|
||||
xt = c1(xt)
|
||||
xt = a2(xt)
|
||||
xt = c2(xt)
|
||||
x = xt + x
|
||||
|
||||
return x
|
||||
|
||||
def remove_weight_norm(self):
|
||||
for l in self.convs1:
|
||||
remove_weight_norm(l)
|
||||
for l in self.convs2:
|
||||
remove_weight_norm(l)
|
||||
|
||||
|
||||
class AMPBlock2(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels,
|
||||
kernel_size=3,
|
||||
dilation=(1, 3),
|
||||
activation=None,
|
||||
snake_logscale=True,
|
||||
use_cuda_kernel=False,
|
||||
):
|
||||
super(AMPBlock2, self).__init__()
|
||||
|
||||
self.convs = nn.ModuleList(
|
||||
[
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=dilation[0],
|
||||
padding=get_padding(kernel_size, dilation[0]),
|
||||
)
|
||||
),
|
||||
weight_norm(
|
||||
Conv1d(
|
||||
channels,
|
||||
channels,
|
||||
kernel_size,
|
||||
1,
|
||||
dilation=dilation[1],
|
||||
padding=get_padding(kernel_size, dilation[1]),
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
self.convs.apply(init_weights)
|
||||
|
||||
self.num_layers = len(self.convs) # total number of conv layers
|
||||
|
||||
# select which Activation1d, lazy-load cuda version to ensure backward compatibility
|
||||
if use_cuda_kernel:
|
||||
from modules.bigvgan.alias_free_cuda.activation1d import (
|
||||
Activation1d as CudaActivation1d,
|
||||
)
|
||||
|
||||
Activation1d = CudaActivation1d
|
||||
else:
|
||||
Activation1d = TorchActivation1d
|
||||
|
||||
if (
|
||||
activation == "snake"
|
||||
): # periodic nonlinearity with snake function and anti-aliasing
|
||||
self.activations = nn.ModuleList(
|
||||
[
|
||||
Activation1d(
|
||||
activation=Snake(channels, alpha_logscale=snake_logscale)
|
||||
)
|
||||
for _ in range(self.num_layers)
|
||||
]
|
||||
)
|
||||
elif (
|
||||
activation == "snakebeta"
|
||||
): # periodic nonlinearity with snakebeta function and anti-aliasing
|
||||
self.activations = nn.ModuleList(
|
||||
[
|
||||
Activation1d(
|
||||
activation=SnakeBeta(channels, alpha_logscale=snake_logscale)
|
||||
)
|
||||
for _ in range(self.num_layers)
|
||||
]
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"activation incorrectly specified. check the config file and look for 'activation'."
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
for c, a in zip(self.convs, self.activations):
|
||||
xt = a(x)
|
||||
xt = c(xt)
|
||||
x = xt + x
|
||||
|
||||
return x
|
||||
|
||||
def remove_weight_norm(self):
|
||||
for l in self.convs:
|
||||
remove_weight_norm(l)
|
||||
|
||||
|
||||
class BigVGAN(torch.nn.Module):
|
||||
# this is our main BigVGAN model. Applies anti-aliased periodic activation for resblocks.
|
||||
def __init__(
|
||||
self,
|
||||
num_mels: int,
|
||||
upsample_initial_channel: int,
|
||||
resblock_kernel_sizes: tp.List[int],
|
||||
resblock_dilation_sizes: tp.List[tp.List[int]],
|
||||
upsample_rates: tp.List[int],
|
||||
upsample_kernel_sizes: tp.List[int],
|
||||
resblock_type: str = "1",
|
||||
snake_logscale: bool = True,
|
||||
activation: str = "snakebeta",
|
||||
use_tanh_at_final: bool = False,
|
||||
use_bias_at_final: bool = False,
|
||||
use_cuda_kernel: bool = False,
|
||||
):
|
||||
super(BigVGAN, self).__init__()
|
||||
|
||||
self.num_kernels = len(resblock_kernel_sizes)
|
||||
self.num_upsamples = len(upsample_rates)
|
||||
|
||||
# pre conv
|
||||
self.conv_pre = weight_norm(
|
||||
Conv1d(num_mels, upsample_initial_channel, 7, 1, padding=3)
|
||||
)
|
||||
|
||||
# define which AMPBlock to use. BigVGAN uses AMPBlock1 as default
|
||||
resblock = AMPBlock1 if resblock_type == "1" else AMPBlock2
|
||||
|
||||
# transposed conv-based upsamplers. does not apply anti-aliasing
|
||||
self.ups = nn.ModuleList()
|
||||
for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)):
|
||||
self.ups.append(
|
||||
nn.ModuleList(
|
||||
[
|
||||
weight_norm(
|
||||
ConvTranspose1d(
|
||||
upsample_initial_channel // (2**i),
|
||||
upsample_initial_channel // (2 ** (i + 1)),
|
||||
k,
|
||||
u,
|
||||
padding=(k - u) // 2,
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# residual blocks using anti-aliased multi-periodicity composition modules (AMP)
|
||||
self.resblocks = nn.ModuleList()
|
||||
for i in range(len(self.ups)):
|
||||
ch = upsample_initial_channel // (2 ** (i + 1))
|
||||
for j, (k, d) in enumerate(
|
||||
zip(resblock_kernel_sizes, resblock_dilation_sizes)
|
||||
):
|
||||
self.resblocks.append(
|
||||
resblock(
|
||||
ch,
|
||||
k,
|
||||
d,
|
||||
activation=activation,
|
||||
snake_logscale=snake_logscale,
|
||||
use_cuda_kernel=use_cuda_kernel,
|
||||
)
|
||||
)
|
||||
|
||||
# select which Activation1d, lazy-load cuda version to ensure backward compatibility
|
||||
if use_cuda_kernel:
|
||||
from modules.bigvgan.alias_free_cuda.activation1d import (
|
||||
Activation1d as CudaActivation1d,
|
||||
)
|
||||
|
||||
Activation1d = CudaActivation1d
|
||||
else:
|
||||
Activation1d = TorchActivation1d
|
||||
|
||||
# post conv
|
||||
if (
|
||||
activation == "snake"
|
||||
): # periodic nonlinearity with snake function and anti-aliasing
|
||||
activation_post = Snake(ch, alpha_logscale=snake_logscale)
|
||||
self.activation_post = Activation1d(activation=activation_post)
|
||||
elif (
|
||||
activation == "snakebeta"
|
||||
): # periodic nonlinearity with snakebeta function and anti-aliasing
|
||||
activation_post = SnakeBeta(ch, alpha_logscale=snake_logscale)
|
||||
self.activation_post = Activation1d(activation=activation_post)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"activation incorrectly specified. check the config file and look for 'activation'."
|
||||
)
|
||||
|
||||
# whether to use bias for the final conv_post. Defaults to True for backward compatibility
|
||||
self.use_bias_at_final = use_bias_at_final
|
||||
self.conv_post = weight_norm(
|
||||
Conv1d(ch, 1, 7, 1, padding=3, bias=self.use_bias_at_final)
|
||||
)
|
||||
|
||||
# weight initialization
|
||||
for i in range(len(self.ups)):
|
||||
self.ups[i].apply(init_weights)
|
||||
self.conv_post.apply(init_weights)
|
||||
|
||||
# final tanh activation. Defaults to True for backward compatibility
|
||||
self.use_tanh_at_final = use_tanh_at_final
|
||||
|
||||
def forward(self, x):
|
||||
# pre conv
|
||||
x = self.conv_pre(x)
|
||||
|
||||
for i in range(self.num_upsamples):
|
||||
# upsampling
|
||||
for i_up in range(len(self.ups[i])):
|
||||
x = self.ups[i][i_up](x)
|
||||
# AMP blocks
|
||||
xs = None
|
||||
for j in range(self.num_kernels):
|
||||
if xs is None:
|
||||
xs = self.resblocks[i * self.num_kernels + j](x)
|
||||
else:
|
||||
xs += self.resblocks[i * self.num_kernels + j](x)
|
||||
x = xs / self.num_kernels
|
||||
|
||||
# post conv
|
||||
x = self.activation_post(x)
|
||||
x = self.conv_post(x)
|
||||
# final tanh activation
|
||||
if self.use_tanh_at_final:
|
||||
x = torch.tanh(x)
|
||||
else:
|
||||
x = torch.clamp(x, min=-1.0, max=1.0) # bound the output to [-1, 1]
|
||||
|
||||
return x
|
||||
|
||||
def remove_weight_norm(self):
|
||||
print("Removing weight norm...")
|
||||
for l in self.ups:
|
||||
for l_i in l:
|
||||
remove_weight_norm(l_i)
|
||||
for l in self.resblocks:
|
||||
l.remove_weight_norm()
|
||||
remove_weight_norm(self.conv_pre)
|
||||
remove_weight_norm(self.conv_post)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
from fireredtts.modules.flow.codec_embedding import HHGCodecEmbedding
|
||||
from fireredtts.modules.flow.conformer import ConformerDecoderV2
|
||||
from fireredtts.modules.flow.mel_encoder import MelReduceEncoder
|
||||
from fireredtts.modules.flow.decoder import ConditionalCFM, ConditionalDecoder
|
||||
from fireredtts.modules.flow.flow_model import InterpolateRegulator, CrossAttnFlowMatching
|
||||
from fireredtts.modules.flow.mel_spectrogram import MelSpectrogramExtractor
|
||||
|
||||
|
||||
def get_flow_frontend(flow_config):
|
||||
flow = CrossAttnFlowMatching(
|
||||
output_size=flow_config["output_size"],
|
||||
input_embedding=HHGCodecEmbedding(**flow_config["input_embedding"]),
|
||||
encoder=ConformerDecoderV2(**flow_config["encoder"]),
|
||||
length_regulator=InterpolateRegulator(**flow_config["length_regulator"]),
|
||||
mel_encoder=MelReduceEncoder(**flow_config["mel_encoder"]),
|
||||
decoder=ConditionalCFM(
|
||||
estimator=ConditionalDecoder(**flow_config["decoder"]["estimator"]),
|
||||
t_scheduler=flow_config["decoder"]["t_scheduler"],
|
||||
inference_cfg_rate=flow_config["decoder"]["inference_cfg_rate"]
|
||||
)
|
||||
)
|
||||
return flow
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,31 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import os.path as osp
|
||||
now_dir = osp.dirname(osp.abspath(__file__))
|
||||
class HHGCodecEmbedding(nn.Module):
|
||||
def __init__(self, out_channels, codebook_path:str, freeze=True):
|
||||
super().__init__()
|
||||
# (2, 128, 128)
|
||||
codebook_path = osp.join(now_dir,"codebook.npy")
|
||||
codebook = torch.from_numpy(np.load(codebook_path).copy())
|
||||
assert codebook.shape[0] == 2 and codebook.shape[1] == 128
|
||||
self.codebook_dim = codebook.shape[2]
|
||||
|
||||
self.codebook = torch.nn.ModuleList([
|
||||
torch.nn.Embedding.from_pretrained(codebook[i], freeze=freeze)
|
||||
for i in range(codebook.shape[0])]
|
||||
)
|
||||
if self.codebook_dim * 2 != out_channels:
|
||||
self.proj = nn.Linear(self.codebook_dim * 2, out_channels)
|
||||
else:
|
||||
self.proj = nn.Identity()
|
||||
|
||||
def forward(self, tokens):
|
||||
token_embs = torch.cat([
|
||||
self.codebook[0](tokens % 128),
|
||||
self.codebook[1](tokens // 128)
|
||||
], dim=-1)
|
||||
token_embs = self.proj(token_embs)
|
||||
return token_embs
|
||||
|
||||
@@ -0,0 +1,730 @@
|
||||
import typing as tp
|
||||
import math
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from fireredtts.modules.flow.utils import make_pad_mask
|
||||
|
||||
|
||||
class MultiHeadedAttention(nn.Module):
|
||||
"""Multi-Head Attention layer.
|
||||
|
||||
Args:
|
||||
n_head (int): The number of heads.
|
||||
n_feat (int): The number of features.
|
||||
dropout_rate (float): Dropout rate.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
n_head: int,
|
||||
n_feat: int,
|
||||
dropout_rate: float,
|
||||
key_bias: bool = True):
|
||||
"""Construct an MultiHeadedAttention object."""
|
||||
super().__init__()
|
||||
assert n_feat % n_head == 0
|
||||
# We assume d_v always equals d_k
|
||||
self.d_k = n_feat // n_head
|
||||
self.h = n_head
|
||||
self.linear_q = nn.Linear(n_feat, n_feat)
|
||||
self.linear_k = nn.Linear(n_feat, n_feat, bias=key_bias)
|
||||
self.linear_v = nn.Linear(n_feat, n_feat)
|
||||
self.linear_out = nn.Linear(n_feat, n_feat)
|
||||
self.dropout = nn.Dropout(p=dropout_rate)
|
||||
|
||||
def forward_qkv(
|
||||
self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor
|
||||
) -> tp.Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""Transform query, key and value.
|
||||
|
||||
Args:
|
||||
query (torch.Tensor): Query tensor (#batch, time1, size).
|
||||
key (torch.Tensor): Key tensor (#batch, time2, size).
|
||||
value (torch.Tensor): Value tensor (#batch, time2, size).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Transformed query tensor, size
|
||||
(#batch, n_head, time1, d_k).
|
||||
torch.Tensor: Transformed key tensor, size
|
||||
(#batch, n_head, time2, d_k).
|
||||
torch.Tensor: Transformed value tensor, size
|
||||
(#batch, n_head, time2, d_k).
|
||||
|
||||
"""
|
||||
n_batch = query.size(0)
|
||||
q = self.linear_q(query).view(n_batch, -1, self.h, self.d_k)
|
||||
k = self.linear_k(key).view(n_batch, -1, self.h, self.d_k)
|
||||
v = self.linear_v(value).view(n_batch, -1, self.h, self.d_k)
|
||||
q = q.transpose(1, 2) # (batch, head, time1, d_k)
|
||||
k = k.transpose(1, 2) # (batch, head, time2, d_k)
|
||||
v = v.transpose(1, 2) # (batch, head, time2, d_k)
|
||||
|
||||
return q, k, v
|
||||
|
||||
def forward_attention(
|
||||
self,
|
||||
value: torch.Tensor,
|
||||
scores: torch.Tensor,
|
||||
mask: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool)
|
||||
) -> torch.Tensor:
|
||||
"""Compute attention context vector.
|
||||
|
||||
Args:
|
||||
value (torch.Tensor): Transformed value, size
|
||||
(#batch, n_head, time2, d_k).
|
||||
scores (torch.Tensor): Attention score, size
|
||||
(#batch, n_head, time1, time2).
|
||||
mask (torch.Tensor): Mask, size (#batch, 1, time2) or
|
||||
(#batch, time1, time2), (0, 0, 0) means fake mask.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Transformed value (#batch, time1, d_model)
|
||||
weighted by the attention score (#batch, time1, time2).
|
||||
|
||||
"""
|
||||
n_batch = value.size(0)
|
||||
# NOTE(xcsong): When will `if mask.size(2) > 0` be True?
|
||||
# 1. onnx(16/4) [WHY? Because we feed real cache & real mask for the
|
||||
# 1st chunk to ease the onnx export.]
|
||||
# 2. pytorch training
|
||||
if mask.size(2) > 0: # time2 > 0
|
||||
mask = mask.unsqueeze(1).eq(0) # (batch, 1, *, time2)
|
||||
# For last chunk, time2 might be larger than scores.size(-1)
|
||||
mask = mask[:, :, :, :scores.size(-1)] # (batch, 1, *, time2)
|
||||
scores = scores.masked_fill(mask, -float('inf'))
|
||||
attn = torch.softmax(scores, dim=-1).masked_fill(
|
||||
mask, 0.0) # (batch, head, time1, time2)
|
||||
# NOTE(xcsong): When will `if mask.size(2) > 0` be False?
|
||||
# 1. onnx(16/-1, -1/-1, 16/0)
|
||||
# 2. jit (16/-1, -1/-1, 16/0, 16/4)
|
||||
else:
|
||||
attn = torch.softmax(scores, dim=-1) # (batch, head, time1, time2)
|
||||
|
||||
p_attn = self.dropout(attn)
|
||||
x = torch.matmul(p_attn, value) # (batch, head, time1, d_k)
|
||||
x = (x.transpose(1, 2).contiguous().view(n_batch, -1,
|
||||
self.h * self.d_k)
|
||||
) # (batch, time1, d_model)
|
||||
|
||||
return self.linear_out(x) # (batch, time1, d_model)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
query: torch.Tensor,
|
||||
key: torch.Tensor,
|
||||
value: torch.Tensor,
|
||||
mask: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
|
||||
pos_emb: torch.Tensor = torch.empty(0),
|
||||
cache: torch.Tensor = torch.zeros((0, 0, 0, 0))
|
||||
) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Compute scaled dot product attention.
|
||||
|
||||
Args:
|
||||
query (torch.Tensor): Query tensor (#batch, time1, size).
|
||||
key (torch.Tensor): Key tensor (#batch, time2, size).
|
||||
value (torch.Tensor): Value tensor (#batch, time2, size).
|
||||
mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
|
||||
(#batch, time1, time2).
|
||||
1.When applying cross attention between decoder and encoder,
|
||||
the batch padding mask for input is in (#batch, 1, T) shape.
|
||||
2.When applying self attention of encoder,
|
||||
the mask is in (#batch, T, T) shape.
|
||||
cache (torch.Tensor): Cache tensor (1, head, cache_t, d_k * 2),
|
||||
where `cache_t == chunk_size * num_decoding_left_chunks`
|
||||
and `head * d_k == size`
|
||||
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Output tensor (#batch, time1, d_model).
|
||||
torch.Tensor: Cache tensor (1, head, cache_t + time1, d_k * 2)
|
||||
where `cache_t == chunk_size * num_decoding_left_chunks`
|
||||
and `head * d_k == size`
|
||||
|
||||
"""
|
||||
q, k, v = self.forward_qkv(query, key, value)
|
||||
|
||||
# NOTE(xcsong):
|
||||
# when export onnx model, for 1st chunk, we feed
|
||||
# cache(1, head, 0, d_k * 2) (16/-1, -1/-1, 16/0 mode)
|
||||
# or cache(1, head, real_cache_t, d_k * 2) (16/4 mode).
|
||||
# In all modes, `if cache.size(0) > 0` will alwayse be `True`
|
||||
# and we will always do splitting and
|
||||
# concatnation(this will simplify onnx export). Note that
|
||||
# it's OK to concat & split zero-shaped tensors(see code below).
|
||||
# when export jit model, for 1st chunk, we always feed
|
||||
# cache(0, 0, 0, 0) since jit supports dynamic if-branch.
|
||||
# >>> a = torch.ones((1, 2, 0, 4))
|
||||
# >>> b = torch.ones((1, 2, 3, 4))
|
||||
# >>> c = torch.cat((a, b), dim=2)
|
||||
# >>> torch.equal(b, c) # True
|
||||
# >>> d = torch.split(a, 2, dim=-1)
|
||||
# >>> torch.equal(d[0], d[1]) # True
|
||||
if cache.size(0) > 0:
|
||||
key_cache, value_cache = torch.split(cache,
|
||||
cache.size(-1) // 2,
|
||||
dim=-1)
|
||||
k = torch.cat([key_cache, k], dim=2)
|
||||
v = torch.cat([value_cache, v], dim=2)
|
||||
# NOTE(xcsong): We do cache slicing in encoder.forward_chunk, since it's
|
||||
# non-trivial to calculate `next_cache_start` here.
|
||||
new_cache = torch.cat((k, v), dim=-1)
|
||||
|
||||
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
|
||||
return self.forward_attention(v, scores, mask), new_cache
|
||||
|
||||
|
||||
class RelPositionMultiHeadedAttention(MultiHeadedAttention):
|
||||
"""Multi-Head Attention layer with relative position encoding.
|
||||
Paper: https://arxiv.org/abs/1901.02860
|
||||
Args:
|
||||
n_head (int): The number of heads.
|
||||
n_feat (int): The number of features.
|
||||
dropout_rate (float): Dropout rate.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
n_head: int,
|
||||
n_feat: int,
|
||||
dropout_rate: float,
|
||||
key_bias: bool = True):
|
||||
"""Construct an RelPositionMultiHeadedAttention object."""
|
||||
super().__init__(n_head, n_feat, dropout_rate, key_bias)
|
||||
# linear transformation for positional encoding
|
||||
self.linear_pos = nn.Linear(n_feat, n_feat, bias=False)
|
||||
# these two learnable bias are used in matrix c and matrix d
|
||||
# as described in https://arxiv.org/abs/1901.02860 Section 3.3
|
||||
self.pos_bias_u = nn.Parameter(torch.Tensor(self.h, self.d_k))
|
||||
self.pos_bias_v = nn.Parameter(torch.Tensor(self.h, self.d_k))
|
||||
torch.nn.init.xavier_uniform_(self.pos_bias_u)
|
||||
torch.nn.init.xavier_uniform_(self.pos_bias_v)
|
||||
|
||||
def rel_shift(self, x):
|
||||
"""Compute relative positional encoding.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): Input tensor (batch, head, time1, 2*time1-1).
|
||||
time1 means the length of query vector.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Output tensor.
|
||||
|
||||
"""
|
||||
zero_pad = torch.zeros((*x.size()[:3], 1), device=x.device, dtype=x.dtype)
|
||||
x_padded = torch.cat([zero_pad, x], dim=-1)
|
||||
|
||||
x_padded = x_padded.view(*x.size()[:2], x.size(3) + 1, x.size(2))
|
||||
x = x_padded[:, :, 1:].view_as(x)[
|
||||
:, :, :, : x.size(-1) // 2 + 1
|
||||
] # only keep the positions from 0 to time2
|
||||
return x
|
||||
|
||||
def forward(
|
||||
self,
|
||||
query: torch.Tensor,
|
||||
key: torch.Tensor,
|
||||
value: torch.Tensor,
|
||||
mask: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
|
||||
pos_emb: torch.Tensor = torch.empty(0),
|
||||
cache: torch.Tensor = torch.zeros((0, 0, 0, 0))
|
||||
) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Compute 'Scaled Dot Product Attention' with rel. positional encoding.
|
||||
Args:
|
||||
query (torch.Tensor): Query tensor (#batch, time1, size).
|
||||
key (torch.Tensor): Key tensor (#batch, time2, size).
|
||||
value (torch.Tensor): Value tensor (#batch, time2, size).
|
||||
mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
|
||||
(#batch, time1, time2), (0, 0, 0) means fake mask.
|
||||
pos_emb (torch.Tensor): Positional embedding tensor
|
||||
(#batch, time2, size).
|
||||
cache (torch.Tensor): Cache tensor (1, head, cache_t, d_k * 2),
|
||||
where `cache_t == chunk_size * num_decoding_left_chunks`
|
||||
and `head * d_k == size`
|
||||
Returns:
|
||||
torch.Tensor: Output tensor (#batch, time1, d_model).
|
||||
torch.Tensor: Cache tensor (1, head, cache_t + time1, d_k * 2)
|
||||
where `cache_t == chunk_size * num_decoding_left_chunks`
|
||||
and `head * d_k == size`
|
||||
"""
|
||||
q, k, v = self.forward_qkv(query, key, value)
|
||||
q = q.transpose(1, 2) # (batch, time1, head, d_k)
|
||||
|
||||
# NOTE(xcsong):
|
||||
# when export onnx model, for 1st chunk, we feed
|
||||
# cache(1, head, 0, d_k * 2) (16/-1, -1/-1, 16/0 mode)
|
||||
# or cache(1, head, real_cache_t, d_k * 2) (16/4 mode).
|
||||
# In all modes, `if cache.size(0) > 0` will alwayse be `True`
|
||||
# and we will always do splitting and
|
||||
# concatnation(this will simplify onnx export). Note that
|
||||
# it's OK to concat & split zero-shaped tensors(see code below).
|
||||
# when export jit model, for 1st chunk, we always feed
|
||||
# cache(0, 0, 0, 0) since jit supports dynamic if-branch.
|
||||
# >>> a = torch.ones((1, 2, 0, 4))
|
||||
# >>> b = torch.ones((1, 2, 3, 4))
|
||||
# >>> c = torch.cat((a, b), dim=2)
|
||||
# >>> torch.equal(b, c) # True
|
||||
# >>> d = torch.split(a, 2, dim=-1)
|
||||
# >>> torch.equal(d[0], d[1]) # True
|
||||
if cache.size(0) > 0:
|
||||
key_cache, value_cache = torch.split(cache,
|
||||
cache.size(-1) // 2,
|
||||
dim=-1)
|
||||
k = torch.cat([key_cache, k], dim=2)
|
||||
v = torch.cat([value_cache, v], dim=2)
|
||||
# NOTE(xcsong): We do cache slicing in encoder.forward_chunk, since it's
|
||||
# non-trivial to calculate `next_cache_start` here.
|
||||
new_cache = torch.cat((k, v), dim=-1)
|
||||
|
||||
n_batch_pos = pos_emb.size(0)
|
||||
p = self.linear_pos(pos_emb).view(n_batch_pos, -1, self.h, self.d_k)
|
||||
p = p.transpose(1, 2) # (batch, head, time1, d_k)
|
||||
|
||||
# (batch, head, time1, d_k)
|
||||
q_with_bias_u = (q + self.pos_bias_u).transpose(1, 2)
|
||||
# (batch, head, time1, d_k)
|
||||
q_with_bias_v = (q + self.pos_bias_v).transpose(1, 2)
|
||||
|
||||
# compute attention score
|
||||
# first compute matrix a and matrix c
|
||||
# as described in https://arxiv.org/abs/1901.02860 Section 3.3
|
||||
# (batch, head, time1, time2)
|
||||
matrix_ac = torch.matmul(q_with_bias_u, k.transpose(-2, -1))
|
||||
|
||||
# compute matrix b and matrix d
|
||||
# (batch, head, time1, time2)
|
||||
matrix_bd = torch.matmul(q_with_bias_v, p.transpose(-2, -1))
|
||||
# NOTE(Xiang Lyu): Keep rel_shift since espnet rel_pos_emb is used
|
||||
if matrix_ac.shape != matrix_bd.shape:
|
||||
matrix_bd = self.rel_shift(matrix_bd)
|
||||
|
||||
scores = (matrix_ac + matrix_bd) / math.sqrt(
|
||||
self.d_k) # (batch, head, time1, time2)
|
||||
|
||||
return self.forward_attention(v, scores, mask), new_cache
|
||||
|
||||
|
||||
class PositionwiseFeedForward(torch.nn.Module):
|
||||
"""Positionwise feed forward layer.
|
||||
|
||||
FeedForward are appied on each position of the sequence.
|
||||
The output dim is same with the input dim.
|
||||
|
||||
Args:
|
||||
idim (int): Input dimenstion.
|
||||
hidden_units (int): The number of hidden units.
|
||||
dropout_rate (float): Dropout rate.
|
||||
activation (torch.nn.Module): Activation function
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
idim: int,
|
||||
hidden_units: int,
|
||||
dropout_rate: float,
|
||||
activation: torch.nn.Module = torch.nn.ReLU(),
|
||||
):
|
||||
"""Construct a PositionwiseFeedForward object."""
|
||||
super(PositionwiseFeedForward, self).__init__()
|
||||
self.w_1 = torch.nn.Linear(idim, hidden_units)
|
||||
self.activation = activation
|
||||
self.dropout = torch.nn.Dropout(dropout_rate)
|
||||
self.w_2 = torch.nn.Linear(hidden_units, idim)
|
||||
|
||||
def forward(self, xs: torch.Tensor) -> torch.Tensor:
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
xs: input tensor (B, L, D)
|
||||
Returns:
|
||||
output tensor, (B, L, D)
|
||||
"""
|
||||
return self.w_2(self.dropout(self.activation(self.w_1(xs))))
|
||||
|
||||
|
||||
class ConformerDecoderLayer(nn.Module):
|
||||
"""Encoder layer module.
|
||||
Args:
|
||||
size (int): Input dimension.
|
||||
self_attn (torch.nn.Module): Self-attention module instance.
|
||||
`MultiHeadedAttention` or `RelPositionMultiHeadedAttention`
|
||||
instance can be used as the argument.
|
||||
src_attn (torch.nn.Module): Cross-attention module instance.
|
||||
`MultiHeadedAttention` or `RelPositionMultiHeadedAttention`
|
||||
instance can be used as the argument.
|
||||
feed_forward (torch.nn.Module): Feed-forward module instance.
|
||||
`PositionwiseFeedForward` instance can be used as the argument.
|
||||
feed_forward_macaron (torch.nn.Module): Additional feed-forward module
|
||||
instance.
|
||||
`PositionwiseFeedForward` instance can be used as the argument.
|
||||
conv_module (torch.nn.Module): Convolution module instance.
|
||||
`ConvlutionModule` instance can be used as the argument.
|
||||
dropout_rate (float): Dropout rate.
|
||||
normalize_before (bool):
|
||||
True: use layer_norm before each sub-block.
|
||||
False: use layer_norm after each sub-block.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
size: int,
|
||||
self_attn: torch.nn.Module,
|
||||
src_attn: tp.Optional[torch.nn.Module] = None,
|
||||
feed_forward: tp.Optional[nn.Module] = None,
|
||||
feed_forward_macaron: tp.Optional[nn.Module] = None,
|
||||
conv_module: tp.Optional[nn.Module] = None,
|
||||
dropout_rate: float = 0.1,
|
||||
normalize_before: bool = True,
|
||||
):
|
||||
"""Construct an EncoderLayer object."""
|
||||
super().__init__()
|
||||
self.self_attn = self_attn
|
||||
self.src_attn = src_attn
|
||||
self.feed_forward = feed_forward
|
||||
self.feed_forward_macaron = feed_forward_macaron
|
||||
self.conv_module = conv_module
|
||||
self.norm_ff = nn.LayerNorm(size, eps=1e-5) # for the FNN module
|
||||
self.norm_mha = nn.LayerNorm(size, eps=1e-5) # for the MHA module
|
||||
if src_attn is not None:
|
||||
self.norm_mha2 = nn.LayerNorm(size, eps=1e-5) # for the MHA module(src_attn)
|
||||
if feed_forward_macaron is not None:
|
||||
self.norm_ff_macaron = nn.LayerNorm(size, eps=1e-5)
|
||||
self.ff_scale = 0.5
|
||||
else:
|
||||
self.ff_scale = 1.0
|
||||
if self.conv_module is not None:
|
||||
self.norm_conv = nn.LayerNorm(size, eps=1e-5) # for the CNN module
|
||||
self.norm_final = nn.LayerNorm(
|
||||
size, eps=1e-5) # for the final output of the block
|
||||
self.dropout = nn.Dropout(dropout_rate)
|
||||
self.size = size
|
||||
self.normalize_before = normalize_before
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
mask: torch.Tensor,
|
||||
# src-attention
|
||||
memory: torch.Tensor,
|
||||
memory_mask: torch.Tensor,
|
||||
pos_emb: torch.Tensor,
|
||||
mask_pad: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
|
||||
att_cache: torch.Tensor = torch.zeros((0, 0, 0, 0)),
|
||||
cnn_cache: torch.Tensor = torch.zeros((0, 0, 0, 0)),
|
||||
) -> tp.Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""Compute encoded features.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): (#batch, time, size)
|
||||
mask (torch.Tensor): Mask tensor for the input (#batch, time,time),
|
||||
(0, 0, 0) means fake mask.
|
||||
pos_emb (torch.Tensor): positional encoding, must not be None
|
||||
for ConformerEncoderLayer.
|
||||
mask_pad (torch.Tensor): batch padding mask used for conv module.
|
||||
(#batch, 1, time), (0, 0, 0) means fake mask.
|
||||
att_cache (torch.Tensor): Cache tensor of the KEY & VALUE
|
||||
(#batch=1, head, cache_t1, d_k * 2), head * d_k == size.
|
||||
cnn_cache (torch.Tensor): Convolution cache in conformer layer
|
||||
(#batch=1, size, cache_t2)
|
||||
Returns:
|
||||
torch.Tensor: Output tensor (#batch, time, size).
|
||||
torch.Tensor: Mask tensor (#batch, time, time).
|
||||
torch.Tensor: att_cache tensor,
|
||||
(#batch=1, head, cache_t1 + time, d_k * 2).
|
||||
torch.Tensor: cnn_cahce tensor (#batch, size, cache_t2).
|
||||
"""
|
||||
|
||||
# whether to use macaron style
|
||||
if self.feed_forward_macaron is not None:
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.norm_ff_macaron(x)
|
||||
x = residual + self.ff_scale * self.dropout(
|
||||
self.feed_forward_macaron(x))
|
||||
if not self.normalize_before:
|
||||
x = self.norm_ff_macaron(x)
|
||||
|
||||
# multi-headed self-attention module
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.norm_mha(x)
|
||||
x_att, new_att_cache = self.self_attn(x, x, x, mask, pos_emb,
|
||||
att_cache)
|
||||
x = residual + self.dropout(x_att)
|
||||
if not self.normalize_before:
|
||||
x = self.norm_mha(x)
|
||||
|
||||
# multi-headed cross-attention module
|
||||
if self.src_attn is not None:
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.norm_mha2(x)
|
||||
x_att, _ = self.src_attn(x, memory, memory, memory_mask)
|
||||
x = residual + self.dropout(x_att)
|
||||
if not self.normalize_before:
|
||||
x = self.norm_mha2(x)
|
||||
|
||||
# convolution module
|
||||
# Fake new cnn cache here, and then change it in conv_module
|
||||
new_cnn_cache = torch.zeros((0, 0, 0), dtype=x.dtype, device=x.device)
|
||||
if self.conv_module is not None:
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.norm_conv(x)
|
||||
x, new_cnn_cache = self.conv_module(x, mask_pad, cnn_cache)
|
||||
x = residual + self.dropout(x)
|
||||
|
||||
if not self.normalize_before:
|
||||
x = self.norm_conv(x)
|
||||
|
||||
# feed forward module
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.norm_ff(x)
|
||||
|
||||
x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
|
||||
if not self.normalize_before:
|
||||
x = self.norm_ff(x)
|
||||
|
||||
if self.conv_module is not None:
|
||||
x = self.norm_final(x)
|
||||
|
||||
return x, mask, new_att_cache, new_cnn_cache
|
||||
|
||||
|
||||
class EspnetRelPositionalEncoding(torch.nn.Module):
|
||||
"""Relative positional encoding module (new implementation).
|
||||
|
||||
Details can be found in https://github.com/espnet/espnet/pull/2816.
|
||||
|
||||
See : Appendix B in https://arxiv.org/abs/1901.02860
|
||||
|
||||
Args:
|
||||
d_model (int): Embedding dimension.
|
||||
dropout_rate (float): Dropout rate.
|
||||
max_len (int): Maximum input length.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, d_model, dropout_rate, max_len=5000):
|
||||
"""Construct an PositionalEncoding object."""
|
||||
super(EspnetRelPositionalEncoding, self).__init__()
|
||||
self.d_model = d_model
|
||||
self.xscale = math.sqrt(self.d_model)
|
||||
self.dropout = torch.nn.Dropout(p=dropout_rate)
|
||||
self.pe = None
|
||||
self.extend_pe(torch.tensor(0.0).expand(1, max_len))
|
||||
|
||||
def extend_pe(self, x):
|
||||
"""Reset the positional encodings."""
|
||||
if self.pe is not None:
|
||||
# self.pe contains both positive and negative parts
|
||||
# the length of self.pe is 2 * input_len - 1
|
||||
if self.pe.size(1) >= x.size(1) * 2 - 1:
|
||||
if self.pe.dtype != x.dtype or self.pe.device != x.device:
|
||||
self.pe = self.pe.to(dtype=x.dtype, device=x.device)
|
||||
return
|
||||
# Suppose `i` means to the position of query vecotr and `j` means the
|
||||
# position of key vector. We use position relative positions when keys
|
||||
# are to the left (i>j) and negative relative positions otherwise (i<j).
|
||||
pe_positive = torch.zeros(x.size(1), self.d_model)
|
||||
pe_negative = torch.zeros(x.size(1), self.d_model)
|
||||
position = torch.arange(0, x.size(1), dtype=torch.float32).unsqueeze(1)
|
||||
div_term = torch.exp(
|
||||
torch.arange(0, self.d_model, 2, dtype=torch.float32)
|
||||
* -(math.log(10000.0) / self.d_model)
|
||||
)
|
||||
pe_positive[:, 0::2] = torch.sin(position * div_term)
|
||||
pe_positive[:, 1::2] = torch.cos(position * div_term)
|
||||
pe_negative[:, 0::2] = torch.sin(-1 * position * div_term)
|
||||
pe_negative[:, 1::2] = torch.cos(-1 * position * div_term)
|
||||
|
||||
# Reserve the order of positive indices and concat both positive and
|
||||
# negative indices. This is used to support the shifting trick
|
||||
# as in https://arxiv.org/abs/1901.02860
|
||||
pe_positive = torch.flip(pe_positive, [0]).unsqueeze(0)
|
||||
pe_negative = pe_negative[1:].unsqueeze(0)
|
||||
pe = torch.cat([pe_positive, pe_negative], dim=1)
|
||||
self.pe = pe.to(device=x.device, dtype=x.dtype)
|
||||
|
||||
def forward(self, x: torch.Tensor, offset: tp.Union[int, torch.Tensor] = 0):
|
||||
"""Add positional encoding.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): Input tensor (batch, time, `*`).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Encoded tensor (batch, time, `*`).
|
||||
|
||||
"""
|
||||
self.extend_pe(x)
|
||||
x = x * self.xscale
|
||||
pos_emb = self.position_encoding(size=x.size(1), offset=offset)
|
||||
return self.dropout(x), self.dropout(pos_emb)
|
||||
|
||||
def position_encoding(self,
|
||||
offset: tp.Union[int, torch.Tensor],
|
||||
size: int) -> torch.Tensor:
|
||||
""" For getting encoding in a streaming fashion
|
||||
|
||||
Attention!!!!!
|
||||
we apply dropout only once at the whole utterance level in a none
|
||||
streaming way, but will call this function several times with
|
||||
increasing input size in a streaming scenario, so the dropout will
|
||||
be applied several times.
|
||||
|
||||
Args:
|
||||
offset (int or torch.tensor): start offset
|
||||
size (int): required size of position encoding
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Corresponding encoding
|
||||
"""
|
||||
pos_emb = self.pe[
|
||||
:,
|
||||
self.pe.size(1) // 2 - size + 1 : self.pe.size(1) // 2 + size,
|
||||
]
|
||||
return pos_emb
|
||||
|
||||
|
||||
class LinearNoSubsampling(torch.nn.Module):
|
||||
"""Linear transform the input without subsampling
|
||||
|
||||
Args:
|
||||
idim (int): Input dimension.
|
||||
odim (int): Output dimension.
|
||||
dropout_rate (float): Dropout rate.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, idim: int, odim: int, dropout_rate: float,
|
||||
pos_enc_class: torch.nn.Module):
|
||||
"""Construct an linear object."""
|
||||
super().__init__()
|
||||
self.out = torch.nn.Sequential(
|
||||
torch.nn.Linear(idim, odim),
|
||||
torch.nn.LayerNorm(odim, eps=1e-5),
|
||||
torch.nn.Dropout(dropout_rate),
|
||||
)
|
||||
self.pos_enc = pos_enc_class
|
||||
self.right_context = 0
|
||||
self.subsampling_rate = 1
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
x_mask: torch.Tensor,
|
||||
offset: tp.Union[int, torch.Tensor] = 0
|
||||
) -> tp.Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""Input x.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): Input tensor (#batch, time, idim).
|
||||
x_mask (torch.Tensor): Input mask (#batch, 1, time).
|
||||
|
||||
Returns:
|
||||
torch.Tensor: linear input tensor (#batch, time', odim),
|
||||
where time' = time .
|
||||
torch.Tensor: linear input mask (#batch, 1, time'),
|
||||
where time' = time .
|
||||
|
||||
"""
|
||||
x = self.out(x)
|
||||
x, pos_emb = self.pos_enc(x, offset)
|
||||
return x, pos_emb, x_mask
|
||||
|
||||
|
||||
class ConformerDecoderV2(nn.Module):
|
||||
def __init__(self,
|
||||
input_size: int = 512,
|
||||
output_size: int = 512,
|
||||
attention_heads: int = 8,
|
||||
linear_units: int = 2048,
|
||||
num_blocks: int = 6,
|
||||
dropout_rate: float = 0.01,
|
||||
srcattention_start_index: int = 0,
|
||||
srcattention_end_index: int = 2,
|
||||
attention_dropout_rate: float = 0.01,
|
||||
positional_dropout_rate: float = 0.01,
|
||||
key_bias: bool = True,
|
||||
normalize_before: bool = True,
|
||||
):
|
||||
super().__init__()
|
||||
self.num_blocks = num_blocks
|
||||
self.normalize_before = normalize_before
|
||||
self.output_size = output_size
|
||||
|
||||
self.embed = LinearNoSubsampling(
|
||||
input_size,
|
||||
output_size,
|
||||
dropout_rate,
|
||||
EspnetRelPositionalEncoding(output_size, positional_dropout_rate),
|
||||
)
|
||||
|
||||
self.encoders = torch.nn.ModuleList()
|
||||
for i in range(self.num_blocks):
|
||||
# construct src attention
|
||||
if srcattention_start_index <= i <= srcattention_end_index:
|
||||
srcattention_layer = MultiHeadedAttention(
|
||||
attention_heads,
|
||||
output_size,
|
||||
attention_dropout_rate,
|
||||
key_bias
|
||||
)
|
||||
else:
|
||||
srcattention_layer = None
|
||||
# construct self attention
|
||||
selfattention_layer = RelPositionMultiHeadedAttention(
|
||||
attention_heads,
|
||||
output_size,
|
||||
attention_dropout_rate,
|
||||
key_bias
|
||||
)
|
||||
# construct ffn
|
||||
ffn_layer = PositionwiseFeedForward(
|
||||
output_size,
|
||||
linear_units,
|
||||
dropout_rate,
|
||||
torch.nn.SiLU()
|
||||
)
|
||||
self.encoders.append(
|
||||
ConformerDecoderLayer(
|
||||
output_size,
|
||||
selfattention_layer,
|
||||
srcattention_layer,
|
||||
ffn_layer,
|
||||
None,
|
||||
None,
|
||||
dropout_rate,
|
||||
normalize_before=normalize_before
|
||||
)
|
||||
)
|
||||
self.after_norm = torch.nn.LayerNorm(output_size, eps=1e-5)
|
||||
|
||||
def forward_layers(self, xs: torch.Tensor, chunk_masks: torch.Tensor,
|
||||
memory: torch.Tensor, memory_masks: torch.Tensor,
|
||||
pos_emb: torch.Tensor, mask_pad: torch.Tensor) -> torch.Tensor:
|
||||
for layer in self.encoders:
|
||||
xs, chunk_masks, _, _ = layer(xs, chunk_masks, memory, memory_masks, pos_emb, mask_pad)
|
||||
return xs
|
||||
|
||||
def forward(self,
|
||||
xs:torch.Tensor,
|
||||
xs_lens:torch.Tensor,
|
||||
memory:torch.Tensor,
|
||||
memory_lens: torch.Tensor,
|
||||
):
|
||||
T = xs.size(1)
|
||||
masks = ~make_pad_mask(xs_lens, T).unsqueeze(1) # (B, 1, T)
|
||||
T2 = memory.size(1)
|
||||
memory_masks = ~make_pad_mask(memory_lens, T2).unsqueeze(1) # (B, 1, T2)
|
||||
|
||||
xs, pos_emb, masks = self.embed(xs, masks)
|
||||
|
||||
xs = self.forward_layers(xs, masks, memory, memory_masks, pos_emb, masks)
|
||||
|
||||
if self.normalize_before:
|
||||
xs = self.after_norm(xs)
|
||||
|
||||
return xs, masks
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
import math
|
||||
from typing import Optional
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from einops import pack, rearrange, repeat
|
||||
from diffusers.models.activations import get_activation
|
||||
|
||||
from fireredtts.modules.flow.transformer import BasicTransformerBlock
|
||||
|
||||
|
||||
class SinusoidalPosEmb(torch.nn.Module):
|
||||
def __init__(self, dim):
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
assert self.dim % 2 == 0, "SinusoidalPosEmb requires dim to be even"
|
||||
|
||||
def forward(self, x, scale=1000):
|
||||
if x.ndim < 1:
|
||||
x = x.unsqueeze(0)
|
||||
device = x.device
|
||||
half_dim = self.dim // 2
|
||||
emb = math.log(10000) / (half_dim - 1)
|
||||
emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb)
|
||||
emb = scale * x.unsqueeze(1) * emb.unsqueeze(0)
|
||||
emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
|
||||
return emb
|
||||
|
||||
|
||||
class Block1D(torch.nn.Module):
|
||||
def __init__(self, dim, dim_out, groups=8):
|
||||
super().__init__()
|
||||
self.block = torch.nn.Sequential(
|
||||
torch.nn.Conv1d(dim, dim_out, 3, padding=1),
|
||||
torch.nn.GroupNorm(groups, dim_out),
|
||||
nn.Mish(),
|
||||
)
|
||||
|
||||
def forward(self, x, mask):
|
||||
output = self.block(x * mask)
|
||||
return output * mask
|
||||
|
||||
|
||||
class ResnetBlock1D(torch.nn.Module):
|
||||
def __init__(self, dim, dim_out, time_emb_dim, groups=8):
|
||||
super().__init__()
|
||||
self.mlp = torch.nn.Sequential(nn.Mish(), torch.nn.Linear(time_emb_dim, dim_out))
|
||||
|
||||
self.block1 = Block1D(dim, dim_out, groups=groups)
|
||||
self.block2 = Block1D(dim_out, dim_out, groups=groups)
|
||||
|
||||
self.res_conv = torch.nn.Conv1d(dim, dim_out, 1)
|
||||
|
||||
def forward(self, x, mask, time_emb):
|
||||
h = self.block1(x, mask)
|
||||
h += self.mlp(time_emb).unsqueeze(-1)
|
||||
h = self.block2(h, mask)
|
||||
output = h + self.res_conv(x * mask)
|
||||
return output
|
||||
|
||||
|
||||
class Downsample1D(nn.Module):
|
||||
def __init__(self, dim):
|
||||
super().__init__()
|
||||
self.conv = torch.nn.Conv1d(dim, dim, 3, 2, 1)
|
||||
|
||||
def forward(self, x):
|
||||
return self.conv(x)
|
||||
|
||||
|
||||
class TimestepEmbedding(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
time_embed_dim: int,
|
||||
act_fn: str = "silu",
|
||||
out_dim: int = None,
|
||||
post_act_fn: Optional[str] = None,
|
||||
cond_proj_dim=None,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.linear_1 = nn.Linear(in_channels, time_embed_dim)
|
||||
|
||||
if cond_proj_dim is not None:
|
||||
self.cond_proj = nn.Linear(cond_proj_dim, in_channels, bias=False)
|
||||
else:
|
||||
self.cond_proj = None
|
||||
|
||||
self.act = get_activation(act_fn)
|
||||
|
||||
if out_dim is not None:
|
||||
time_embed_dim_out = out_dim
|
||||
else:
|
||||
time_embed_dim_out = time_embed_dim
|
||||
self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim_out)
|
||||
|
||||
if post_act_fn is None:
|
||||
self.post_act = None
|
||||
else:
|
||||
self.post_act = get_activation(post_act_fn)
|
||||
|
||||
def forward(self, sample, condition=None):
|
||||
if condition is not None:
|
||||
sample = sample + self.cond_proj(condition)
|
||||
sample = self.linear_1(sample)
|
||||
|
||||
if self.act is not None:
|
||||
sample = self.act(sample)
|
||||
|
||||
sample = self.linear_2(sample)
|
||||
|
||||
if self.post_act is not None:
|
||||
sample = self.post_act(sample)
|
||||
return sample
|
||||
|
||||
|
||||
class Upsample1D(nn.Module):
|
||||
"""A 1D upsampling layer with an optional convolution.
|
||||
|
||||
Parameters:
|
||||
channels (`int`):
|
||||
number of channels in the inputs and outputs.
|
||||
use_conv (`bool`, default `False`):
|
||||
option to use a convolution.
|
||||
use_conv_transpose (`bool`, default `False`):
|
||||
option to use a convolution transpose.
|
||||
out_channels (`int`, optional):
|
||||
number of output channels. Defaults to `channels`.
|
||||
"""
|
||||
|
||||
def __init__(self, channels, use_conv=False, use_conv_transpose=True, out_channels=None, name="conv"):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.out_channels = out_channels or channels
|
||||
self.use_conv = use_conv
|
||||
self.use_conv_transpose = use_conv_transpose
|
||||
self.name = name
|
||||
|
||||
self.conv = None
|
||||
if use_conv_transpose:
|
||||
self.conv = nn.ConvTranspose1d(channels, self.out_channels, 4, 2, 1)
|
||||
elif use_conv:
|
||||
self.conv = nn.Conv1d(self.channels, self.out_channels, 3, padding=1)
|
||||
|
||||
def forward(self, inputs):
|
||||
assert inputs.shape[1] == self.channels
|
||||
if self.use_conv_transpose:
|
||||
return self.conv(inputs)
|
||||
|
||||
outputs = F.interpolate(inputs, scale_factor=2.0, mode="nearest")
|
||||
|
||||
if self.use_conv:
|
||||
outputs = self.conv(outputs)
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
class ConditionalDecoder(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
channels=(256, 256),
|
||||
dropout=0.0,
|
||||
attention_head_dim=64,
|
||||
n_blocks=4,
|
||||
num_mid_blocks=12,
|
||||
num_heads=8,
|
||||
act_fn="gelu",
|
||||
):
|
||||
"""
|
||||
This decoder requires an input with the same shape of the target. So, if your text content
|
||||
is shorter or longer than the outputs, please re-sampling it before feeding to the decoder.
|
||||
"""
|
||||
super().__init__()
|
||||
channels = tuple(channels)
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
|
||||
self.time_embeddings = SinusoidalPosEmb(in_channels)
|
||||
time_embed_dim = channels[0] * 4
|
||||
self.time_mlp = TimestepEmbedding(
|
||||
in_channels=in_channels,
|
||||
time_embed_dim=time_embed_dim,
|
||||
act_fn="silu",
|
||||
)
|
||||
self.down_blocks = nn.ModuleList([])
|
||||
self.mid_blocks = nn.ModuleList([])
|
||||
self.up_blocks = nn.ModuleList([])
|
||||
|
||||
output_channel = in_channels
|
||||
for i in range(len(channels)): # pylint: disable=consider-using-enumerate
|
||||
input_channel = output_channel
|
||||
output_channel = channels[i]
|
||||
is_last = i == len(channels) - 1
|
||||
resnet = ResnetBlock1D(dim=input_channel, dim_out=output_channel, time_emb_dim=time_embed_dim)
|
||||
transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
dim=output_channel,
|
||||
num_attention_heads=num_heads,
|
||||
attention_head_dim=attention_head_dim,
|
||||
dropout=dropout,
|
||||
activation_fn=act_fn,
|
||||
)
|
||||
for _ in range(n_blocks)
|
||||
]
|
||||
)
|
||||
downsample = (
|
||||
Downsample1D(output_channel) if not is_last else nn.Conv1d(output_channel, output_channel, 3, padding=1)
|
||||
)
|
||||
self.down_blocks.append(nn.ModuleList([resnet, transformer_blocks, downsample]))
|
||||
|
||||
for i in range(num_mid_blocks):
|
||||
input_channel = channels[-1]
|
||||
out_channels = channels[-1]
|
||||
resnet = ResnetBlock1D(dim=input_channel, dim_out=output_channel, time_emb_dim=time_embed_dim)
|
||||
|
||||
transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
dim=output_channel,
|
||||
num_attention_heads=num_heads,
|
||||
attention_head_dim=attention_head_dim,
|
||||
dropout=dropout,
|
||||
activation_fn=act_fn,
|
||||
)
|
||||
for _ in range(n_blocks)
|
||||
]
|
||||
)
|
||||
|
||||
self.mid_blocks.append(nn.ModuleList([resnet, transformer_blocks]))
|
||||
|
||||
channels = channels[::-1] + (channels[0],)
|
||||
for i in range(len(channels) - 1):
|
||||
input_channel = channels[i] * 2
|
||||
output_channel = channels[i + 1]
|
||||
is_last = i == len(channels) - 2
|
||||
resnet = ResnetBlock1D(
|
||||
dim=input_channel,
|
||||
dim_out=output_channel,
|
||||
time_emb_dim=time_embed_dim,
|
||||
)
|
||||
transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
dim=output_channel,
|
||||
num_attention_heads=num_heads,
|
||||
attention_head_dim=attention_head_dim,
|
||||
dropout=dropout,
|
||||
activation_fn=act_fn,
|
||||
)
|
||||
for _ in range(n_blocks)
|
||||
]
|
||||
)
|
||||
upsample = (
|
||||
Upsample1D(output_channel, use_conv_transpose=True)
|
||||
if not is_last
|
||||
else nn.Conv1d(output_channel, output_channel, 3, padding=1)
|
||||
)
|
||||
self.up_blocks.append(nn.ModuleList([resnet, transformer_blocks, upsample]))
|
||||
self.final_block = Block1D(channels[-1], channels[-1])
|
||||
self.final_proj = nn.Conv1d(channels[-1], self.out_channels, 1)
|
||||
self.initialize_weights()
|
||||
|
||||
|
||||
def initialize_weights(self):
|
||||
for m in self.modules():
|
||||
if isinstance(m, nn.Conv1d):
|
||||
nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.GroupNorm):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.Linear):
|
||||
nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
def forward(self, x, mask, mu, t):
|
||||
"""Forward pass of the UNet1DConditional model.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): shape (batch_size, in_channels, time)
|
||||
mask (_type_): shape (batch_size, 1, time)
|
||||
t (_type_): shape (batch_size)
|
||||
spks (_type_, optional): shape: (batch_size, condition_channels). Defaults to None.
|
||||
cond (_type_, optional): placeholder for future use. Defaults to None.
|
||||
|
||||
Raises:
|
||||
ValueError: _description_
|
||||
ValueError: _description_
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
|
||||
t = self.time_embeddings(t)
|
||||
t = self.time_mlp(t)
|
||||
|
||||
x = pack([x, mu], "b * t")[0]
|
||||
|
||||
hiddens = []
|
||||
masks = [mask]
|
||||
for resnet, transformer_blocks, downsample in self.down_blocks:
|
||||
mask_down = masks[-1]
|
||||
x = resnet(x, mask_down, t)
|
||||
x = rearrange(x, "b c t -> b t c").contiguous()
|
||||
attn_mask = torch.matmul(mask_down.transpose(1, 2).contiguous(), mask_down)
|
||||
for transformer_block in transformer_blocks:
|
||||
x = transformer_block(
|
||||
hidden_states=x,
|
||||
attention_mask=attn_mask,
|
||||
timestep=t,
|
||||
)
|
||||
x = rearrange(x, "b t c -> b c t").contiguous()
|
||||
hiddens.append(x) # Save hidden states for skip connections
|
||||
x = downsample(x * mask_down)
|
||||
masks.append(mask_down[:, :, ::2])
|
||||
masks = masks[:-1]
|
||||
mask_mid = masks[-1]
|
||||
|
||||
for resnet, transformer_blocks in self.mid_blocks:
|
||||
x = resnet(x, mask_mid, t)
|
||||
x = rearrange(x, "b c t -> b t c").contiguous()
|
||||
attn_mask = torch.matmul(mask_mid.transpose(1, 2).contiguous(), mask_mid)
|
||||
for transformer_block in transformer_blocks:
|
||||
x = transformer_block(
|
||||
hidden_states=x,
|
||||
attention_mask=attn_mask,
|
||||
timestep=t,
|
||||
)
|
||||
x = rearrange(x, "b t c -> b c t").contiguous()
|
||||
|
||||
for resnet, transformer_blocks, upsample in self.up_blocks:
|
||||
mask_up = masks.pop()
|
||||
skip = hiddens.pop()
|
||||
x = pack([x[:, :, :skip.shape[-1]], skip], "b * t")[0]
|
||||
x = resnet(x, mask_up, t)
|
||||
x = rearrange(x, "b c t -> b t c").contiguous()
|
||||
attn_mask = torch.matmul(mask_up.transpose(1, 2).contiguous(), mask_up)
|
||||
for transformer_block in transformer_blocks:
|
||||
x = transformer_block(
|
||||
hidden_states=x,
|
||||
attention_mask=attn_mask,
|
||||
timestep=t,
|
||||
)
|
||||
x = rearrange(x, "b t c -> b c t").contiguous()
|
||||
x = upsample(x * mask_up)
|
||||
x = self.final_block(x, mask_up)
|
||||
output = self.final_proj(x * mask_up)
|
||||
return output * mask
|
||||
|
||||
|
||||
class ConditionalCFM(nn.Module):
|
||||
def __init__(self,
|
||||
estimator: nn.Module,
|
||||
t_scheduler: str = "cosine",
|
||||
inference_cfg_rate: float = 0.7,
|
||||
):
|
||||
super().__init__()
|
||||
self.estimator = estimator
|
||||
self.t_scheduler = t_scheduler
|
||||
self.inference_cfg_rate = inference_cfg_rate
|
||||
|
||||
def solve_euler(self, x, t_span, mu, mask):
|
||||
t, _, dt = t_span[0], t_span[-1], t_span[1] - t_span[0]
|
||||
|
||||
# I am storing this because I can later plot it by putting a debugger here and saving it to a file
|
||||
# Or in future might add like a return_all_steps flag
|
||||
sol = []
|
||||
|
||||
for step in range(1, len(t_span)):
|
||||
dphi_dt = self.estimator(x, mask, mu, t)
|
||||
# Classifier-Free Guidance inference introduced in VoiceBox
|
||||
if self.inference_cfg_rate > 0:
|
||||
cfg_dphi_dt = self.estimator(x, mask, torch.zeros_like(mu), t)
|
||||
dphi_dt = ((1.0 + self.inference_cfg_rate) * dphi_dt -
|
||||
self.inference_cfg_rate * cfg_dphi_dt)
|
||||
x = x + dt * dphi_dt
|
||||
t = t + dt
|
||||
sol.append(x)
|
||||
if step < len(t_span) - 1:
|
||||
dt = t_span[step + 1] - t
|
||||
|
||||
return sol[-1]
|
||||
|
||||
def inference(self, mu, mask, n_timesteps, temperature: float=1.0):
|
||||
z = torch.randn_like(mu) * temperature
|
||||
t_span = torch.linspace(0, 1, n_timesteps + 1, device=mu.device)
|
||||
if self.t_scheduler == 'cosine':
|
||||
t_span = 1 - torch.cos(t_span * 0.5 * torch.pi)
|
||||
return self.solve_euler(z, t_span=t_span, mu=mu, mask=mask)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import time
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
from fireredtts.modules.flow.utils import make_pad_mask
|
||||
|
||||
|
||||
class InterpolateRegulator(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels: int = 512,
|
||||
num_blocks: int = 4,
|
||||
groups: int = 1,
|
||||
):
|
||||
super().__init__()
|
||||
model = []
|
||||
for _ in range(num_blocks):
|
||||
model.extend([
|
||||
nn.Conv1d(channels, channels, 3, 1, 1),
|
||||
nn.GroupNorm(groups, channels),
|
||||
nn.Mish(),
|
||||
])
|
||||
model.append(
|
||||
nn.Conv1d(channels, channels, 1, 1)
|
||||
)
|
||||
self.model = nn.Sequential(*model)
|
||||
|
||||
def forward(self, x, ylens=None):
|
||||
# x in (B, T, D)
|
||||
mask = (~make_pad_mask(ylens)).to(x).unsqueeze(-1)
|
||||
x = F.interpolate(x.transpose(1, 2).contiguous(), size=ylens.max(), mode='nearest')
|
||||
out = self.model(x).transpose(1, 2).contiguous()
|
||||
olens = ylens
|
||||
return out * mask, olens
|
||||
|
||||
|
||||
class CrossAttnFlowMatching(nn.Module):
|
||||
def __init__(self,
|
||||
output_size: int,
|
||||
input_embedding: nn.Module,
|
||||
encoder: nn.Module,
|
||||
length_regulator: nn.Module,
|
||||
mel_encoder: nn.Module,
|
||||
decoder: nn.Module,
|
||||
):
|
||||
super().__init__()
|
||||
self.input_embedding = input_embedding
|
||||
self.encoder = encoder
|
||||
self.length_regulator = length_regulator
|
||||
self.encoder_proj = torch.nn.Linear(self.encoder.output_size, output_size)
|
||||
self.prompt_prenet = mel_encoder
|
||||
self.decoder = decoder
|
||||
|
||||
def inference(self,
|
||||
token: torch.Tensor,
|
||||
token_len: torch.Tensor,
|
||||
prompt_mel: torch.Tensor,
|
||||
prompt_mel_len: torch.Tensor,
|
||||
n_timesteps:int=10,
|
||||
):
|
||||
# prompt projection
|
||||
prompt_feat = self.prompt_prenet(prompt_mel)
|
||||
prompt_feat_len = torch.ceil(prompt_mel_len/self.prompt_prenet.reduction_rate).long()
|
||||
|
||||
# concat text and prompt_text
|
||||
mask = (~make_pad_mask(token_len)).float().unsqueeze(-1).to(token_len.device)
|
||||
token = self.input_embedding(torch.clamp(token, min=0)) * mask
|
||||
|
||||
# 40ms shift to 10ms shift
|
||||
feat_len = (token_len *4).int()
|
||||
|
||||
# first encoder
|
||||
h, _ = self.encoder(token, token_len, prompt_feat, prompt_feat_len)
|
||||
# length regulate
|
||||
h, _ = self.length_regulator(h, feat_len)
|
||||
# final projection
|
||||
h = self.encoder_proj(h)
|
||||
|
||||
mask = (~make_pad_mask(feat_len)).to(h)
|
||||
|
||||
feat = self.decoder.inference(
|
||||
mu=h.transpose(1, 2).contiguous(),
|
||||
mask=mask.unsqueeze(1),
|
||||
n_timesteps=n_timesteps,
|
||||
)
|
||||
return feat
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import typing as tp
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class ConvLayer(nn.Module):
|
||||
def __init__(self,
|
||||
in_channels:int,
|
||||
out_channels:int,
|
||||
kernel_size:int,
|
||||
stride:int,
|
||||
activation:str="GELU",
|
||||
dropout_rate:float=0.0,
|
||||
):
|
||||
super().__init__()
|
||||
self.conv = nn.Conv1d(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=(kernel_size-stride)//2,
|
||||
)
|
||||
self.drop = nn.Dropout(dropout_rate)
|
||||
self.norm = nn.LayerNorm(out_channels)
|
||||
self.activ = getattr(nn, activation)()
|
||||
|
||||
def forward(self, x:torch.Tensor):
|
||||
"""
|
||||
Args:
|
||||
x: (b, t, c)
|
||||
Return:
|
||||
x: (b, t, c)
|
||||
"""
|
||||
x = x.transpose(2, 1)
|
||||
x = self.conv(x)
|
||||
x = x.transpose(2, 1)
|
||||
x = self.drop(x)
|
||||
x = self.norm(x)
|
||||
x = self.activ(x)
|
||||
return x
|
||||
|
||||
|
||||
class ResidualConvLayer(nn.Module):
|
||||
def __init__(self,
|
||||
hidden_channels:int,
|
||||
n_layers:int=2,
|
||||
kernel_size:int=5,
|
||||
activation:str="GELU",
|
||||
dropout_rate:float=0.0,
|
||||
):
|
||||
super().__init__()
|
||||
layers = [
|
||||
ConvLayer(hidden_channels, hidden_channels, kernel_size, 1, activation, dropout_rate)
|
||||
for _ in range(n_layers)
|
||||
]
|
||||
self.layers = nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x:torch.Tensor):
|
||||
"""
|
||||
Args:
|
||||
x: (b, t, c)
|
||||
Returns:
|
||||
x: (b, t, c)
|
||||
"""
|
||||
return x + self.layers(x)
|
||||
|
||||
|
||||
class ResidualConvBlock(nn.Module):
|
||||
def __init__(self,
|
||||
in_channels:int,
|
||||
hidden_channels:int,
|
||||
out_channels:int,
|
||||
n_layers:int=2,
|
||||
n_blocks:int=5,
|
||||
middle_layer:tp.Optional[nn.Module]=None,
|
||||
kernel_size:int=5,
|
||||
activation:str="GELU",
|
||||
dropout_rate:float=0.0,
|
||||
):
|
||||
super().__init__()
|
||||
self.in_proj = nn.Conv1d(
|
||||
in_channels,
|
||||
hidden_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=(kernel_size-1)//2,
|
||||
) if in_channels != hidden_channels else nn.Identity()
|
||||
|
||||
self.conv1 = nn.Sequential(*[
|
||||
ResidualConvLayer(hidden_channels, n_layers, kernel_size, activation, dropout_rate)
|
||||
for _ in range(n_blocks)
|
||||
])
|
||||
|
||||
if middle_layer is None:
|
||||
self.middle_layer = nn.Identity()
|
||||
elif isinstance(middle_layer, nn.Module):
|
||||
self.middle_layer = middle_layer
|
||||
else:
|
||||
raise TypeError("unknown middle layer type:{}".format(type(middle_layer)))
|
||||
|
||||
self.conv2 = nn.Sequential(*[
|
||||
ResidualConvLayer(hidden_channels, n_layers, kernel_size, activation, dropout_rate)
|
||||
for _ in range(n_blocks)
|
||||
])
|
||||
|
||||
self.out_proj = nn.Conv1d(
|
||||
hidden_channels,
|
||||
out_channels,
|
||||
kernel_size=kernel_size,
|
||||
stride=1,
|
||||
padding=(kernel_size-1)//2,
|
||||
) if out_channels != hidden_channels else nn.Identity()
|
||||
|
||||
def forward(self, x:torch.Tensor, **middle_layer_kwargs):
|
||||
"""
|
||||
Args:
|
||||
x: (b, t1, c)
|
||||
Return:
|
||||
x: (b, t2, c)
|
||||
"""
|
||||
x = self.in_proj(x.transpose(2, 1)).transpose(2, 1)
|
||||
x = self.conv1(x)
|
||||
if isinstance(self.middle_layer, nn.MaxPool1d) or isinstance(self.middle_layer, nn.Conv1d):
|
||||
x = self.middle_layer(x.transpose(2, 1)).transpose(2, 1)
|
||||
elif isinstance(self.middle_layer, nn.Identity):
|
||||
x = self.middle_layer(x)
|
||||
else:
|
||||
# incase of phoneme-pooling layer
|
||||
x = self.middle_layer(x, **middle_layer_kwargs)
|
||||
x = self.conv2(x)
|
||||
x = self.out_proj(x.transpose(2, 1)).transpose(2, 1)
|
||||
return x
|
||||
|
||||
|
||||
class MelReduceEncoder(nn.Module):
|
||||
def __init__(self,
|
||||
in_channels:int,
|
||||
out_channels:int,
|
||||
hidden_channels:int=384,
|
||||
reduction_rate:int=4,
|
||||
n_layers:int=2,
|
||||
n_blocks:int=5,
|
||||
kernel_size:int=3,
|
||||
activation:str="GELU",
|
||||
dropout:float=0.0,
|
||||
):
|
||||
super().__init__()
|
||||
self.reduction_rate = reduction_rate
|
||||
middle_conv = nn.Conv1d(
|
||||
in_channels=hidden_channels,
|
||||
out_channels=hidden_channels,
|
||||
kernel_size=reduction_rate,
|
||||
stride=reduction_rate,
|
||||
padding=0
|
||||
)
|
||||
self.encoder = ResidualConvBlock(
|
||||
in_channels=in_channels,
|
||||
hidden_channels=hidden_channels,
|
||||
out_channels=out_channels,
|
||||
n_layers=n_layers,
|
||||
n_blocks=n_blocks,
|
||||
middle_layer=middle_conv,
|
||||
kernel_size=kernel_size,
|
||||
activation=activation,
|
||||
dropout_rate=dropout
|
||||
)
|
||||
|
||||
def forward(self, x:torch.Tensor):
|
||||
return self.encoder(x)
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
from functools import partial
|
||||
import torch
|
||||
import numpy as np
|
||||
import librosa
|
||||
from librosa.filters import mel as librosa_mel_fn
|
||||
from torchaudio.functional import resample as ta_resample_fn
|
||||
|
||||
MAX_WAV_VALUE = 32767.0 # NOTE: 32768.0 -1 to prevent int16 overflow (results in popping sound in corner cases)
|
||||
|
||||
|
||||
def dynamic_range_compression(x, C=1, clip_val=1e-5):
|
||||
return np.log(np.clip(x, a_min=clip_val, a_max=None) * C)
|
||||
|
||||
|
||||
def dynamic_range_decompression(x, C=1):
|
||||
return np.exp(x) / C
|
||||
|
||||
|
||||
def dynamic_range_compression_torch(x, C=1, clip_val=1e-5):
|
||||
return torch.log(torch.clamp(x, min=clip_val) * C)
|
||||
|
||||
|
||||
def dynamic_range_decompression_torch(x, C=1):
|
||||
return torch.exp(x) / C
|
||||
|
||||
|
||||
def spectral_normalize_torch(magnitudes):
|
||||
output = dynamic_range_compression_torch(magnitudes)
|
||||
return output
|
||||
|
||||
|
||||
def spectral_de_normalize_torch(magnitudes):
|
||||
output = dynamic_range_decompression_torch(magnitudes)
|
||||
return output
|
||||
|
||||
|
||||
mel_basis = {}
|
||||
hann_window = {}
|
||||
|
||||
|
||||
def mel_spectrogram(
|
||||
y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False
|
||||
):
|
||||
global mel_basis, hann_window
|
||||
if fmax not in mel_basis:
|
||||
mel = librosa_mel_fn(
|
||||
sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax
|
||||
)
|
||||
str_key_mel_basis = str(fmax) + "_" + str(y.device)
|
||||
mel_basis[str_key_mel_basis] = torch.from_numpy(mel).float().to(y.device)
|
||||
hann_window[str(y.device)] = torch.hann_window(win_size).to(y.device)
|
||||
|
||||
y = torch.nn.functional.pad(
|
||||
y.unsqueeze(1),
|
||||
(int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)),
|
||||
mode="reflect",
|
||||
)
|
||||
y = y.squeeze(1)
|
||||
|
||||
# complex tensor as default, then use view_as_real for future pytorch compatibility
|
||||
spec = torch.stft(
|
||||
y,
|
||||
n_fft,
|
||||
hop_length=hop_size,
|
||||
win_length=win_size,
|
||||
window=hann_window[str(y.device)],
|
||||
center=center,
|
||||
pad_mode="reflect",
|
||||
normalized=False,
|
||||
onesided=True,
|
||||
return_complex=True,
|
||||
)
|
||||
spec = torch.view_as_real(spec)
|
||||
spec = torch.sqrt(spec.pow(2).sum(-1) + (1e-9))
|
||||
|
||||
spec = torch.matmul(mel_basis[str_key_mel_basis], spec)
|
||||
spec = spectral_normalize_torch(spec)
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
kaiser_best_resampling_fn = partial(
|
||||
ta_resample_fn,
|
||||
resampling_method="sinc_interp_kaiser", # DO NOT CHANGE!
|
||||
rolloff=0.917347, # DO NOT CHANGE!
|
||||
beta=12.9846, # DO NOT CHANGE!
|
||||
lowpass_filter_width=50, # DO NOT CHANGE!
|
||||
)
|
||||
|
||||
|
||||
class MelSpectrogramExtractor(object):
|
||||
def __init__(
|
||||
self,
|
||||
n_fft=1024,
|
||||
win_size=1024,
|
||||
num_mels=100,
|
||||
hop_size=160,
|
||||
sampling_rate=16000,
|
||||
fmin=0,
|
||||
fmax=None,
|
||||
):
|
||||
self.n_fft = n_fft
|
||||
self.win_size = win_size
|
||||
self.num_mels = num_mels
|
||||
self.hop_size = hop_size
|
||||
self.sampling_rate = sampling_rate
|
||||
self.fmin = fmin
|
||||
self.fmax = fmax
|
||||
|
||||
def __call__(self, wav_path) -> np.ndarray:
|
||||
wav_data, wav_sr = librosa.load(wav_path, sr=None, mono=True)
|
||||
wav_data = torch.from_numpy(wav_data.copy()).unsqueeze(0)
|
||||
# for 16k wavs, up-downsample to reduce artifects
|
||||
if wav_sr == self.sampling_rate:
|
||||
wav_data = kaiser_best_resampling_fn(wav_data, orig_freq=wav_sr, new_freq=24000)
|
||||
wav_data = kaiser_best_resampling_fn(wav_data, orig_freq=24000, new_freq=self.sampling_rate)
|
||||
else:
|
||||
wav_data = kaiser_best_resampling_fn(wav_data, orig_freq=wav_sr, new_freq=self.sampling_rate)
|
||||
|
||||
# (1, num_mels, t)
|
||||
mel = mel_spectrogram(
|
||||
wav_data,
|
||||
self.n_fft,
|
||||
self.num_mels,
|
||||
self.sampling_rate,
|
||||
self.hop_size,
|
||||
self.win_size,
|
||||
self.fmin,
|
||||
self.fmax,
|
||||
)
|
||||
mel = mel.squeeze(0).transpose(1, 0)
|
||||
return mel # (t, num_mels)
|
||||
@@ -0,0 +1,249 @@
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from diffusers.models.attention import (
|
||||
GEGLU,
|
||||
GELU,
|
||||
AdaLayerNorm,
|
||||
AdaLayerNormZero,
|
||||
ApproximateGELU,
|
||||
)
|
||||
from diffusers.models.attention_processor import Attention
|
||||
# from diffusers.models.lora import LoRACompatibleLinear
|
||||
from diffusers.utils.torch_utils import maybe_allow_in_graph
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
r"""
|
||||
A feed-forward layer.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input.
|
||||
dim_out (`int`, *optional*): The number of channels in the output. If not given, defaults to `dim`.
|
||||
mult (`int`, *optional*, defaults to 4): The multiplier to use for the hidden dimension.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
final_dropout (`bool` *optional*, defaults to False): Apply a final dropout.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
dim_out: Optional[int] = None,
|
||||
mult: int = 4,
|
||||
dropout: float = 0.0,
|
||||
activation_fn: str = "geglu",
|
||||
final_dropout: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
inner_dim = int(dim * mult)
|
||||
dim_out = dim_out if dim_out is not None else dim
|
||||
|
||||
if activation_fn == "gelu":
|
||||
act_fn = GELU(dim, inner_dim)
|
||||
if activation_fn == "gelu-approximate":
|
||||
act_fn = GELU(dim, inner_dim, approximate="tanh")
|
||||
elif activation_fn == "geglu":
|
||||
act_fn = GEGLU(dim, inner_dim)
|
||||
elif activation_fn == "geglu-approximate":
|
||||
act_fn = ApproximateGELU(dim, inner_dim)
|
||||
|
||||
self.net = nn.ModuleList([])
|
||||
# project in
|
||||
self.net.append(act_fn)
|
||||
# project dropout
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
# project out
|
||||
self.net.append(nn.Linear(inner_dim, dim_out))
|
||||
# self.net.append(LoRACompatibleLinear(inner_dim, dim_out))
|
||||
# FF as used in Vision Transformer, MLP-Mixer, etc. have a final dropout
|
||||
if final_dropout:
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
|
||||
def forward(self, hidden_states):
|
||||
for module in self.net:
|
||||
hidden_states = module(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class BasicTransformerBlock(nn.Module):
|
||||
r"""
|
||||
A basic Transformer block.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input and output.
|
||||
num_attention_heads (`int`): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`): The number of channels in each head.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention.
|
||||
only_cross_attention (`bool`, *optional*):
|
||||
Whether to use only cross-attention layers. In this case two cross attention layers are used.
|
||||
double_self_attention (`bool`, *optional*):
|
||||
Whether to use two self-attention layers. In this case no cross attention layers are used.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
num_embeds_ada_norm (:
|
||||
obj: `int`, *optional*): The number of diffusion steps used during training. See `Transformer2DModel`.
|
||||
attention_bias (:
|
||||
obj: `bool`, *optional*, defaults to `False`): Configure if the attentions should contain a bias parameter.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
num_attention_heads: int,
|
||||
attention_head_dim: int,
|
||||
dropout=0.0,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
double_self_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
norm_elementwise_affine: bool = True,
|
||||
norm_type: str = "layer_norm",
|
||||
final_dropout: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.only_cross_attention = only_cross_attention
|
||||
|
||||
self.use_ada_layer_norm_zero = (num_embeds_ada_norm is not None) and norm_type == "ada_norm_zero"
|
||||
self.use_ada_layer_norm = (num_embeds_ada_norm is not None) and norm_type == "ada_norm"
|
||||
|
||||
if norm_type in ("ada_norm", "ada_norm_zero") and num_embeds_ada_norm is None:
|
||||
raise ValueError(
|
||||
f"`norm_type` is set to {norm_type}, but `num_embeds_ada_norm` is not defined. Please make sure to"
|
||||
f" define `num_embeds_ada_norm` if setting `norm_type` to {norm_type}."
|
||||
)
|
||||
|
||||
# Define 3 blocks. Each block has its own normalization layer.
|
||||
# 1. Self-Attn
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
self.norm1 = AdaLayerNormZero(dim, num_embeds_ada_norm)
|
||||
else:
|
||||
self.norm1 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.attn1 = Attention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
cross_attention_dim=cross_attention_dim if only_cross_attention else None,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
|
||||
# 2. Cross-Attn
|
||||
if cross_attention_dim is not None or double_self_attention:
|
||||
# We currently only use AdaLayerNormZero for self attention where there will only be one attention block.
|
||||
# I.e. the number of returned modulation chunks from AdaLayerZero would not make sense if returned during
|
||||
# the second cross attention block.
|
||||
self.norm2 = (
|
||||
AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
if self.use_ada_layer_norm
|
||||
else nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
)
|
||||
self.attn2 = Attention(
|
||||
query_dim=dim,
|
||||
cross_attention_dim=cross_attention_dim if not double_self_attention else None,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
# scale_qk=False, # uncomment this to not to use flash attention
|
||||
) # is self-attn if encoder_hidden_states is none
|
||||
else:
|
||||
self.norm2 = None
|
||||
self.attn2 = None
|
||||
|
||||
# 3. Feed-forward
|
||||
self.norm3 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.ff = FeedForward(dim, dropout=dropout, activation_fn=activation_fn, final_dropout=final_dropout)
|
||||
|
||||
# let chunk size default to None
|
||||
self._chunk_size = None
|
||||
self._chunk_dim = 0
|
||||
|
||||
def set_chunk_feed_forward(self, chunk_size: Optional[int], dim: int):
|
||||
# Sets chunk feed-forward
|
||||
self._chunk_size = chunk_size
|
||||
self._chunk_dim = dim
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
):
|
||||
# Notice that normalization is always applied before the real computation in the following blocks.
|
||||
# 1. Self-Attention
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm1(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(
|
||||
hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype
|
||||
)
|
||||
else:
|
||||
norm_hidden_states = self.norm1(hidden_states)
|
||||
|
||||
cross_attention_kwargs = cross_attention_kwargs if cross_attention_kwargs is not None else {}
|
||||
|
||||
attn_output = self.attn1(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None,
|
||||
attention_mask=encoder_attention_mask if self.only_cross_attention else attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
if self.use_ada_layer_norm_zero:
|
||||
attn_output = gate_msa.unsqueeze(1) * attn_output
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 2. Cross-Attention
|
||||
if self.attn2 is not None:
|
||||
norm_hidden_states = (
|
||||
self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states)
|
||||
)
|
||||
|
||||
attn_output = self.attn2(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=encoder_attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 3. Feed-forward
|
||||
norm_hidden_states = self.norm3(hidden_states)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
||||
|
||||
if self._chunk_size is not None:
|
||||
# "feed_forward_chunk_size" can be used to save memory
|
||||
if norm_hidden_states.shape[self._chunk_dim] % self._chunk_size != 0:
|
||||
raise ValueError(
|
||||
f"`hidden_states` dimension to be chunked: {norm_hidden_states.shape[self._chunk_dim]} has to be divisible by chunk size: {self._chunk_size}. Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`."
|
||||
)
|
||||
|
||||
num_chunks = norm_hidden_states.shape[self._chunk_dim] // self._chunk_size
|
||||
ff_output = torch.cat(
|
||||
[self.ff(hid_slice) for hid_slice in norm_hidden_states.chunk(num_chunks, dim=self._chunk_dim)],
|
||||
dim=self._chunk_dim,
|
||||
)
|
||||
else:
|
||||
ff_output = self.ff(norm_hidden_states)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
ff_output = gate_mlp.unsqueeze(1) * ff_output
|
||||
|
||||
hidden_states = ff_output + hidden_states
|
||||
|
||||
return hidden_states
|
||||
@@ -0,0 +1,30 @@
|
||||
import torch
|
||||
|
||||
|
||||
def make_pad_mask(lengths: torch.Tensor, max_len: int = 0) -> torch.Tensor:
|
||||
"""Make mask tensor containing indices of padded part.
|
||||
|
||||
See description of make_non_pad_mask.
|
||||
|
||||
Args:
|
||||
lengths (torch.Tensor): Batch of lengths (B,).
|
||||
Returns:
|
||||
torch.Tensor: Mask tensor containing indices of padded part.
|
||||
|
||||
Examples:
|
||||
>>> lengths = [5, 3, 2]
|
||||
>>> make_pad_mask(lengths)
|
||||
masks = [[0, 0, 0, 0 ,0],
|
||||
[0, 0, 0, 1, 1],
|
||||
[0, 0, 1, 1, 1]]
|
||||
"""
|
||||
batch_size = lengths.size(0)
|
||||
max_len = max_len if max_len > 0 else lengths.max().item()
|
||||
seq_range = torch.arange(0,
|
||||
max_len,
|
||||
dtype=torch.int64,
|
||||
device=lengths.device)
|
||||
seq_range_expand = seq_range.unsqueeze(0).expand(batch_size, max_len)
|
||||
seq_length_expand = lengths.unsqueeze(-1)
|
||||
mask = seq_range_expand >= seq_length_expand
|
||||
return mask
|
||||
@@ -0,0 +1,356 @@
|
||||
# ported from: https://github.com/neonbjb/tortoise-tts
|
||||
# ported from: https://github.com/coqui-ai/TTS/blob/dev/TTS/tts/layers/xtts/gpt.py
|
||||
|
||||
import functools
|
||||
import math
|
||||
import random
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from transformers import GPT2Config, GPT2Model, GPT2PreTrainedModel
|
||||
from transformers.modeling_outputs import CausalLMOutputWithCrossAttentions
|
||||
|
||||
|
||||
class GPT2InferenceModel(GPT2PreTrainedModel):
|
||||
"""Override GPT2LMHeadModel to allow for prefix conditioning."""
|
||||
|
||||
def __init__(self, config, gpt, pos_emb, embeddings, norm, linear, kv_cache):
|
||||
super().__init__(config)
|
||||
self.transformer = gpt
|
||||
self.pos_embedding = pos_emb
|
||||
self.embeddings = embeddings
|
||||
self.final_norm = norm
|
||||
self.lm_head = nn.Sequential(norm, linear)
|
||||
self.kv_cache = kv_cache
|
||||
|
||||
def store_prefix_emb(self, prefix_emb):
|
||||
self.cached_prefix_emb = prefix_emb
|
||||
|
||||
def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs):
|
||||
token_type_ids = kwargs.get("token_type_ids", None) # usually None
|
||||
if not self.kv_cache:
|
||||
past_key_values = None
|
||||
|
||||
# only last token for inputs_ids if past is defined in kwargs
|
||||
if past_key_values is not None:
|
||||
input_ids = input_ids[:, -1].unsqueeze(-1)
|
||||
if token_type_ids is not None:
|
||||
token_type_ids = token_type_ids[:, -1].unsqueeze(-1)
|
||||
|
||||
attention_mask = kwargs.get("attention_mask", None)
|
||||
position_ids = kwargs.get("position_ids", None)
|
||||
|
||||
if attention_mask is not None and position_ids is None:
|
||||
# create position_ids on the fly for batch generation
|
||||
position_ids = attention_mask.long().cumsum(-1) - 1
|
||||
position_ids.masked_fill_(attention_mask == 0, 1)
|
||||
if past_key_values is not None:
|
||||
position_ids = position_ids[:, -1].unsqueeze(-1)
|
||||
else:
|
||||
position_ids = None
|
||||
return {
|
||||
"input_ids": input_ids,
|
||||
"past_key_values": past_key_values,
|
||||
"use_cache": kwargs.get("use_cache"),
|
||||
"position_ids": position_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"token_type_ids": token_type_ids,
|
||||
}
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids=None,
|
||||
past_key_values=None,
|
||||
attention_mask=None,
|
||||
token_type_ids=None,
|
||||
position_ids=None,
|
||||
head_mask=None,
|
||||
inputs_embeds=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
labels=None,
|
||||
use_cache=None,
|
||||
output_attentions=None,
|
||||
output_hidden_states=None,
|
||||
return_dict=None,
|
||||
):
|
||||
assert self.cached_prefix_emb is not None
|
||||
assert inputs_embeds is None # Not supported by this inference model.
|
||||
assert labels is None # Training not supported by this inference model.
|
||||
return_dict = (
|
||||
return_dict if return_dict is not None else self.config.use_return_dict
|
||||
)
|
||||
|
||||
# Create embedding
|
||||
prefix_len = self.cached_prefix_emb.shape[1]
|
||||
if input_ids.shape[1] != 1:
|
||||
gen_inputs = input_ids[:, prefix_len:]
|
||||
gen_emb = self.embeddings(gen_inputs)
|
||||
gen_emb = gen_emb + self.pos_embedding(gen_emb)
|
||||
if self.cached_prefix_emb.shape[0] != gen_emb.shape[0]:
|
||||
prefix_emb = self.cached_prefix_emb.repeat_interleave(
|
||||
gen_emb.shape[0] // self.cached_prefix_emb.shape[0], 0
|
||||
)
|
||||
else:
|
||||
prefix_emb = self.cached_prefix_emb.to(gen_emb.dtype)
|
||||
emb = torch.cat([prefix_emb, gen_emb], dim=1)
|
||||
else:
|
||||
emb = self.embeddings(input_ids)
|
||||
emb = emb + self.pos_embedding.get_fixed_embedding(
|
||||
attention_mask.shape[1] - (prefix_len + 1), attention_mask.device
|
||||
)
|
||||
transformer_outputs = self.transformer(
|
||||
inputs_embeds=emb,
|
||||
past_key_values=past_key_values,
|
||||
attention_mask=attention_mask,
|
||||
token_type_ids=token_type_ids,
|
||||
position_ids=position_ids,
|
||||
head_mask=head_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
use_cache=use_cache,
|
||||
output_attentions=output_attentions,
|
||||
output_hidden_states=output_hidden_states,
|
||||
return_dict=return_dict,
|
||||
)
|
||||
hidden_states = transformer_outputs[0]
|
||||
lm_logits = self.lm_head(hidden_states)
|
||||
|
||||
if not return_dict:
|
||||
return (lm_logits,) + transformer_outputs[1:]
|
||||
|
||||
return CausalLMOutputWithCrossAttentions(
|
||||
loss=None,
|
||||
logits=lm_logits,
|
||||
past_key_values=transformer_outputs.past_key_values,
|
||||
hidden_states=transformer_outputs.hidden_states,
|
||||
attentions=transformer_outputs.attentions,
|
||||
cross_attentions=transformer_outputs.cross_attentions,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _reorder_cache(past, beam_idx):
|
||||
"""
|
||||
This function is used to re-order the :obj:`past_key_values` cache if
|
||||
:meth:`~transformers.PreTrainedModel.beam_search` or :meth:`~transformers.PreTrainedModel.beam_sample` is
|
||||
called. This is required to match :obj:`past_key_values` with the correct beam_idx at every generation step.
|
||||
"""
|
||||
return tuple(
|
||||
tuple(
|
||||
past_state.index_select(0, beam_idx.to(past_state.device))
|
||||
for past_state in layer_past
|
||||
)
|
||||
for layer_past in past
|
||||
)
|
||||
|
||||
|
||||
def null_position_embeddings(range, dim):
|
||||
return torch.zeros((range.shape[0], range.shape[1], dim), device=range.device)
|
||||
|
||||
|
||||
class LearnedPositionEmbeddings(nn.Module):
|
||||
def __init__(self, seq_len, model_dim, init=0.02):
|
||||
super().__init__()
|
||||
self.emb = torch.nn.Embedding(seq_len, model_dim)
|
||||
# Initializing this way is standard for GPT-2
|
||||
self.emb.weight.data.normal_(mean=0.0, std=init)
|
||||
|
||||
def forward(self, x):
|
||||
sl = x.shape[1]
|
||||
return self.emb(torch.arange(0, sl, device=x.device))
|
||||
|
||||
def get_fixed_embedding(self, ind, dev):
|
||||
return self.emb(torch.tensor([ind], device=dev)).unsqueeze(0)
|
||||
|
||||
|
||||
def build_hf_gpt_transformer(
|
||||
layers,
|
||||
model_dim,
|
||||
heads,
|
||||
max_mel_seq_len,
|
||||
max_text_seq_len,
|
||||
max_prompt_len,
|
||||
checkpointing,
|
||||
):
|
||||
"""
|
||||
GPT-2 implemented by the HuggingFace library.
|
||||
"""
|
||||
gpt_config = GPT2Config(
|
||||
vocab_size=256, # Unused.
|
||||
n_positions=max_mel_seq_len + max_text_seq_len + max_prompt_len,
|
||||
n_ctx=max_mel_seq_len + max_text_seq_len + max_prompt_len,
|
||||
n_embd=model_dim,
|
||||
n_layer=layers,
|
||||
n_head=heads,
|
||||
gradient_checkpointing=checkpointing,
|
||||
use_cache=not checkpointing,
|
||||
)
|
||||
gpt = GPT2Model(gpt_config)
|
||||
# Override the built in positional embeddings
|
||||
del gpt.wpe
|
||||
gpt.wpe = functools.partial(null_position_embeddings, dim=model_dim)
|
||||
# Built-in token embeddings are unused.
|
||||
del gpt.wte
|
||||
|
||||
mel_pos_emb = (
|
||||
LearnedPositionEmbeddings(max_mel_seq_len, model_dim)
|
||||
if max_mel_seq_len != -1
|
||||
else functools.partial(null_position_embeddings, dim=model_dim)
|
||||
)
|
||||
text_pos_emb = (
|
||||
LearnedPositionEmbeddings(max_text_seq_len, model_dim)
|
||||
if max_mel_seq_len != -1
|
||||
else functools.partial(null_position_embeddings, dim=model_dim)
|
||||
)
|
||||
return gpt, mel_pos_emb, text_pos_emb, None, None
|
||||
|
||||
|
||||
class GPT(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
start_text_token=261,
|
||||
stop_text_token=0,
|
||||
layers=8,
|
||||
model_dim=512,
|
||||
heads=8,
|
||||
max_text_tokens=120,
|
||||
max_mel_tokens=250,
|
||||
max_prompt_tokens=70,
|
||||
max_conditioning_inputs=1,
|
||||
code_stride_len=1024,
|
||||
number_text_tokens=256,
|
||||
num_audio_tokens=8194,
|
||||
start_audio_token=8192,
|
||||
stop_audio_token=8193,
|
||||
checkpointing=False,
|
||||
label_smoothing=0.0,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.label_smoothing = label_smoothing
|
||||
self.number_text_tokens = number_text_tokens
|
||||
self.start_text_token = start_text_token
|
||||
self.stop_text_token = stop_text_token
|
||||
self.num_audio_tokens = num_audio_tokens
|
||||
self.start_audio_token = start_audio_token
|
||||
self.stop_audio_token = stop_audio_token
|
||||
self.start_prompt_token = start_audio_token
|
||||
self.stop_prompt_token = stop_audio_token
|
||||
self.layers = layers
|
||||
self.heads = heads
|
||||
self.model_dim = model_dim
|
||||
self.max_conditioning_inputs = max_conditioning_inputs
|
||||
self.max_gen_mel_tokens = max_mel_tokens - self.max_conditioning_inputs - 2
|
||||
self.max_mel_tokens = (
|
||||
-1
|
||||
if max_mel_tokens == -1
|
||||
else max_mel_tokens + 2 + self.max_conditioning_inputs
|
||||
)
|
||||
self.max_text_tokens = -1 if max_text_tokens == -1 else max_text_tokens + 2
|
||||
self.max_prompt_tokens = max_prompt_tokens
|
||||
self.code_stride_len = code_stride_len
|
||||
self.conditioning_dropout = nn.Dropout1d(0.1)
|
||||
self.text_embedding = nn.Embedding(self.number_text_tokens, model_dim)
|
||||
self.mel_embedding = nn.Embedding(self.num_audio_tokens, model_dim)
|
||||
|
||||
(
|
||||
self.gpt,
|
||||
self.mel_pos_embedding,
|
||||
self.text_pos_embedding,
|
||||
self.mel_layer_pos_embedding,
|
||||
self.text_layer_pos_embedding,
|
||||
) = build_hf_gpt_transformer(
|
||||
layers,
|
||||
model_dim,
|
||||
heads,
|
||||
self.max_mel_tokens,
|
||||
self.max_text_tokens,
|
||||
self.max_prompt_tokens,
|
||||
checkpointing,
|
||||
)
|
||||
|
||||
self.final_norm = nn.LayerNorm(model_dim)
|
||||
self.text_head = nn.Linear(model_dim, self.number_text_tokens)
|
||||
self.mel_head = nn.Linear(model_dim, self.num_audio_tokens)
|
||||
|
||||
# reference_embedding
|
||||
self.reference_embedding = nn.Sequential(
|
||||
nn.Linear(512, 256),
|
||||
nn.Tanh(),
|
||||
nn.Linear(256, self.model_dim),
|
||||
)
|
||||
|
||||
def init_gpt_for_inference(self, kv_cache=True, use_deepspeed=False):
|
||||
seq_length = (
|
||||
self.max_prompt_tokens + self.max_mel_tokens + self.max_text_tokens + 1
|
||||
)
|
||||
gpt_config = GPT2Config(
|
||||
vocab_size=self.max_mel_tokens,
|
||||
n_positions=seq_length,
|
||||
n_ctx=seq_length,
|
||||
n_embd=self.model_dim,
|
||||
n_layer=self.layers,
|
||||
n_head=self.heads,
|
||||
gradient_checkpointing=False,
|
||||
use_cache=True,
|
||||
)
|
||||
self.gpt_inference = GPT2InferenceModel(
|
||||
gpt_config,
|
||||
self.gpt,
|
||||
self.mel_pos_embedding,
|
||||
self.mel_embedding,
|
||||
self.final_norm,
|
||||
self.mel_head,
|
||||
kv_cache=kv_cache,
|
||||
)
|
||||
self.gpt.wte = self.mel_embedding
|
||||
|
||||
def inference(self, cond_latents, text_inputs, **hf_generate_kwargs):
|
||||
self.compute_embeddings(cond_latents, text_inputs)
|
||||
return self.generate(cond_latents, text_inputs, **hf_generate_kwargs)
|
||||
|
||||
def compute_embeddings(
|
||||
self,
|
||||
cond_latents,
|
||||
text_inputs,
|
||||
):
|
||||
text_inputs = F.pad(text_inputs, (0, 1), value=self.stop_text_token)
|
||||
text_inputs = F.pad(text_inputs, (1, 0), value=self.start_text_token)
|
||||
emb = self.text_embedding(text_inputs) + self.text_pos_embedding(text_inputs)
|
||||
emb = torch.cat([cond_latents, emb], dim=1)
|
||||
self.gpt_inference.store_prefix_emb(emb)
|
||||
gpt_inputs = torch.full(
|
||||
(
|
||||
emb.shape[0],
|
||||
emb.shape[1] + 1, # +1 for the start_audio_token
|
||||
),
|
||||
fill_value=1,
|
||||
dtype=torch.long,
|
||||
device=text_inputs.device,
|
||||
)
|
||||
gpt_inputs[:, -1] = self.start_audio_token
|
||||
return gpt_inputs
|
||||
|
||||
def generate(
|
||||
self,
|
||||
cond_latents,
|
||||
text_inputs,
|
||||
**hf_generate_kwargs,
|
||||
):
|
||||
gpt_inputs = self.compute_embeddings(cond_latents, text_inputs)
|
||||
gen = self.gpt_inference.generate(
|
||||
gpt_inputs,
|
||||
bos_token_id=self.start_audio_token,
|
||||
pad_token_id=self.stop_audio_token,
|
||||
eos_token_id=self.stop_audio_token,
|
||||
max_length=self.max_gen_mel_tokens + gpt_inputs.shape[-1],
|
||||
**hf_generate_kwargs,
|
||||
)
|
||||
if "return_dict_in_generate" in hf_generate_kwargs:
|
||||
return gen.sequences[:, gpt_inputs.shape[1] :], gen
|
||||
return gen[:, gpt_inputs.shape[1] :]
|
||||
@@ -0,0 +1,178 @@
|
||||
import re
|
||||
import regex
|
||||
import inflect
|
||||
import unicodedata
|
||||
from lingua import Language, LanguageDetectorBuilder
|
||||
from builtins import str as unicode
|
||||
|
||||
from tn.chinese.normalizer import Normalizer as ZhNormalizer
|
||||
from tn.english.normalizer import Normalizer as EnNormalizer
|
||||
|
||||
from fireredtts.modules.text_normalizer.regex_common import *
|
||||
from fireredtts.modules.text_normalizer.utils import *
|
||||
|
||||
|
||||
def preprocess_text(sentence):
|
||||
# preprocessing
|
||||
sentence = bytes(sentence, "utf-8").decode("utf-8", "ignore")
|
||||
sentence = regex.sub("[\p{Cf}--[\u200d]]", "", sentence, flags=regex.V1)
|
||||
sentence = regex.sub("\p{Co}", "", sentence)
|
||||
sentence = sentence.replace("\u00a0", " ")
|
||||
sentence = sentence.replace("\ufffd", "")
|
||||
sentence = regex.sub("\p{Zl}", "\n", sentence)
|
||||
sentence = regex.sub("\p{Zp}", "\n", sentence)
|
||||
|
||||
sentence = unicode(sentence)
|
||||
sentence = "".join(
|
||||
char
|
||||
for char in unicodedata.normalize("NFD", sentence)
|
||||
if unicodedata.category(char) != "Mn"
|
||||
) # Strip accents
|
||||
|
||||
sentence = strip_kaomoji(sentence)
|
||||
# full to half with exemption (to be converted after number TN): 。,:
|
||||
sentence = f2b(sentence, exemption="。,:")
|
||||
|
||||
# clean spaces
|
||||
sentence = sentence.replace("\n", ",")
|
||||
sentence = sentence.replace("\t", ",")
|
||||
sentence = sentence.replace("\r", ",")
|
||||
sentence = re.sub(r"[。.]{3,}", "…", sentence)
|
||||
sentence = re.sub(r"[…⋯]{1,}", "…", sentence)
|
||||
sentence = re.sub(r"[ ]+", " ", sentence)
|
||||
sentence = sentence.strip()
|
||||
|
||||
# punctuation reduction
|
||||
result = ""
|
||||
for idx, char in enumerate(sentence):
|
||||
if char in symbol_reduction:
|
||||
char = symbol_reduction[char]
|
||||
|
||||
if char == " ":
|
||||
if idx == 0:
|
||||
continue
|
||||
if is_chinese(sentence[idx + 1]) and (
|
||||
is_chinese(sentence[idx - 1]) or sentence[idx - 1] in '") '
|
||||
):
|
||||
result += ","
|
||||
else:
|
||||
result += " "
|
||||
continue
|
||||
|
||||
if is_valid_char(char):
|
||||
result += char
|
||||
result = re.sub(r"[ ]+", " ", result)
|
||||
return result
|
||||
|
||||
|
||||
def rettt(sentence):
|
||||
# handle abbreviations for all languages
|
||||
sentence = sentence.replace("&nd", "and")
|
||||
sentence = sentence.replace("Jan.", "january")
|
||||
sentence = sentence.replace("Feb.", "febrary")
|
||||
sentence = sentence.replace("Mar.", "march")
|
||||
sentence = sentence.replace("Apr.", "april")
|
||||
sentence = sentence.replace("May.", "may")
|
||||
sentence = sentence.replace("Jun.", "june")
|
||||
sentence = sentence.replace("Jul.", "july")
|
||||
sentence = sentence.replace("Aug.", "august")
|
||||
sentence = sentence.replace("Sept.", "september")
|
||||
sentence = sentence.replace("Sep.", "september")
|
||||
sentence = sentence.replace("Oct.", "october")
|
||||
sentence = sentence.replace("Nov.", "november")
|
||||
sentence = sentence.replace("Dec.", "december")
|
||||
sentence = sentence.replace("Mon.", "monday")
|
||||
sentence = sentence.replace("Tues.", "tuesday")
|
||||
sentence = sentence.replace("Wed.", "wednesday")
|
||||
sentence = sentence.replace("Thur.", "thursday")
|
||||
sentence = sentence.replace("Fri.", "friday")
|
||||
sentence = sentence.replace("Sat.", "saturday")
|
||||
if sentence != "Sun.":
|
||||
sentence = sentence.replace("Sun.", "sunday")
|
||||
sentence = re.sub(r" St\. ([A-Z])", r" saint \1", sentence)
|
||||
sentence = re.sub(r" St\.", " street", sentence)
|
||||
sentence = re.sub(r" Rd\.", " road", sentence)
|
||||
sentence = re.sub(r"[Aa]\.[Mm]\.", "A_M", sentence)
|
||||
sentence = re.sub(r"[Pp]\.[Mm]\.", "P_M", sentence)
|
||||
sentence = re.sub(r"[Bb]\.[Cc]\.", "B_C", sentence)
|
||||
sentence = re.sub(r"[Ad]\.[Dd]\.", "A_D", sentence)
|
||||
sentence = sentence.replace("Mr.", "mister")
|
||||
sentence = sentence.replace("Ms.", "miss")
|
||||
sentence = sentence.replace("Mrs.", "misses")
|
||||
sentence = sentence.replace("Ph.D", "P_H_D")
|
||||
sentence = sentence.replace("i.e.", "that is")
|
||||
sentence = sentence.replace("e.g.", "for example")
|
||||
sentence = sentence.replace("btw.", "by the way")
|
||||
sentence = sentence.replace("btw", "by the way")
|
||||
sentence = sentence.replace("b.t.w.", "by the way")
|
||||
sentence = sentence.replace("@", " at ")
|
||||
return sentence
|
||||
|
||||
|
||||
class TextNormalizer:
|
||||
def __init__(self):
|
||||
self.language_detector = LanguageDetectorBuilder.from_languages(
|
||||
Language.ENGLISH, Language.CHINESE
|
||||
).build()
|
||||
self.zh_normalizer = ZhNormalizer()
|
||||
self.en_normalizer = EnNormalizer()
|
||||
self.inflect_parser = inflect.engine()
|
||||
self.lang2token = {Language.ENGLISH: "en", Language.CHINESE: "zh"}
|
||||
|
||||
def tn(self, text):
|
||||
text = preprocess_text(text)
|
||||
text = rettt(text) # regex replacements
|
||||
# for non chinese languages
|
||||
language = self.language_detector.detect_language_of(text)
|
||||
# enforce chinese if text contains any chinese character
|
||||
if contains_chinese(text):
|
||||
language = Language.CHINESE
|
||||
text_lang = self.lang2token.get(language, "zh")
|
||||
|
||||
if is_upper_eng_and_digit(text):
|
||||
language = Language.CHINESE
|
||||
|
||||
if language == Language.CHINESE:
|
||||
text = self.zh_normalizer.normalize(text)
|
||||
text = text.replace("\n", "")
|
||||
text = re.sub(r"[,,]+$", "。", text)
|
||||
else:
|
||||
text = re.sub(r"[^ 0-9A-Za-z\[\]'.,:?!_\-]", "", text)
|
||||
text = self.en_normalizer.normalize(text)
|
||||
# fallback number normalization
|
||||
pieces = re.split(r"(\d+)", text)
|
||||
text = "".join(
|
||||
[
|
||||
self.inflect_parser.number_to_words(p) if p.isnumeric() else p
|
||||
for p in pieces
|
||||
if len(p) > 0
|
||||
]
|
||||
)
|
||||
|
||||
# cleanup
|
||||
text = text.replace("_", " ")
|
||||
text = re.sub(r"[ ]+", " ", text)
|
||||
|
||||
# spell caplital words
|
||||
pieces = re.split(r"([A-Z]{2,4}|[ ])", text)
|
||||
for idx, p in enumerate(pieces):
|
||||
if re.match("[A-Z]{2,4}", p):
|
||||
pieces[idx] = " ".join(p)
|
||||
text = " ".join([p for p in pieces if p != " "])
|
||||
|
||||
# post TN full to half
|
||||
text = text.replace("。", ".")
|
||||
text = text.replace(",", ",")
|
||||
text = text.replace(":", ":")
|
||||
|
||||
# model limitations
|
||||
text = text.lower().strip()
|
||||
text = text.replace('"', "")
|
||||
text = text.replace("·", " ")
|
||||
text = re.sub("[…~、!,?:;!?:;]+", ",", text)
|
||||
text = re.sub("[,]+", ",", text)
|
||||
text = re.sub(r"[,. ]+$", ".", text)
|
||||
if len(text) > 0 and text[-1] != ".":
|
||||
text = text + "."
|
||||
|
||||
return text, text_lang
|
||||
@@ -0,0 +1,23 @@
|
||||
import re
|
||||
|
||||
kaomoji_regex = re.compile(
|
||||
r"[oヽwΣ┗╰O︿Ψ凸]?[(|≡*(].{0,4}[Д✿_▽→≧﹏`∩⊙∇☆≡๑〃′エ≦▔@﹁εヘ•́ω益‿≖ฺ皿•̀艹 ̄△|゚].{0,5}[|≡*))][┛ブ凸cdd︴oOΨ︿w╯ノ]?"
|
||||
)
|
||||
chinese_regex = re.compile(r"[\u4e00-\u9fa5]")
|
||||
digit_regex = re.compile(r"(\\d+)(\\.\\d+)?", re.UNICODE)
|
||||
|
||||
chinese_char_regex = re.compile(r"^[\u4e00-\u9fa5]$", re.UNICODE)
|
||||
eng_and_digit_char_regex = re.compile(r"^[0-9.,A-Za-z]+$", re.UNICODE)
|
||||
upper_eng_and_digit_regex = re.compile(r"^[ 0-9A-Z\"'.,:?!\-]+$", re.UNICODE)
|
||||
valid_char_regex = re.compile(
|
||||
r"[\t\r\n ]|"
|
||||
r"[\u4e00-\u9fa5]|"
|
||||
r"\u0080|[\u20a0-\u20bf]|\u00a2|\u00a3|\u00a5|\uffe0|\uffe1|\uffe5|\uffe6|"
|
||||
r"\u3000|\u3002|\u00b7|\u2014|\u2019|\u2026|\uff01|\uff1f|\uff0e|\uff1a|\uff1b|\uff0b|\uff0c|\uff0d|\uff0f|[\ufe10-\ufe16]|[\ufe50-\ufe51]|[\ufe55-\ufe57]|\ufe6a|"
|
||||
r"[\u0030-\u0039]|"
|
||||
r"[\u0391-\u03c9]|"
|
||||
r"[\u00b0-\u00b3]|[\u2015-\u2018]|[\u3000-\u303f]|"
|
||||
r"[\u0022-\u002f\u003a-\u003e\u0040\u005b-\u0060\u007b-\u007e]|"
|
||||
r"[\uff21-\uff3a]|[\uff41-\uff5a]|[\u0041-\u005a]|[\u0061-\u007a]",
|
||||
re.UNICODE,
|
||||
)
|
||||
@@ -0,0 +1,121 @@
|
||||
from fireredtts.modules.text_normalizer.regex_common import *
|
||||
|
||||
|
||||
def contains_chinese(text):
|
||||
return bool(chinese_regex.search(text))
|
||||
|
||||
|
||||
def strip_kaomoji(text):
|
||||
return kaomoji_regex.sub(" ", text)
|
||||
|
||||
|
||||
def is_chinese(char):
|
||||
return chinese_char_regex.match(char)
|
||||
|
||||
|
||||
def is_eng_and_digit(char):
|
||||
return eng_and_digit_char_regex.match(char)
|
||||
|
||||
|
||||
def is_upper_eng_and_digit(text):
|
||||
return upper_eng_and_digit_regex.match(text)
|
||||
|
||||
|
||||
def is_valid_char(char):
|
||||
return valid_char_regex.match(char)
|
||||
|
||||
|
||||
def is_digit(text):
|
||||
return digit_regex.match(text)
|
||||
|
||||
|
||||
def contains_chinese(text):
|
||||
return bool(chinese_regex.search(text))
|
||||
|
||||
|
||||
def f2b(ustr, exemption="。,:"):
|
||||
half = []
|
||||
for u in ustr:
|
||||
num = ord(u)
|
||||
if num == 0x3000:
|
||||
half.append(" ")
|
||||
elif u in exemption: # exemption
|
||||
half.append(u)
|
||||
elif 0xFF01 <= num <= 0xFF5E:
|
||||
num -= 0xFEE0
|
||||
half.append(chr(num))
|
||||
else:
|
||||
half.append(u)
|
||||
return "".join(half)
|
||||
|
||||
|
||||
symbol_reduction = {
|
||||
"「": '"',
|
||||
"」": '"',
|
||||
"`": '"',
|
||||
"〝": '"',
|
||||
"〞": '"',
|
||||
"‟": '"',
|
||||
"„": '"',
|
||||
"{": "(",
|
||||
"}": ")",
|
||||
"【": "(",
|
||||
"】": ")",
|
||||
"〖": "(",
|
||||
"〗": ")",
|
||||
"〔": "(",
|
||||
"〕": ")",
|
||||
"〘": "(",
|
||||
"〙": ")",
|
||||
"《": "(",
|
||||
"》": ")",
|
||||
"⦅": "(",
|
||||
"⦆": ")",
|
||||
"〚": "(",
|
||||
"〛": ")",
|
||||
"『": '"',
|
||||
"』": '"',
|
||||
"「": '"',
|
||||
"」": '"',
|
||||
"{": "(",
|
||||
"}": ")",
|
||||
"〈": "(",
|
||||
"〉": ")",
|
||||
"•": "·",
|
||||
"‧": "·",
|
||||
"〰": "…",
|
||||
"﹏": "…",
|
||||
"〜": "~",
|
||||
"~": "~",
|
||||
"+": "+",
|
||||
"、": "、",
|
||||
"。": "。",
|
||||
"︐": ",",
|
||||
"﹐": ",",
|
||||
"︑": "、",
|
||||
"﹑": "、",
|
||||
"︒": "。",
|
||||
"︓": ":",
|
||||
"﹕": ":",
|
||||
"︔": ";",
|
||||
"﹔": ";",
|
||||
"︕": "!",
|
||||
"﹗": "!",
|
||||
"︖": "?",
|
||||
"﹖": "?",
|
||||
"﹙": "(",
|
||||
"﹚": ")",
|
||||
"﹪": "%",
|
||||
"﹠": "&",
|
||||
">": ">",
|
||||
"|": "、",
|
||||
"=": "=",
|
||||
"‐": "-",
|
||||
"‑": "-",
|
||||
"‒": "-",
|
||||
"–": "-",
|
||||
"—": "-",
|
||||
"―": "-",
|
||||
"%": "%",
|
||||
"μ": "u",
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
import torch
|
||||
from fireredtts.modules.tokenizer.whisper_tokenizer import get_tokenizer
|
||||
from fireredtts.modules.text_normalizer.normalize import TextNormalizer
|
||||
|
||||
|
||||
DEFAULT_VOCAB_FILE = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), "../data/tokenizer.json"
|
||||
)
|
||||
|
||||
|
||||
class VoiceBpeTokenizer:
|
||||
def __init__(self):
|
||||
self.tokenizer = get_tokenizer(multilingual=True)
|
||||
self.tn_engine = TextNormalizer()
|
||||
|
||||
def redtts_text_cleaner(self, text):
|
||||
text = text.strip()
|
||||
text, text_lang = self.tn_engine.tn(text)
|
||||
# print("---text after tn:", text)
|
||||
return text, text_lang
|
||||
|
||||
def encode(self, text, lang="auto"):
|
||||
text, text_lang = self.redtts_text_cleaner(text=text)
|
||||
if lang == "auto":
|
||||
lang = text_lang
|
||||
text = f"[{lang}]{text}"
|
||||
return self.tokenizer.encode(text)
|
||||
|
||||
def decode(self, seq):
|
||||
if isinstance(seq, torch.Tensor):
|
||||
seq = seq.cpu().numpy()
|
||||
text = self.tokenizer.decode(seq)
|
||||
return text
|
||||
|
||||
def __len__(self):
|
||||
return self.tokenizer.get_vocab_size()
|
||||
|
||||
def get_number_tokens(self):
|
||||
return self.tokenizer.get_vocab_size()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tok = VoiceBpeTokenizer()
|
||||
codes = tok.encode("我、真是hello USA啊?谢谢你world!")
|
||||
print([tok.decode([c]) for c in codes])
|
||||
@@ -0,0 +1,456 @@
|
||||
# adapted from https://github.com/openai/whisper/blob/main/whisper/tokenizer.py
|
||||
# Copyright (c) 2022 OpenAI
|
||||
|
||||
# MIT License for this file
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import base64
|
||||
import os
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
from functools import cached_property, lru_cache
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import tiktoken
|
||||
|
||||
LANGUAGES = {
|
||||
"en": "english",
|
||||
"zh": "chinese",
|
||||
"de": "german",
|
||||
"es": "spanish",
|
||||
"ru": "russian",
|
||||
"ko": "korean",
|
||||
"fr": "french",
|
||||
"ja": "japanese",
|
||||
"pt": "portuguese",
|
||||
"tr": "turkish",
|
||||
"pl": "polish",
|
||||
"ca": "catalan",
|
||||
"nl": "dutch",
|
||||
"ar": "arabic",
|
||||
"sv": "swedish",
|
||||
"it": "italian",
|
||||
"id": "indonesian",
|
||||
"hi": "hindi",
|
||||
"fi": "finnish",
|
||||
"vi": "vietnamese",
|
||||
"he": "hebrew",
|
||||
"uk": "ukrainian",
|
||||
"el": "greek",
|
||||
"ms": "malay",
|
||||
"cs": "czech",
|
||||
"ro": "romanian",
|
||||
"da": "danish",
|
||||
"hu": "hungarian",
|
||||
"ta": "tamil",
|
||||
"no": "norwegian",
|
||||
"th": "thai",
|
||||
"ur": "urdu",
|
||||
"hr": "croatian",
|
||||
"bg": "bulgarian",
|
||||
"lt": "lithuanian",
|
||||
"la": "latin",
|
||||
"mi": "maori",
|
||||
"ml": "malayalam",
|
||||
"cy": "welsh",
|
||||
"sk": "slovak",
|
||||
"te": "telugu",
|
||||
"fa": "persian",
|
||||
"lv": "latvian",
|
||||
"bn": "bengali",
|
||||
"sr": "serbian",
|
||||
"az": "azerbaijani",
|
||||
"sl": "slovenian",
|
||||
"kn": "kannada",
|
||||
"et": "estonian",
|
||||
"mk": "macedonian",
|
||||
"br": "breton",
|
||||
"eu": "basque",
|
||||
"is": "icelandic",
|
||||
"hy": "armenian",
|
||||
"ne": "nepali",
|
||||
"mn": "mongolian",
|
||||
"bs": "bosnian",
|
||||
"kk": "kazakh",
|
||||
"sq": "albanian",
|
||||
"sw": "swahili",
|
||||
"gl": "galician",
|
||||
"mr": "marathi",
|
||||
"pa": "punjabi",
|
||||
"si": "sinhala",
|
||||
"km": "khmer",
|
||||
"sn": "shona",
|
||||
"yo": "yoruba",
|
||||
"so": "somali",
|
||||
"af": "afrikaans",
|
||||
"oc": "occitan",
|
||||
"ka": "georgian",
|
||||
"be": "belarusian",
|
||||
"tg": "tajik",
|
||||
"sd": "sindhi",
|
||||
"gu": "gujarati",
|
||||
"am": "amharic",
|
||||
"yi": "yiddish",
|
||||
"lo": "lao",
|
||||
"uz": "uzbek",
|
||||
"fo": "faroese",
|
||||
"ht": "haitian creole",
|
||||
"ps": "pashto",
|
||||
"tk": "turkmen",
|
||||
"nn": "nynorsk",
|
||||
"mt": "maltese",
|
||||
"sa": "sanskrit",
|
||||
"lb": "luxembourgish",
|
||||
"my": "myanmar",
|
||||
"bo": "tibetan",
|
||||
"tl": "tagalog",
|
||||
"mg": "malagasy",
|
||||
"as": "assamese",
|
||||
"tt": "tatar",
|
||||
"haw": "hawaiian",
|
||||
"ln": "lingala",
|
||||
"ha": "hausa",
|
||||
"ba": "bashkir",
|
||||
"jw": "javanese",
|
||||
"su": "sundanese",
|
||||
"yue": "cantonese",
|
||||
}
|
||||
|
||||
# language code lookup by name, with a few language aliases
|
||||
TO_LANGUAGE_CODE = {
|
||||
**{language: code for code, language in LANGUAGES.items()},
|
||||
"burmese": "my",
|
||||
"valencian": "ca",
|
||||
"flemish": "nl",
|
||||
"haitian": "ht",
|
||||
"letzeburgesch": "lb",
|
||||
"pushto": "ps",
|
||||
"panjabi": "pa",
|
||||
"moldavian": "ro",
|
||||
"moldovan": "ro",
|
||||
"sinhalese": "si",
|
||||
"castilian": "es",
|
||||
"mandarin": "zh",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tokenizer:
|
||||
"""A thin wrapper around `tiktoken` providing quick access to special tokens"""
|
||||
|
||||
encoding: tiktoken.Encoding
|
||||
num_languages: int
|
||||
language: Optional[str] = None
|
||||
task: Optional[str] = None
|
||||
sot_sequence: Tuple[int] = ()
|
||||
special_tokens: Dict[str, int] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self):
|
||||
for special in self.encoding.special_tokens_set:
|
||||
special_token = self.encoding.encode_single_token(special)
|
||||
self.special_tokens[special] = special_token
|
||||
|
||||
sot: int = self.special_tokens["[startoftranscript]"]
|
||||
translate: int = self.special_tokens["[translate]"]
|
||||
transcribe: int = self.special_tokens["[transcribe]"]
|
||||
|
||||
langs = tuple(LANGUAGES.keys())[: self.num_languages]
|
||||
sot_sequence = [sot]
|
||||
if self.language is not None:
|
||||
sot_sequence.append(sot + 1 + langs.index(self.language))
|
||||
if self.task is not None:
|
||||
task_token: int = transcribe if self.task == "transcribe" else translate
|
||||
sot_sequence.append(task_token)
|
||||
|
||||
self.sot_sequence = tuple(sot_sequence)
|
||||
|
||||
def get_vocab_size(self):
|
||||
return self.encoding.n_vocab
|
||||
|
||||
def encode(self, text):
|
||||
return self.encoding.encode(text, allowed_special="all")
|
||||
|
||||
def decode(self, token_ids: List[int], **kwargs) -> str:
|
||||
return self.encoding.decode(token_ids, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def eot(self) -> int:
|
||||
return self.encoding.eot_token
|
||||
|
||||
@cached_property
|
||||
def stop(self) -> int:
|
||||
return self.special_tokens["[STOP]"]
|
||||
|
||||
@cached_property
|
||||
def start(self) -> int:
|
||||
return self.special_tokens["[START]"]
|
||||
|
||||
@cached_property
|
||||
def transcribe(self) -> int:
|
||||
return self.special_tokens["[transcribe]"]
|
||||
|
||||
@cached_property
|
||||
def translate(self) -> int:
|
||||
return self.special_tokens["[translate]"]
|
||||
|
||||
@cached_property
|
||||
def sot(self) -> int:
|
||||
return self.special_tokens["[startoftranscript]"]
|
||||
|
||||
@cached_property
|
||||
def sot_lm(self) -> int:
|
||||
return self.special_tokens["[startoflm]"]
|
||||
|
||||
@cached_property
|
||||
def sot_prev(self) -> int:
|
||||
return self.special_tokens["[startofprev]"]
|
||||
|
||||
@cached_property
|
||||
def no_speech(self) -> int:
|
||||
return self.special_tokens["[nospeech]"]
|
||||
|
||||
@cached_property
|
||||
def language_token(self) -> int:
|
||||
"""Returns the token id corresponding to the value of the `language` field"""
|
||||
if self.language is None:
|
||||
raise ValueError("This tokenizer does not have language token configured")
|
||||
|
||||
return self.to_language_token(self.language)
|
||||
|
||||
def to_language_token(self, language):
|
||||
if token := self.special_tokens.get(f"[{language}]", None):
|
||||
return token
|
||||
|
||||
raise KeyError(f"Language {language} not found in tokenizer.")
|
||||
|
||||
@cached_property
|
||||
def all_language_tokens(self) -> Tuple[int]:
|
||||
result = []
|
||||
for token, token_id in self.special_tokens.items():
|
||||
if token.strip("[]") in LANGUAGES:
|
||||
result.append(token_id)
|
||||
return tuple(result)[: self.num_languages]
|
||||
|
||||
@cached_property
|
||||
def all_language_codes(self) -> Tuple[str]:
|
||||
return tuple(self.decode([_l]).strip("[]") for _l in self.all_language_tokens)
|
||||
|
||||
@cached_property
|
||||
def non_speech_tokens(self) -> Tuple[int]:
|
||||
"""
|
||||
Returns the list of tokens to suppress in order to avoid any speaker tags or non-speech
|
||||
annotations, to prevent sampling texts that are not actually spoken in the audio, e.g.
|
||||
|
||||
- ♪♪♪
|
||||
- ( SPEAKING FOREIGN LANGUAGE )
|
||||
- [DAVID] Hey there,
|
||||
|
||||
keeping basic punctuations like commas, periods, question marks, exclamation points, etc.
|
||||
"""
|
||||
symbols = list('"#()*+/:;<=>@[\\]^_`{|}~「」『』')
|
||||
symbols += (
|
||||
"<< >> <<< >>> -- --- -( -[ (' (\" (( )) ((( ))) [[ ]] {{ }} ♪♪ ♪♪♪".split()
|
||||
)
|
||||
|
||||
# symbols that may be a single token or multiple tokens depending on the tokenizer.
|
||||
# In case they're multiple tokens, suppress the first token, which is safe because:
|
||||
# These are between U+2640 and U+267F miscellaneous symbols that are okay to suppress
|
||||
# in generations, and in the 3-byte UTF-8 representation they share the first two bytes.
|
||||
miscellaneous = set("♩♪♫♬♭♮♯")
|
||||
assert all(0x2640 <= ord(c) <= 0x267F for c in miscellaneous)
|
||||
|
||||
# allow hyphens "-" and single quotes "'" between words, but not at the beginning of a word
|
||||
result = {self.encoding.encode(" -")[0], self.encoding.encode(" '")[0]}
|
||||
for symbol in symbols + list(miscellaneous):
|
||||
for tokens in [
|
||||
self.encoding.encode(symbol),
|
||||
self.encoding.encode(" " + symbol),
|
||||
]:
|
||||
if len(tokens) == 1 or symbol in miscellaneous:
|
||||
result.add(tokens[0])
|
||||
|
||||
return tuple(sorted(result))
|
||||
|
||||
def split_to_word_tokens(self, tokens: List[int]):
|
||||
if self.language in {"zh", "ja", "th", "lo", "my", "yue"}:
|
||||
# These languages don't typically use spaces, so it is difficult to split words
|
||||
# without morpheme analysis. Here, we instead split words at any
|
||||
# position where the tokens are decoded as valid unicode points
|
||||
return self.split_tokens_on_unicode(tokens)
|
||||
|
||||
return self.split_tokens_on_spaces(tokens)
|
||||
|
||||
def split_tokens_on_unicode(self, tokens: List[int]):
|
||||
decoded_full = self.decode(tokens)
|
||||
replacement_char = "\ufffd"
|
||||
|
||||
words = []
|
||||
word_tokens = []
|
||||
current_tokens = []
|
||||
unicode_offset = 0
|
||||
|
||||
for token in tokens:
|
||||
current_tokens.append(token)
|
||||
decoded = self.decode(current_tokens)
|
||||
|
||||
if (
|
||||
replacement_char not in decoded
|
||||
or decoded_full[unicode_offset + decoded.index(replacement_char)]
|
||||
== replacement_char
|
||||
):
|
||||
words.append(decoded)
|
||||
word_tokens.append(current_tokens)
|
||||
current_tokens = []
|
||||
unicode_offset += len(decoded)
|
||||
|
||||
return words, word_tokens
|
||||
|
||||
def split_tokens_on_spaces(self, tokens: List[int]):
|
||||
subwords, subword_tokens_list = self.split_tokens_on_unicode(tokens)
|
||||
words = []
|
||||
word_tokens = []
|
||||
|
||||
for subword, subword_tokens in zip(subwords, subword_tokens_list):
|
||||
special = subword_tokens[0] >= self.eot
|
||||
with_space = subword.startswith(" ")
|
||||
punctuation = subword.strip() in string.punctuation
|
||||
if special or with_space or punctuation or len(words) == 0:
|
||||
words.append(subword)
|
||||
word_tokens.append(subword_tokens)
|
||||
else:
|
||||
words[-1] = words[-1] + subword
|
||||
word_tokens[-1].extend(subword_tokens)
|
||||
|
||||
return words, word_tokens
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_encoding(name: str = "multilingual", num_languages: int = 100):
|
||||
vocab_path = os.path.join(os.path.dirname(__file__), "assets", f"{name}.tiktoken")
|
||||
ranks = {
|
||||
base64.b64decode(token): int(rank)
|
||||
for token, rank in (line.split() for line in open(vocab_path) if line)
|
||||
}
|
||||
n_vocab = len(ranks)
|
||||
special_tokens = {}
|
||||
|
||||
specials = [
|
||||
"[STOP]",
|
||||
"[UNK]",
|
||||
"[SPACE]",
|
||||
"[START]",
|
||||
"[nospk]",
|
||||
"[spkemb]",
|
||||
"[emotionemb]",
|
||||
"[contextemb]",
|
||||
"[sbreak]",
|
||||
"[pbreak]",
|
||||
"[uvbreak]",
|
||||
"[bsing]",
|
||||
"[esing]",
|
||||
"[sing]",
|
||||
"[hum]",
|
||||
"[laugh]",
|
||||
"[break]",
|
||||
"[breath]",
|
||||
"[oralsii]",
|
||||
"[oralze]",
|
||||
"[prolong]",
|
||||
"[stress]",
|
||||
"[bstrong]",
|
||||
"[estrong]",
|
||||
"[hiccup]",
|
||||
"[inhale]",
|
||||
"[exhale]",
|
||||
"[emounknown]",
|
||||
"[happy]",
|
||||
"[neutral]",
|
||||
"[sad]",
|
||||
"[surprise]",
|
||||
"[angry]",
|
||||
"[disgust]",
|
||||
"[emo]",
|
||||
"[laugha]",
|
||||
"[laughb]",
|
||||
"[laughc]",
|
||||
"[orala]",
|
||||
"[oralb]",
|
||||
"[oralc]",
|
||||
"[orald]",
|
||||
"[orale]",
|
||||
"[breaka]",
|
||||
"[breakb]",
|
||||
"[breakc]",
|
||||
"[breakd]",
|
||||
"[breake]",
|
||||
"[breakf]",
|
||||
"[endoftext]",
|
||||
"[startoftranscript]",
|
||||
*[f"[{lang}]" for lang in list(LANGUAGES.keys())[:num_languages]],
|
||||
"[translate]",
|
||||
"[transcribe]",
|
||||
"[startoflm]",
|
||||
"[startofprev]",
|
||||
"[nospeech]",
|
||||
]
|
||||
|
||||
for token in specials:
|
||||
special_tokens[token] = n_vocab
|
||||
n_vocab += 1
|
||||
|
||||
return tiktoken.Encoding(
|
||||
name=os.path.basename(vocab_path),
|
||||
explicit_n_vocab=n_vocab,
|
||||
pat_str=r"""'s|'t|'re|'ve|'m|'ll|'d|\[[A-Z]+\]|\[[a-z]+\]|[\x{4e00}-\x{9df5}]| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""",
|
||||
mergeable_ranks=ranks,
|
||||
special_tokens=special_tokens,
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_tokenizer(
|
||||
multilingual: bool,
|
||||
*,
|
||||
num_languages: int = 100,
|
||||
language: Optional[str] = None,
|
||||
task: Optional[str] = None, # Literal["transcribe", "translate", None]
|
||||
) -> Tokenizer:
|
||||
if language is not None:
|
||||
language = language.lower()
|
||||
if language not in LANGUAGES:
|
||||
if language in TO_LANGUAGE_CODE:
|
||||
language = TO_LANGUAGE_CODE[language]
|
||||
else:
|
||||
raise ValueError(f"Unsupported language: {language}")
|
||||
|
||||
if multilingual:
|
||||
encoding_name = "multilingual"
|
||||
language = language or "en"
|
||||
task = task or "transcribe"
|
||||
else:
|
||||
encoding_name = "gpt2"
|
||||
language = None
|
||||
task = None
|
||||
|
||||
encoding = get_encoding(name=encoding_name, num_languages=num_languages)
|
||||
|
||||
return Tokenizer(
|
||||
encoding=encoding, num_languages=num_languages, language=language, task=task
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torchaudio
|
||||
|
||||
|
||||
def load_audio(audiopath, sampling_rate):
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
audiopath (_type_): audio_path
|
||||
sampling_rate (_type_): sampling_rate
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
audio, lsr = torchaudio.load(audiopath)
|
||||
|
||||
# stereo to mono if needed
|
||||
if audio.size(0) != 1:
|
||||
audio = torch.mean(audio, dim=0, keepdim=True)
|
||||
|
||||
# resample
|
||||
audio_resampled = torchaudio.functional.resample(audio, lsr, sampling_rate)
|
||||
if torch.any(audio > 10) or not torch.any(audio < 0):
|
||||
print(f"Error with {audiopath}. Max={audio.max()} min={audio.min()}")
|
||||
|
||||
if torch.any(audio_resampled > 10) or not torch.any(audio_resampled < 0):
|
||||
print(
|
||||
f"Error with {audiopath}. Max={audio_resampled.max()} min={audio_resampled.min()}"
|
||||
)
|
||||
# clip audio invalid values
|
||||
audio.clip_(-1, 1)
|
||||
audio_resampled.clip_(-1, 1)
|
||||
return audio, lsr, audio_resampled
|
||||
@@ -0,0 +1,12 @@
|
||||
diffusers
|
||||
librosa
|
||||
soundfile
|
||||
einops
|
||||
transformers
|
||||
tiktoken
|
||||
inflect
|
||||
lingua-language-detector
|
||||
WeTextProcessing
|
||||
pydub
|
||||
zhon
|
||||
ffmpeg-python
|
||||
Reference in New Issue
Block a user