gz412 commited on
Commit
a3f711d
·
1 Parent(s): bbbdf13

add base and zjg

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. app.py +80 -0
  3. asset/F01_中立_20054.wav +3 -0
  4. asset/sg_017_090.wav +3 -0
  5. cosyvoice/__init__.py +0 -0
  6. cosyvoice/__pycache__/__init__.cpython-310.pyc +0 -0
  7. cosyvoice/bin/average_model.py +93 -0
  8. cosyvoice/bin/export_jit.py +103 -0
  9. cosyvoice/bin/export_onnx.py +121 -0
  10. cosyvoice/bin/inference_deprecated.py +126 -0
  11. cosyvoice/bin/train.py +202 -0
  12. cosyvoice/cli/__init__.py +0 -0
  13. cosyvoice/cli/__pycache__/__init__.cpython-310.pyc +0 -0
  14. cosyvoice/cli/__pycache__/cosyvoice.cpython-310.pyc +0 -0
  15. cosyvoice/cli/__pycache__/frontend.cpython-310.pyc +0 -0
  16. cosyvoice/cli/__pycache__/model.cpython-310.pyc +0 -0
  17. cosyvoice/cli/cosyvoice.py +194 -0
  18. cosyvoice/cli/frontend.py +215 -0
  19. cosyvoice/cli/model.py +386 -0
  20. cosyvoice/dataset/__init__.py +0 -0
  21. cosyvoice/dataset/__pycache__/__init__.cpython-310.pyc +0 -0
  22. cosyvoice/dataset/__pycache__/custom_processor.cpython-310.pyc +0 -0
  23. cosyvoice/dataset/__pycache__/dataset.cpython-310.pyc +0 -0
  24. cosyvoice/dataset/__pycache__/processor.cpython-310.pyc +0 -0
  25. cosyvoice/dataset/custom_processor.py +494 -0
  26. cosyvoice/dataset/dataset.py +151 -0
  27. cosyvoice/dataset/processor.py +434 -0
  28. cosyvoice/flow/__pycache__/decoder.cpython-310.pyc +0 -0
  29. cosyvoice/flow/__pycache__/flow.cpython-310.pyc +0 -0
  30. cosyvoice/flow/__pycache__/flow_matching.cpython-310.pyc +0 -0
  31. cosyvoice/flow/decoder.py +494 -0
  32. cosyvoice/flow/flow.py +281 -0
  33. cosyvoice/flow/flow_matching.py +227 -0
  34. cosyvoice/flow/length_regulator.py +70 -0
  35. cosyvoice/hifigan/__pycache__/discriminator.cpython-310.pyc +0 -0
  36. cosyvoice/hifigan/__pycache__/f0_predictor.cpython-310.pyc +0 -0
  37. cosyvoice/hifigan/__pycache__/generator.cpython-310.pyc +0 -0
  38. cosyvoice/hifigan/__pycache__/hifigan.cpython-310.pyc +0 -0
  39. cosyvoice/hifigan/discriminator.py +230 -0
  40. cosyvoice/hifigan/f0_predictor.py +58 -0
  41. cosyvoice/hifigan/generator.py +582 -0
  42. cosyvoice/hifigan/hifigan.py +67 -0
  43. cosyvoice/llm/__pycache__/llm.cpython-310.pyc +0 -0
  44. cosyvoice/llm/llm.py +611 -0
  45. cosyvoice/tokenizer/__pycache__/tokenizer.cpython-310.pyc +0 -0
  46. cosyvoice/tokenizer/assets/multilingual_zh_ja_yue_char_del.tiktoken +0 -0
  47. cosyvoice/tokenizer/tokenizer.py +279 -0
  48. cosyvoice/transformer/__init__.py +0 -0
  49. cosyvoice/transformer/__pycache__/__init__.cpython-310.pyc +0 -0
  50. cosyvoice/transformer/__pycache__/activation.cpython-310.pyc +0 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.wav filter=lfs diff=lfs merge=lfs -text
app.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import torch
3
+ import gradio as gr
4
+ import opencc
5
+
6
+ # 添加第三方库路径
7
+ sys.path.append('third_party/Matcha-TTS')
8
+
9
+ from cosyvoice.cli.cosyvoice import CosyVoice2
10
+ from cosyvoice.utils.file_utils import load_wav
11
+
12
+ # 繁简转换
13
+ converter = opencc.OpenCC('s2t.json')
14
+
15
+ # 加载模型
16
+ cosyvoice_base = CosyVoice2(
17
+ 'ASLP-lab/WSYue-TTS-Cosyvoice2',
18
+ load_jit=False, load_trt=False, load_vllm=False, fp16=False
19
+ )
20
+ cosyvoice_zjg = CosyVoice2(
21
+ 'ASLP-lab/WSYue-TTS-Cosyvoice2-zjg',
22
+ load_jit=False, load_trt=False, load_vllm=False, fp16=False
23
+ )
24
+ # cosyvoice_biaobei = CosyVoice2(
25
+ # 'pretrained_models/CosyVoice2-yue-biaobei',
26
+ # load_jit=False, load_trt=False, load_vllm=False, fp16=False
27
+ # )
28
+
29
+ def tts_inference(model_choice, text, prompt_audio):
30
+ # 选择模型和默认音频
31
+ if model_choice == "CosyVoice2-张悦楷粤语评书":
32
+ model = cosyvoice_zjg
33
+ prompt_audio = "asset/default_zjg.wav"
34
+ elif model_choice == "CosyVoice2-精品女音":
35
+ model = cosyvoice_base
36
+ prompt_audio = "asset/default_biaobei.wav"
37
+ elif model_choice == "CosyVoice2-base":
38
+ model = cosyvoice_base
39
+ if prompt_audio is None:
40
+ return None, "请上传参考音频"
41
+ else:
42
+ return None, "未知模型"
43
+
44
+ # 繁简转换
45
+ text = converter.convert(text)
46
+ prompt_speech_16k = load_wav(prompt_audio, 16000)
47
+
48
+ all_speech = []
49
+ for _, j in enumerate(
50
+ model.inference_instruct2(
51
+ text, "用粤语说这句话", prompt_speech_16k, stream=False
52
+ )
53
+ ):
54
+ all_speech.append(j['tts_speech'])
55
+
56
+ concatenated_speech = torch.cat(all_speech, dim=1)
57
+ audio_numpy = concatenated_speech.squeeze(0).cpu().numpy()
58
+ sample_rate = model.sample_rate
59
+
60
+ return (sample_rate, audio_numpy), f"生成成功:{text}"
61
+
62
+
63
+ # ---- Gradio Interface ----
64
+ demo = gr.Interface(
65
+ fn=tts_inference,
66
+ inputs=[
67
+ gr.Dropdown(
68
+ ["CosyVoice2-base", "CosyVoice2-张悦楷粤语评书", "CosyVoice2-精品女音"],
69
+ label="选择模型", value="CosyVoice2-base"
70
+ ),
71
+ gr.Textbox(lines=2, label="输入文本"),
72
+ gr.Audio(source="upload", type="filepath", label="上传参考音频(仅 CosyVoice2-base 必需)")
73
+ ],
74
+ outputs=[
75
+ gr.Audio(type="numpy", label="生成的语音"),
76
+ gr.Textbox(label="状态信息")
77
+ ]
78
+ )
79
+
80
+ demo.launch()
asset/F01_中立_20054.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0dfb5cd8ebda4ff5d385e8bbdbf5a24f1a76f3f2f562afba1c807d3f3f8444a6
3
+ size 1050752
asset/sg_017_090.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ae70a87f4c9714d4ab5d1b176137ffde74520dd82140c31ef3b8a9f7d5ef260
3
+ size 971564
cosyvoice/__init__.py ADDED
File without changes
cosyvoice/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (152 Bytes). View file
 
cosyvoice/bin/average_model.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2020 Mobvoi Inc (Di Wu)
2
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import os
17
+ import argparse
18
+ import glob
19
+
20
+ import yaml
21
+ import torch
22
+
23
+
24
+ def get_args():
25
+ parser = argparse.ArgumentParser(description='average model')
26
+ parser.add_argument('--dst_model', required=True, help='averaged model')
27
+ parser.add_argument('--src_path',
28
+ required=True,
29
+ help='src model path for average')
30
+ parser.add_argument('--val_best',
31
+ action="store_true",
32
+ help='averaged model')
33
+ parser.add_argument('--num',
34
+ default=5,
35
+ type=int,
36
+ help='nums for averaged model')
37
+
38
+ args = parser.parse_args()
39
+ print(args)
40
+ return args
41
+
42
+
43
+ def main():
44
+ args = get_args()
45
+ val_scores = []
46
+ if args.val_best:
47
+ yamls = glob.glob('{}/*.yaml'.format(args.src_path))
48
+ yamls = [
49
+ f for f in yamls
50
+ if not (os.path.basename(f).startswith('train')
51
+ or os.path.basename(f).startswith('init'))
52
+ ]
53
+ for y in yamls:
54
+ with open(y, 'r') as f:
55
+ dic_yaml = yaml.load(f, Loader=yaml.BaseLoader)
56
+ loss = float(dic_yaml['loss_dict']['loss'])
57
+ epoch = int(dic_yaml['epoch'])
58
+ step = int(dic_yaml['step'])
59
+ tag = dic_yaml['tag']
60
+ val_scores += [[epoch, step, loss, tag]]
61
+ sorted_val_scores = sorted(val_scores,
62
+ key=lambda x: x[2],
63
+ reverse=False)
64
+ print("best val (epoch, step, loss, tag) = " +
65
+ str(sorted_val_scores[:args.num]))
66
+ path_list = [
67
+ args.src_path + '/epoch_{}_whole.pt'.format(score[0])
68
+ for score in sorted_val_scores[:args.num]
69
+ ]
70
+ print(path_list)
71
+ avg = {}
72
+ num = args.num
73
+ assert num == len(path_list)
74
+ for path in path_list:
75
+ print('Processing {}'.format(path))
76
+ states = torch.load(path, map_location=torch.device('cpu'))
77
+ for k in states.keys():
78
+ if k not in ['step', 'epoch']:
79
+ if k not in avg.keys():
80
+ avg[k] = states[k].clone()
81
+ else:
82
+ avg[k] += states[k]
83
+ # average
84
+ for k in avg.keys():
85
+ if avg[k] is not None:
86
+ # pytorch 1.6 use true_divide instead of /=
87
+ avg[k] = torch.true_divide(avg[k], num)
88
+ print('Saving to {}'.format(args.dst_model))
89
+ torch.save(avg, args.dst_model)
90
+
91
+
92
+ if __name__ == '__main__':
93
+ main()
cosyvoice/bin/export_jit.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import print_function
16
+
17
+ import argparse
18
+ import logging
19
+ logging.getLogger('matplotlib').setLevel(logging.WARNING)
20
+ import os
21
+ import sys
22
+ import torch
23
+ ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
24
+ sys.path.append('{}/../..'.format(ROOT_DIR))
25
+ sys.path.append('{}/../../third_party/Matcha-TTS'.format(ROOT_DIR))
26
+ from cosyvoice.cli.cosyvoice import CosyVoice, CosyVoice2
27
+ from cosyvoice.utils.file_utils import logging
28
+
29
+
30
+ def get_args():
31
+ parser = argparse.ArgumentParser(description='export your model for deployment')
32
+ parser.add_argument('--model_dir',
33
+ type=str,
34
+ default='pretrained_models/CosyVoice-300M',
35
+ help='local path')
36
+ args = parser.parse_args()
37
+ print(args)
38
+ return args
39
+
40
+
41
+ def get_optimized_script(model, preserved_attrs=[]):
42
+ script = torch.jit.script(model)
43
+ if preserved_attrs != []:
44
+ script = torch.jit.freeze(script, preserved_attrs=preserved_attrs)
45
+ else:
46
+ script = torch.jit.freeze(script)
47
+ script = torch.jit.optimize_for_inference(script)
48
+ return script
49
+
50
+
51
+ def main():
52
+ args = get_args()
53
+ logging.basicConfig(level=logging.DEBUG,
54
+ format='%(asctime)s %(levelname)s %(message)s')
55
+
56
+ torch._C._jit_set_fusion_strategy([('STATIC', 1)])
57
+ torch._C._jit_set_profiling_mode(False)
58
+ torch._C._jit_set_profiling_executor(False)
59
+
60
+ # try:
61
+ # model = CosyVoice(args.model_dir)
62
+ # except Exception:
63
+ try:
64
+ model = CosyVoice2(args.model_dir)
65
+ except Exception:
66
+ raise TypeError('no valid model_type!')
67
+
68
+ if not isinstance(model, CosyVoice2):
69
+ # 1. export llm text_encoder
70
+ llm_text_encoder = model.model.llm.text_encoder
71
+ script = get_optimized_script(llm_text_encoder)
72
+ script.save('{}/llm.text_encoder.fp32.zip'.format(args.model_dir))
73
+ script = get_optimized_script(llm_text_encoder.half())
74
+ script.save('{}/llm.text_encoder.fp16.zip'.format(args.model_dir))
75
+ logging.info('successfully export llm_text_encoder')
76
+
77
+ # 2. export llm llm
78
+ llm_llm = model.model.llm.llm
79
+ script = get_optimized_script(llm_llm, ['forward_chunk'])
80
+ script.save('{}/llm.llm.fp32.zip'.format(args.model_dir))
81
+ script = get_optimized_script(llm_llm.half(), ['forward_chunk'])
82
+ script.save('{}/llm.llm.fp16.zip'.format(args.model_dir))
83
+ logging.info('successfully export llm_llm')
84
+
85
+ # 3. export flow encoder
86
+ flow_encoder = model.model.flow.encoder
87
+ script = get_optimized_script(flow_encoder)
88
+ script.save('{}/flow.encoder.fp32.zip'.format(args.model_dir))
89
+ script = get_optimized_script(flow_encoder.half())
90
+ script.save('{}/flow.encoder.fp16.zip'.format(args.model_dir))
91
+ logging.info('successfully export flow_encoder')
92
+ else:
93
+ # 3. export flow encoder
94
+ flow_encoder = model.model.flow.encoder
95
+ script = get_optimized_script(flow_encoder)
96
+ script.save('{}/flow.encoder.fp32.zip'.format(args.model_dir))
97
+ script = get_optimized_script(flow_encoder.half())
98
+ script.save('{}/flow.encoder.fp16.zip'.format(args.model_dir))
99
+ logging.info('successfully export flow_encoder')
100
+
101
+
102
+ if __name__ == '__main__':
103
+ main()
cosyvoice/bin/export_onnx.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Antgroup Inc (authors: Zhoubofan, [email protected])
2
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import print_function
17
+
18
+ import argparse
19
+ import logging
20
+ logging.getLogger('matplotlib').setLevel(logging.WARNING)
21
+ import os
22
+ import sys
23
+ import onnxruntime
24
+ import random
25
+ import torch
26
+ from tqdm import tqdm
27
+ ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
28
+ sys.path.append('{}/../..'.format(ROOT_DIR))
29
+ sys.path.append('{}/../../third_party/Matcha-TTS'.format(ROOT_DIR))
30
+ sys.path.append('{}/../../../third_party/Matcha-TTS'.format(ROOT_DIR))
31
+ from cosyvoice.cli.cosyvoice import CosyVoice, CosyVoice2
32
+ from cosyvoice.utils.file_utils import logging
33
+
34
+
35
+ def get_dummy_input(batch_size, seq_len, out_channels, device):
36
+ x = torch.rand((batch_size, out_channels, seq_len), dtype=torch.float32, device=device)
37
+ mask = torch.ones((batch_size, 1, seq_len), dtype=torch.float32, device=device)
38
+ mu = torch.rand((batch_size, out_channels, seq_len), dtype=torch.float32, device=device)
39
+ t = torch.rand((batch_size), dtype=torch.float32, device=device)
40
+ spks = torch.rand((batch_size, out_channels), dtype=torch.float32, device=device)
41
+ cond = torch.rand((batch_size, out_channels, seq_len), dtype=torch.float32, device=device)
42
+ return x, mask, mu, t, spks, cond
43
+
44
+
45
+ def get_args():
46
+ parser = argparse.ArgumentParser(description='export your model for deployment')
47
+ parser.add_argument('--model_dir',
48
+ type=str,
49
+ default='pretrained_models/CosyVoice-300M',
50
+ help='local path')
51
+ args = parser.parse_args()
52
+ print(args)
53
+ return args
54
+
55
+
56
+ @torch.no_grad()
57
+ def main():
58
+ args = get_args()
59
+ logging.basicConfig(level=logging.DEBUG,
60
+ format='%(asctime)s %(levelname)s %(message)s')
61
+
62
+ try:
63
+ model = CosyVoice(args.model_dir)
64
+ except Exception:
65
+ try:
66
+ model = CosyVoice2(args.model_dir)
67
+ except Exception:
68
+ raise TypeError('no valid model_type!')
69
+
70
+ # 1. export flow decoder estimator
71
+ estimator = model.model.flow.decoder.estimator
72
+ estimator.eval()
73
+
74
+ device = model.model.device
75
+ batch_size, seq_len = 2, 256
76
+ out_channels = model.model.flow.decoder.estimator.out_channels
77
+ x, mask, mu, t, spks, cond = get_dummy_input(batch_size, seq_len, out_channels, device)
78
+ torch.onnx.export(
79
+ estimator,
80
+ (x, mask, mu, t, spks, cond),
81
+ '{}/flow.decoder.estimator.fp32.onnx'.format(args.model_dir),
82
+ export_params=True,
83
+ opset_version=18,
84
+ do_constant_folding=True,
85
+ input_names=['x', 'mask', 'mu', 't', 'spks', 'cond'],
86
+ output_names=['estimator_out'],
87
+ dynamic_axes={
88
+ 'x': {2: 'seq_len'},
89
+ 'mask': {2: 'seq_len'},
90
+ 'mu': {2: 'seq_len'},
91
+ 'cond': {2: 'seq_len'},
92
+ 'estimator_out': {2: 'seq_len'},
93
+ }
94
+ )
95
+
96
+ # 2. test computation consistency
97
+ option = onnxruntime.SessionOptions()
98
+ option.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
99
+ option.intra_op_num_threads = 1
100
+ providers = ['CUDAExecutionProvider' if torch.cuda.is_available() else 'CPUExecutionProvider']
101
+ estimator_onnx = onnxruntime.InferenceSession('{}/flow.decoder.estimator.fp32.onnx'.format(args.model_dir),
102
+ sess_options=option, providers=providers)
103
+
104
+ for _ in tqdm(range(10)):
105
+ x, mask, mu, t, spks, cond = get_dummy_input(batch_size, random.randint(16, 512), out_channels, device)
106
+ output_pytorch = estimator(x, mask, mu, t, spks, cond)
107
+ ort_inputs = {
108
+ 'x': x.cpu().numpy(),
109
+ 'mask': mask.cpu().numpy(),
110
+ 'mu': mu.cpu().numpy(),
111
+ 't': t.cpu().numpy(),
112
+ 'spks': spks.cpu().numpy(),
113
+ 'cond': cond.cpu().numpy()
114
+ }
115
+ output_onnx = estimator_onnx.run(None, ort_inputs)[0]
116
+ torch.testing.assert_allclose(output_pytorch, torch.from_numpy(output_onnx).to(device), rtol=1e-2, atol=1e-4)
117
+ logging.info('successfully export estimator')
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
cosyvoice/bin/inference_deprecated.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import print_function
16
+
17
+ import argparse
18
+ import logging
19
+ logging.getLogger('matplotlib').setLevel(logging.WARNING)
20
+ import os
21
+ import torch
22
+ from torch.utils.data import DataLoader
23
+ import torchaudio
24
+ from hyperpyyaml import load_hyperpyyaml
25
+ from tqdm import tqdm
26
+ from cosyvoice.cli.model import CosyVoiceModel, CosyVoice2Model
27
+ from cosyvoice.dataset.dataset import Dataset
28
+
29
+
30
+ def get_args():
31
+ parser = argparse.ArgumentParser(description='inference with your model')
32
+ parser.add_argument('--config', required=True, help='config file')
33
+ parser.add_argument('--prompt_data', required=True, help='prompt data file')
34
+ parser.add_argument('--prompt_utt2data', required=True, help='prompt data file')
35
+ parser.add_argument('--tts_text', required=True, help='tts input file')
36
+ parser.add_argument('--qwen_pretrain_path', required=False, help='qwen pretrain path')
37
+ parser.add_argument('--llm_model', required=True, help='llm model file')
38
+ parser.add_argument('--flow_model', required=True, help='flow model file')
39
+ parser.add_argument('--hifigan_model', required=True, help='hifigan model file')
40
+ parser.add_argument('--gpu',
41
+ type=int,
42
+ default=-1,
43
+ help='gpu id for this rank, -1 for cpu')
44
+ parser.add_argument('--mode',
45
+ default='sft',
46
+ choices=['sft', 'zero_shot'],
47
+ help='inference mode')
48
+ parser.add_argument('--result_dir', required=True, help='asr result file')
49
+ args = parser.parse_args()
50
+ print(args)
51
+ return args
52
+
53
+
54
+ def main():
55
+ args = get_args()
56
+ logging.basicConfig(level=logging.DEBUG,
57
+ format='%(asctime)s %(levelname)s %(message)s')
58
+ os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
59
+
60
+ # Init cosyvoice models from configs
61
+ use_cuda = args.gpu >= 0 and torch.cuda.is_available()
62
+ device = torch.device('cuda' if use_cuda else 'cpu')
63
+ try:
64
+ with open(args.config, 'r') as f:
65
+ configs = load_hyperpyyaml(f, overrides={'qwen_pretrain_path': args.qwen_pretrain_path})
66
+ model = CosyVoice2Model(configs['llm'], configs['flow'], configs['hift'])
67
+ except Exception:
68
+ try:
69
+ with open(args.config, 'r') as f:
70
+ configs = load_hyperpyyaml(f)
71
+ model = CosyVoiceModel(configs['llm'], configs['flow'], configs['hift'])
72
+ except Exception:
73
+ raise TypeError('no valid model_type!')
74
+
75
+ model.load(args.llm_model, args.flow_model, args.hifigan_model)
76
+
77
+ test_dataset = Dataset(args.prompt_data, data_pipeline=configs['data_pipeline'], mode='inference', shuffle=False, partition=False,
78
+ tts_file=args.tts_text, prompt_utt2data=args.prompt_utt2data)
79
+ test_data_loader = DataLoader(test_dataset, batch_size=None, num_workers=0)
80
+
81
+ sample_rate = configs['sample_rate']
82
+ del configs
83
+ os.makedirs(args.result_dir, exist_ok=True)
84
+ fn = os.path.join(args.result_dir, 'wav.scp')
85
+ f = open(fn, 'w')
86
+ with torch.no_grad():
87
+ for _, batch in tqdm(enumerate(test_data_loader)):
88
+ utts = batch["utts"]
89
+ assert len(utts) == 1, "inference mode only support batchsize 1"
90
+ text_token = batch["text_token"].to(device)
91
+ text_token_len = batch["text_token_len"].to(device)
92
+ tts_index = batch["tts_index"]
93
+ tts_text_token = batch["tts_text_token"].to(device)
94
+ tts_text_token_len = batch["tts_text_token_len"].to(device)
95
+ speech_token = batch["speech_token"].to(device)
96
+ speech_token_len = batch["speech_token_len"].to(device)
97
+ speech_feat = batch["speech_feat"].to(device)
98
+ speech_feat_len = batch["speech_feat_len"].to(device)
99
+ utt_embedding = batch["utt_embedding"].to(device)
100
+ spk_embedding = batch["spk_embedding"].to(device)
101
+ if args.mode == 'sft':
102
+ model_input = {'text': tts_text_token, 'text_len': tts_text_token_len,
103
+ 'llm_embedding': spk_embedding, 'flow_embedding': spk_embedding}
104
+ else:
105
+ model_input = {'text': tts_text_token, 'text_len': tts_text_token_len,
106
+ 'prompt_text': text_token, 'prompt_text_len': text_token_len,
107
+ 'llm_prompt_speech_token': speech_token, 'llm_prompt_speech_token_len': speech_token_len,
108
+ 'flow_prompt_speech_token': speech_token, 'flow_prompt_speech_token_len': speech_token_len,
109
+ 'prompt_speech_feat': speech_feat, 'prompt_speech_feat_len': speech_feat_len,
110
+ 'llm_embedding': utt_embedding, 'flow_embedding': utt_embedding}
111
+ tts_speeches = []
112
+ for model_output in model.tts(**model_input):
113
+ tts_speeches.append(model_output['tts_speech'])
114
+ tts_speeches = torch.concat(tts_speeches, dim=1)
115
+ tts_key = '{}_{}'.format(utts[0], tts_index[0])
116
+ tts_fn = os.path.join(args.result_dir, '{}.wav'.format(tts_key))
117
+ torchaudio.save(tts_fn, tts_speeches, sample_rate=sample_rate, backend='soundfile')
118
+ f.write('{} {}\n'.format(tts_key, tts_fn))
119
+ f.flush()
120
+ f.close()
121
+ logging.info('Result wav.scp saved in {}'.format(fn))
122
+
123
+
124
+ if __name__ == '__main__':
125
+ logging.warning('this code has been deprecated, please refer to README for CosyVoice inference usage!')
126
+ main()
cosyvoice/bin/train.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import print_function
16
+ import argparse
17
+ import datetime
18
+ import logging
19
+ logging.getLogger('matplotlib').setLevel(logging.WARNING)
20
+ from copy import deepcopy
21
+ import os
22
+ import torch
23
+ import torch.distributed as dist
24
+ import deepspeed
25
+
26
+ from hyperpyyaml import load_hyperpyyaml
27
+
28
+ from torch.distributed.elastic.multiprocessing.errors import record
29
+
30
+ from cosyvoice.utils.losses import DPOLoss
31
+ from cosyvoice.utils.executor import Executor
32
+ from cosyvoice.utils.train_utils import (
33
+ init_distributed,
34
+ init_dataset_and_dataloader,
35
+ init_optimizer_and_scheduler,
36
+ init_summarywriter, save_model,
37
+ wrap_cuda_model, check_modify_and_save_config)
38
+
39
+
40
+ def get_args():
41
+ parser = argparse.ArgumentParser(description='training your network')
42
+ parser.add_argument('--train_engine',
43
+ default='torch_ddp',
44
+ choices=['torch_ddp', 'deepspeed'],
45
+ help='Engine for paralleled training')
46
+ parser.add_argument('--model', required=True, help='model which will be trained')
47
+ parser.add_argument('--ref_model', required=False, help='ref model used in dpo')
48
+ parser.add_argument('--config', required=True, help='config file')
49
+ parser.add_argument('--train_data', required=True, help='train data file')
50
+ parser.add_argument('--cv_data', required=True, help='cv data file')
51
+ parser.add_argument('--qwen_pretrain_path', required=False, help='qwen pretrain path')
52
+ parser.add_argument('--checkpoint', help='checkpoint model')
53
+ parser.add_argument('--model_dir', required=True, help='save model dir')
54
+ parser.add_argument('--tensorboard_dir',
55
+ default='tensorboard',
56
+ help='tensorboard log dir')
57
+ parser.add_argument('--ddp.dist_backend',
58
+ dest='dist_backend',
59
+ default='nccl',
60
+ choices=['nccl', 'gloo'],
61
+ help='distributed backend')
62
+ parser.add_argument('--num_workers',
63
+ default=0,
64
+ type=int,
65
+ help='num of subprocess workers for reading')
66
+ parser.add_argument('--prefetch',
67
+ default=100,
68
+ type=int,
69
+ help='prefetch number')
70
+ parser.add_argument('--pin_memory',
71
+ action='store_true',
72
+ default=False,
73
+ help='Use pinned memory buffers used for reading')
74
+ parser.add_argument('--use_amp',
75
+ action='store_true',
76
+ default=False,
77
+ help='Use automatic mixed precision training')
78
+ parser.add_argument('--dpo',
79
+ action='store_true',
80
+ default=False,
81
+ help='Use Direct Preference Optimization')
82
+ parser.add_argument('--deepspeed.save_states',
83
+ dest='save_states',
84
+ default='model_only',
85
+ choices=['model_only', 'model+optimizer'],
86
+ help='save model/optimizer states')
87
+ parser.add_argument('--timeout',
88
+ default=60,
89
+ type=int,
90
+ help='timeout (in seconds) of cosyvoice_join.')
91
+ parser = deepspeed.add_config_arguments(parser)
92
+ args = parser.parse_args()
93
+ return args
94
+
95
+
96
+ @record
97
+ def main():
98
+ args = get_args()
99
+ logging.basicConfig(level=logging.DEBUG,
100
+ format='%(asctime)s %(levelname)s %(message)s')
101
+ # gan train has some special initialization logic
102
+ gan = True if args.model == 'hifigan' else False
103
+
104
+ override_dict = {k: None for k in ['llm', 'flow', 'hift', 'hifigan'] if k != args.model}
105
+ if gan is True:
106
+ override_dict.pop('hift')
107
+ try:
108
+ with open(args.config, 'r') as f:
109
+ configs = load_hyperpyyaml(f, overrides={**override_dict, 'qwen_pretrain_path': args.qwen_pretrain_path})
110
+ except Exception:
111
+ with open(args.config, 'r') as f:
112
+ configs = load_hyperpyyaml(f, overrides=override_dict)
113
+ if gan is True:
114
+ configs['train_conf'] = configs['train_conf_gan']
115
+ configs['train_conf'].update(vars(args))
116
+
117
+ # Init env for ddp
118
+ init_distributed(args)
119
+
120
+ # Get dataset & dataloader
121
+ train_dataset, cv_dataset, train_data_loader, cv_data_loader = \
122
+ init_dataset_and_dataloader(args, configs, gan, args.dpo)
123
+
124
+ # Do some sanity checks and save config to arsg.model_dir
125
+ configs = check_modify_and_save_config(args, configs)
126
+
127
+ # Tensorboard summary
128
+ writer = init_summarywriter(args)
129
+
130
+ # load checkpoint
131
+ if args.dpo is True:
132
+ configs[args.model].forward = configs[args.model].forward_dpo
133
+ model = configs[args.model]
134
+ start_step, start_epoch = 0, -1
135
+ if args.checkpoint is not None:
136
+ if os.path.exists(args.checkpoint):
137
+ state_dict = torch.load(args.checkpoint, map_location='cpu')
138
+ model.load_state_dict(state_dict, strict=False)
139
+ if 'step' in state_dict:
140
+ start_step = state_dict['step']
141
+ if 'epoch' in state_dict:
142
+ start_epoch = state_dict['epoch']
143
+ else:
144
+ logging.warning('checkpoint {} do not exsist!'.format(args.checkpoint))
145
+
146
+ # Dispatch model from cpu to gpu
147
+ model = wrap_cuda_model(args, model)
148
+
149
+ # Get optimizer & scheduler
150
+ model, optimizer, scheduler, optimizer_d, scheduler_d = init_optimizer_and_scheduler(args, configs, model, gan)
151
+ scheduler.set_step(start_step)
152
+ if scheduler_d is not None:
153
+ scheduler_d.set_step(start_step)
154
+
155
+ # Save init checkpoints
156
+ info_dict = deepcopy(configs['train_conf'])
157
+ info_dict['step'] = start_step
158
+ info_dict['epoch'] = start_epoch
159
+ save_model(model, 'init', info_dict)
160
+
161
+ # DPO related
162
+ if args.dpo is True:
163
+ ref_model = deepcopy(configs[args.model])
164
+ state_dict = torch.load(args.ref_model, map_location='cpu')
165
+ ref_model.load_state_dict(state_dict, strict=False)
166
+ dpo_loss = DPOLoss(beta=0.01, label_smoothing=0.0, ipo=False)
167
+ # NOTE maybe it is not needed to wrap ref_model as ddp because its parameter is not updated
168
+ ref_model = wrap_cuda_model(args, ref_model)
169
+ else:
170
+ ref_model, dpo_loss = None, None
171
+
172
+ # Get executor
173
+ executor = Executor(gan=gan, ref_model=ref_model, dpo_loss=dpo_loss)
174
+ executor.step = start_step
175
+
176
+ # Init scaler, used for pytorch amp mixed precision training
177
+ scaler = torch.cuda.amp.GradScaler() if args.use_amp else None
178
+ print('start step {} start epoch {}'.format(start_step, start_epoch))
179
+
180
+ # Start training loop
181
+ for epoch in range(start_epoch + 1, info_dict['max_epoch']):
182
+ executor.epoch = epoch
183
+ train_dataset.set_epoch(epoch)
184
+ dist.barrier()
185
+ group_join = dist.new_group(backend="gloo", timeout=datetime.timedelta(seconds=args.timeout))
186
+ if gan is True:
187
+ executor.train_one_epoc_gan(model, optimizer, scheduler, optimizer_d, scheduler_d, train_data_loader, cv_data_loader,
188
+ writer, info_dict, scaler, group_join)
189
+ else:
190
+ executor.train_one_epoc(model, optimizer, scheduler, train_data_loader, cv_data_loader, writer, info_dict, scaler, group_join, ref_model=ref_model)
191
+ dist.destroy_process_group(group_join)
192
+
193
+
194
+ if __name__ == '__main__':
195
+ try:
196
+ main()
197
+ except:
198
+ import os, traceback, sys
199
+ print("RANK!!!!!!!!!!!!!!!!!!!!!!!!"*4, int(os.environ.get("RANK", 0)))
200
+ traceback.print_exc()
201
+ sys.stderr.flush()
202
+ raise
cosyvoice/cli/__init__.py ADDED
File without changes
cosyvoice/cli/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (156 Bytes). View file
 
cosyvoice/cli/__pycache__/cosyvoice.cpython-310.pyc ADDED
Binary file (7.98 kB). View file
 
cosyvoice/cli/__pycache__/frontend.cpython-310.pyc ADDED
Binary file (8.6 kB). View file
 
cosyvoice/cli/__pycache__/model.cpython-310.pyc ADDED
Binary file (12.4 kB). View file
 
cosyvoice/cli/cosyvoice.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import os
15
+ import time
16
+ from typing import Generator
17
+ from tqdm import tqdm
18
+ from hyperpyyaml import load_hyperpyyaml
19
+ from huggingface_hub import snapshot_download
20
+ import torch
21
+ from cosyvoice.cli.frontend import CosyVoiceFrontEnd
22
+ from cosyvoice.cli.model import CosyVoiceModel, CosyVoice2Model
23
+ from cosyvoice.utils.file_utils import logging
24
+ from cosyvoice.utils.class_utils import get_model_type
25
+
26
+
27
+ class CosyVoice:
28
+
29
+ def __init__(self, model_dir, load_jit=False, load_trt=False, fp16=False, trt_concurrent=1):
30
+ self.instruct = True if '-Instruct' in model_dir else False
31
+ self.model_dir = model_dir
32
+ self.fp16 = fp16
33
+ if not os.path.exists(model_dir):
34
+ model_dir = snapshot_download(model_dir)
35
+ hyper_yaml_path = '{}/cosyvoice.yaml'.format(model_dir)
36
+ if not os.path.exists(hyper_yaml_path):
37
+ raise ValueError('{} not found!'.format(hyper_yaml_path))
38
+ with open(hyper_yaml_path, 'r') as f:
39
+ configs = load_hyperpyyaml(f)
40
+ assert get_model_type(configs) != CosyVoice2Model, 'do not use {} for CosyVoice initialization!'.format(model_dir)
41
+ self.frontend = CosyVoiceFrontEnd(configs['get_tokenizer'],
42
+ configs['feat_extractor'],
43
+ '{}/campplus.onnx'.format(model_dir),
44
+ '{}/speech_tokenizer_v1.onnx'.format(model_dir),
45
+ '{}/spk2info.pt'.format(model_dir),
46
+ configs['allowed_special'])
47
+ self.sample_rate = configs['sample_rate']
48
+ if torch.cuda.is_available() is False and (load_jit is True or load_trt is True or fp16 is True):
49
+ load_jit, load_trt, fp16 = False, False, False
50
+ logging.warning('no cuda device, set load_jit/load_trt/fp16 to False')
51
+ self.model = CosyVoiceModel(configs['llm'], configs['flow'], configs['hift'], fp16)
52
+ self.model.load('{}/llm.pt'.format(model_dir),
53
+ '{}/flow.pt'.format(model_dir),
54
+ '{}/hift.pt'.format(model_dir))
55
+ if load_jit:
56
+ self.model.load_jit('{}/llm.text_encoder.{}.zip'.format(model_dir, 'fp16' if self.fp16 is True else 'fp32'),
57
+ '{}/llm.llm.{}.zip'.format(model_dir, 'fp16' if self.fp16 is True else 'fp32'),
58
+ '{}/flow.encoder.{}.zip'.format(model_dir, 'fp16' if self.fp16 is True else 'fp32'))
59
+ if load_trt:
60
+ self.model.load_trt('{}/flow.decoder.estimator.{}.mygpu.plan'.format(model_dir, 'fp16' if self.fp16 is True else 'fp32'),
61
+ '{}/flow.decoder.estimator.fp32.onnx'.format(model_dir),
62
+ trt_concurrent,
63
+ self.fp16)
64
+ del configs
65
+
66
+ def list_available_spks(self):
67
+ spks = list(self.frontend.spk2info.keys())
68
+ return spks
69
+
70
+ def add_zero_shot_spk(self, prompt_text, prompt_speech_16k, zero_shot_spk_id):
71
+ assert zero_shot_spk_id != '', 'do not use empty zero_shot_spk_id'
72
+ model_input = self.frontend.frontend_zero_shot('', prompt_text, prompt_speech_16k, self.sample_rate, '')
73
+ del model_input['text']
74
+ del model_input['text_len']
75
+ self.frontend.spk2info[zero_shot_spk_id] = model_input
76
+ return True
77
+
78
+ def save_spkinfo(self):
79
+ torch.save(self.frontend.spk2info, '{}/spk2info.pt'.format(self.model_dir))
80
+
81
+ def inference_sft(self, tts_text, spk_id, stream=False, speed=1.0, text_frontend=True):
82
+ for i in tqdm(self.frontend.text_normalize(tts_text, split=True, text_frontend=text_frontend)):
83
+ model_input = self.frontend.frontend_sft(i, spk_id)
84
+ start_time = time.time()
85
+ logging.info('synthesis text {}'.format(i))
86
+ for model_output in self.model.tts(**model_input, stream=stream, speed=speed):
87
+ speech_len = model_output['tts_speech'].shape[1] / self.sample_rate
88
+ logging.info('yield speech len {}, rtf {}'.format(speech_len, (time.time() - start_time) / speech_len))
89
+ yield model_output
90
+ start_time = time.time()
91
+
92
+ def inference_zero_shot(self, tts_text, prompt_text, prompt_speech_16k, zero_shot_spk_id='', stream=False, speed=1.0, text_frontend=True):
93
+ prompt_text = self.frontend.text_normalize(prompt_text, split=False, text_frontend=text_frontend)
94
+ for i in tqdm(self.frontend.text_normalize(tts_text, split=True, text_frontend=text_frontend)):
95
+ if (not isinstance(i, Generator)) and len(i) < 0.5 * len(prompt_text):
96
+ logging.warning('synthesis text {} too short than prompt text {}, this may lead to bad performance'.format(i, prompt_text))
97
+ model_input = self.frontend.frontend_zero_shot(i, prompt_text, prompt_speech_16k, self.sample_rate, zero_shot_spk_id)
98
+ start_time = time.time()
99
+ logging.info('synthesis text {}'.format(i))
100
+ for model_output in self.model.tts(**model_input, stream=stream, speed=speed):
101
+ speech_len = model_output['tts_speech'].shape[1] / self.sample_rate
102
+ logging.info('yield speech len {}, rtf {}'.format(speech_len, (time.time() - start_time) / speech_len))
103
+ yield model_output
104
+ start_time = time.time()
105
+
106
+ def inference_cross_lingual(self, tts_text, prompt_speech_16k, zero_shot_spk_id='', stream=False, speed=1.0, text_frontend=True):
107
+ for i in tqdm(self.frontend.text_normalize(tts_text, split=True, text_frontend=text_frontend)):
108
+ model_input = self.frontend.frontend_cross_lingual(i, prompt_speech_16k, self.sample_rate, zero_shot_spk_id)
109
+ start_time = time.time()
110
+ logging.info('synthesis text {}'.format(i))
111
+ for model_output in self.model.tts(**model_input, stream=stream, speed=speed):
112
+ speech_len = model_output['tts_speech'].shape[1] / self.sample_rate
113
+ logging.info('yield speech len {}, rtf {}'.format(speech_len, (time.time() - start_time) / speech_len))
114
+ yield model_output
115
+ start_time = time.time()
116
+
117
+ def inference_instruct(self, tts_text, spk_id, instruct_text, stream=False, speed=1.0, text_frontend=True):
118
+ assert isinstance(self.model, CosyVoiceModel), 'inference_instruct is only implemented for CosyVoice!'
119
+ if self.instruct is False:
120
+ raise ValueError('{} do not support instruct inference'.format(self.model_dir))
121
+ instruct_text = self.frontend.text_normalize(instruct_text, split=False, text_frontend=text_frontend)
122
+ for i in tqdm(self.frontend.text_normalize(tts_text, split=True, text_frontend=text_frontend)):
123
+ model_input = self.frontend.frontend_instruct(i, spk_id, instruct_text)
124
+ start_time = time.time()
125
+ logging.info('synthesis text {}'.format(i))
126
+ for model_output in self.model.tts(**model_input, stream=stream, speed=speed):
127
+ speech_len = model_output['tts_speech'].shape[1] / self.sample_rate
128
+ logging.info('yield speech len {}, rtf {}'.format(speech_len, (time.time() - start_time) / speech_len))
129
+ yield model_output
130
+ start_time = time.time()
131
+
132
+ def inference_vc(self, source_speech_16k, prompt_speech_16k, stream=False, speed=1.0):
133
+ model_input = self.frontend.frontend_vc(source_speech_16k, prompt_speech_16k, self.sample_rate)
134
+ start_time = time.time()
135
+ for model_output in self.model.tts(**model_input, stream=stream, speed=speed):
136
+ speech_len = model_output['tts_speech'].shape[1] / self.sample_rate
137
+ logging.info('yield speech len {}, rtf {}'.format(speech_len, (time.time() - start_time) / speech_len))
138
+ yield model_output
139
+ start_time = time.time()
140
+
141
+
142
+ class CosyVoice2(CosyVoice):
143
+
144
+ def __init__(self, model_dir, load_jit=False, load_trt=False, load_vllm=False, fp16=False, trt_concurrent=1):
145
+ self.instruct = True if '-Instruct' in model_dir else False
146
+ self.model_dir = model_dir
147
+ self.fp16 = fp16
148
+ if not os.path.exists(model_dir):
149
+ model_dir = snapshot_download(model_dir)
150
+ hyper_yaml_path = '{}/cosyvoice2.yaml'.format(model_dir)
151
+ if not os.path.exists(hyper_yaml_path):
152
+ raise ValueError('{} not found!'.format(hyper_yaml_path))
153
+ with open(hyper_yaml_path, 'r') as f:
154
+ configs = load_hyperpyyaml(f, overrides={'qwen_pretrain_path': os.path.join(model_dir, 'CosyVoice-BlankEN')})
155
+ assert get_model_type(configs) == CosyVoice2Model, 'do not use {} for CosyVoice2 initialization!'.format(model_dir)
156
+ self.frontend = CosyVoiceFrontEnd(configs['get_tokenizer'],
157
+ configs['feat_extractor'],
158
+ '{}/campplus.onnx'.format(model_dir),
159
+ '{}/speech_tokenizer_v2.onnx'.format(model_dir),
160
+ '{}/spk2info.pt'.format(model_dir),
161
+ configs['allowed_special'])
162
+ self.sample_rate = configs['sample_rate']
163
+ if torch.cuda.is_available() is False and (load_jit is True or load_trt is True or fp16 is True):
164
+ load_jit, load_trt, fp16 = False, False, False
165
+ logging.warning('no cuda device, set load_jit/load_trt/fp16 to False')
166
+ self.model = CosyVoice2Model(configs['llm'], configs['flow'], configs['hift'], fp16)
167
+ self.model.load('{}/llm.pt'.format(model_dir),
168
+ '{}/flow.pt'.format(model_dir),
169
+ '{}/hift.pt'.format(model_dir))
170
+ if load_vllm:
171
+ self.model.load_vllm('{}/vllm'.format(model_dir))
172
+ if load_jit:
173
+ self.model.load_jit('{}/flow.encoder.{}.zip'.format(model_dir, 'fp16' if self.fp16 is True else 'fp32'))
174
+ if load_trt:
175
+ self.model.load_trt('{}/flow.decoder.estimator.{}.mygpu.plan'.format(model_dir, 'fp16' if self.fp16 is True else 'fp32'),
176
+ '{}/flow.decoder.estimator.fp32.onnx'.format(model_dir),
177
+ trt_concurrent,
178
+ self.fp16)
179
+ del configs
180
+
181
+ def inference_instruct(self, *args, **kwargs):
182
+ raise NotImplementedError('inference_instruct is not implemented for CosyVoice2!')
183
+
184
+ def inference_instruct2(self, tts_text, instruct_text, prompt_speech_16k, zero_shot_spk_id='', stream=False, speed=1.0, text_frontend=True):
185
+ assert isinstance(self.model, CosyVoice2Model), 'inference_instruct2 is only implemented for CosyVoice2!'
186
+ for i in tqdm(self.frontend.text_normalize(tts_text, split=True, text_frontend=text_frontend)):
187
+ model_input = self.frontend.frontend_instruct2(i, instruct_text, prompt_speech_16k, self.sample_rate, zero_shot_spk_id)
188
+ start_time = time.time()
189
+ logging.info('synthesis text {}'.format(i))
190
+ for model_output in self.model.tts(**model_input, stream=stream, speed=speed):
191
+ speech_len = model_output['tts_speech'].shape[1] / self.sample_rate
192
+ logging.info('yield speech len {}, rtf {}'.format(speech_len, (time.time() - start_time) / speech_len))
193
+ yield model_output
194
+ start_time = time.time()
cosyvoice/cli/frontend.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from functools import partial
15
+ from typing import Generator
16
+ import json
17
+ import onnxruntime
18
+ import torch
19
+ import numpy as np
20
+ import whisper
21
+ from typing import Callable
22
+ import torchaudio.compliance.kaldi as kaldi
23
+ import torchaudio
24
+ import os
25
+ import re
26
+ import inflect
27
+ try:
28
+ import ttsfrd
29
+ use_ttsfrd = True
30
+ except ImportError:
31
+ print("failed to import ttsfrd, use wetext instead")
32
+ from wetext import Normalizer as ZhNormalizer
33
+ from wetext import Normalizer as EnNormalizer
34
+ use_ttsfrd = False
35
+ from cosyvoice.utils.file_utils import logging
36
+ from cosyvoice.utils.frontend_utils import contains_chinese, replace_blank, replace_corner_mark, remove_bracket, spell_out_number, split_paragraph, is_only_punctuation
37
+
38
+
39
+ class CosyVoiceFrontEnd:
40
+
41
+ def __init__(self,
42
+ get_tokenizer: Callable,
43
+ feat_extractor: Callable,
44
+ campplus_model: str,
45
+ speech_tokenizer_model: str,
46
+ spk2info: str = '',
47
+ allowed_special: str = 'all'):
48
+ self.tokenizer = get_tokenizer()
49
+ self.feat_extractor = feat_extractor
50
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
51
+ option = onnxruntime.SessionOptions()
52
+ option.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
53
+ option.intra_op_num_threads = 1
54
+ self.campplus_session = onnxruntime.InferenceSession(campplus_model, sess_options=option, providers=["CPUExecutionProvider"])
55
+ self.speech_tokenizer_session = onnxruntime.InferenceSession(speech_tokenizer_model, sess_options=option,
56
+ providers=["CUDAExecutionProvider" if torch.cuda.is_available() else
57
+ "CPUExecutionProvider"])
58
+ if os.path.exists(spk2info):
59
+ self.spk2info = torch.load(spk2info, map_location=self.device)
60
+ else:
61
+ self.spk2info = {}
62
+ self.allowed_special = allowed_special
63
+ self.use_ttsfrd = use_ttsfrd
64
+ if self.use_ttsfrd:
65
+ self.frd = ttsfrd.TtsFrontendEngine()
66
+ ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
67
+ assert self.frd.initialize('{}/../../pretrained_models/CosyVoice-ttsfrd/resource'.format(ROOT_DIR)) is True, \
68
+ 'failed to initialize ttsfrd resource'
69
+ self.frd.set_lang_type('pinyinvg')
70
+ else:
71
+ self.zh_tn_model = ZhNormalizer(remove_erhua=False)
72
+ self.en_tn_model = EnNormalizer()
73
+ self.inflect_parser = inflect.engine()
74
+
75
+ def _extract_text_token(self, text):
76
+ if isinstance(text, Generator):
77
+ logging.info('get tts_text generator, will return _extract_text_token_generator!')
78
+ # NOTE add a dummy text_token_len for compatibility
79
+ return self._extract_text_token_generator(text), torch.tensor([0], dtype=torch.int32).to(self.device)
80
+ else:
81
+ text_token = self.tokenizer.encode(text, allowed_special=self.allowed_special)
82
+ text_token = torch.tensor([text_token], dtype=torch.int32).to(self.device)
83
+ text_token_len = torch.tensor([text_token.shape[1]], dtype=torch.int32).to(self.device)
84
+ return text_token, text_token_len
85
+
86
+ def _extract_text_token_generator(self, text_generator):
87
+ for text in text_generator:
88
+ text_token, _ = self._extract_text_token(text)
89
+ for i in range(text_token.shape[1]):
90
+ yield text_token[:, i: i + 1]
91
+
92
+ def _extract_speech_token(self, speech):
93
+ assert speech.shape[1] / 16000 <= 30, 'do not support extract speech token for audio longer than 30s'
94
+ feat = whisper.log_mel_spectrogram(speech, n_mels=128)
95
+ speech_token = self.speech_tokenizer_session.run(None,
96
+ {self.speech_tokenizer_session.get_inputs()[0].name:
97
+ feat.detach().cpu().numpy(),
98
+ self.speech_tokenizer_session.get_inputs()[1].name:
99
+ np.array([feat.shape[2]], dtype=np.int32)})[0].flatten().tolist()
100
+ speech_token = torch.tensor([speech_token], dtype=torch.int32).to(self.device)
101
+ speech_token_len = torch.tensor([speech_token.shape[1]], dtype=torch.int32).to(self.device)
102
+ return speech_token, speech_token_len
103
+
104
+ def _extract_spk_embedding(self, speech):
105
+ feat = kaldi.fbank(speech,
106
+ num_mel_bins=80,
107
+ dither=0,
108
+ sample_frequency=16000)
109
+ feat = feat - feat.mean(dim=0, keepdim=True)
110
+ embedding = self.campplus_session.run(None,
111
+ {self.campplus_session.get_inputs()[0].name: feat.unsqueeze(dim=0).cpu().numpy()})[0].flatten().tolist()
112
+ embedding = torch.tensor([embedding]).to(self.device)
113
+ return embedding
114
+
115
+ def _extract_speech_feat(self, speech):
116
+ speech_feat = self.feat_extractor(speech).squeeze(dim=0).transpose(0, 1).to(self.device)
117
+ speech_feat = speech_feat.unsqueeze(dim=0)
118
+ speech_feat_len = torch.tensor([speech_feat.shape[1]], dtype=torch.int32).to(self.device)
119
+ return speech_feat, speech_feat_len
120
+
121
+ def text_normalize(self, text, split=True, text_frontend=True):
122
+ if isinstance(text, Generator):
123
+ logging.info('get tts_text generator, will skip text_normalize!')
124
+ return [text]
125
+ if text_frontend is False or text == '':
126
+ return [text] if split is True else text
127
+ text = text.strip()
128
+ if self.use_ttsfrd:
129
+ texts = [i["text"] for i in json.loads(self.frd.do_voicegen_frd(text))["sentences"]]
130
+ text = ''.join(texts)
131
+ else:
132
+ if contains_chinese(text):
133
+ text = self.zh_tn_model.normalize(text)
134
+ text = text.replace("\n", "")
135
+ text = replace_blank(text)
136
+ text = replace_corner_mark(text)
137
+ text = text.replace(".", "。")
138
+ text = text.replace(" - ", ",")
139
+ text = remove_bracket(text)
140
+ text = re.sub(r'[,,、]+$', '。', text)
141
+ texts = list(split_paragraph(text, partial(self.tokenizer.encode, allowed_special=self.allowed_special), "zh", token_max_n=80,
142
+ token_min_n=60, merge_len=20, comma_split=False))
143
+ else:
144
+ text = self.en_tn_model.normalize(text)
145
+ text = spell_out_number(text, self.inflect_parser)
146
+ texts = list(split_paragraph(text, partial(self.tokenizer.encode, allowed_special=self.allowed_special), "en", token_max_n=80,
147
+ token_min_n=60, merge_len=20, comma_split=False))
148
+ texts = [i for i in texts if not is_only_punctuation(i)]
149
+ return texts if split is True else text
150
+
151
+ def frontend_sft(self, tts_text, spk_id):
152
+ tts_text_token, tts_text_token_len = self._extract_text_token(tts_text)
153
+ embedding = self.spk2info[spk_id]['embedding']
154
+ model_input = {'text': tts_text_token, 'text_len': tts_text_token_len, 'llm_embedding': embedding, 'flow_embedding': embedding}
155
+ return model_input
156
+
157
+ def frontend_zero_shot(self, tts_text, prompt_text, prompt_speech_16k, resample_rate, zero_shot_spk_id):
158
+ tts_text_token, tts_text_token_len = self._extract_text_token(tts_text)
159
+ if zero_shot_spk_id == '':
160
+ prompt_text_token, prompt_text_token_len = self._extract_text_token(prompt_text)
161
+ prompt_speech_resample = torchaudio.transforms.Resample(orig_freq=16000, new_freq=resample_rate)(prompt_speech_16k)
162
+ speech_feat, speech_feat_len = self._extract_speech_feat(prompt_speech_resample)
163
+ speech_token, speech_token_len = self._extract_speech_token(prompt_speech_16k)
164
+ if resample_rate == 24000:
165
+ # cosyvoice2, force speech_feat % speech_token = 2
166
+ token_len = min(int(speech_feat.shape[1] / 2), speech_token.shape[1])
167
+ speech_feat, speech_feat_len[:] = speech_feat[:, :2 * token_len], 2 * token_len
168
+ speech_token, speech_token_len[:] = speech_token[:, :token_len], token_len
169
+ embedding = self._extract_spk_embedding(prompt_speech_16k)
170
+ model_input = {'prompt_text': prompt_text_token, 'prompt_text_len': prompt_text_token_len,
171
+ 'llm_prompt_speech_token': speech_token, 'llm_prompt_speech_token_len': speech_token_len,
172
+ 'flow_prompt_speech_token': speech_token, 'flow_prompt_speech_token_len': speech_token_len,
173
+ 'prompt_speech_feat': speech_feat, 'prompt_speech_feat_len': speech_feat_len,
174
+ 'llm_embedding': embedding, 'flow_embedding': embedding}
175
+ else:
176
+ model_input = self.spk2info[zero_shot_spk_id]
177
+ model_input['text'] = tts_text_token
178
+ model_input['text_len'] = tts_text_token_len
179
+ return model_input
180
+
181
+ def frontend_cross_lingual(self, tts_text, prompt_speech_16k, resample_rate, zero_shot_spk_id):
182
+ model_input = self.frontend_zero_shot(tts_text, '', prompt_speech_16k, resample_rate, zero_shot_spk_id)
183
+ # in cross lingual mode, we remove prompt in llm
184
+ del model_input['prompt_text']
185
+ del model_input['prompt_text_len']
186
+ del model_input['llm_prompt_speech_token']
187
+ del model_input['llm_prompt_speech_token_len']
188
+ return model_input
189
+
190
+ def frontend_instruct(self, tts_text, spk_id, instruct_text):
191
+ model_input = self.frontend_sft(tts_text, spk_id)
192
+ # in instruct mode, we remove spk_embedding in llm due to information leakage
193
+ del model_input['llm_embedding']
194
+ instruct_text_token, instruct_text_token_len = self._extract_text_token(instruct_text + '<endofprompt>')
195
+ model_input['prompt_text'] = instruct_text_token
196
+ model_input['prompt_text_len'] = instruct_text_token_len
197
+ return model_input
198
+
199
+ def frontend_instruct2(self, tts_text, instruct_text, prompt_speech_16k, resample_rate, zero_shot_spk_id):
200
+ model_input = self.frontend_zero_shot(tts_text, instruct_text + '<|endofprompt|>', prompt_speech_16k, resample_rate, zero_shot_spk_id)
201
+ del model_input['llm_prompt_speech_token']
202
+ del model_input['llm_prompt_speech_token_len']
203
+ return model_input
204
+
205
+ def frontend_vc(self, source_speech_16k, prompt_speech_16k, resample_rate):
206
+ prompt_speech_token, prompt_speech_token_len = self._extract_speech_token(prompt_speech_16k)
207
+ prompt_speech_resample = torchaudio.transforms.Resample(orig_freq=16000, new_freq=resample_rate)(prompt_speech_16k)
208
+ prompt_speech_feat, prompt_speech_feat_len = self._extract_speech_feat(prompt_speech_resample)
209
+ embedding = self._extract_spk_embedding(prompt_speech_16k)
210
+ source_speech_token, source_speech_token_len = self._extract_speech_token(source_speech_16k)
211
+ model_input = {'source_speech_token': source_speech_token, 'source_speech_token_len': source_speech_token_len,
212
+ 'flow_prompt_speech_token': prompt_speech_token, 'flow_prompt_speech_token_len': prompt_speech_token_len,
213
+ 'prompt_speech_feat': prompt_speech_feat, 'prompt_speech_feat_len': prompt_speech_feat_len,
214
+ 'flow_embedding': embedding}
215
+ return model_input
cosyvoice/cli/model.py ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ # 2025 Alibaba Inc (authors: Xiang Lyu, Bofan Zhou)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import os
16
+ from typing import Generator
17
+ import torch
18
+ import numpy as np
19
+ import threading
20
+ import time
21
+ from torch.nn import functional as F
22
+ from contextlib import nullcontext
23
+ import uuid
24
+ from cosyvoice.utils.common import fade_in_out
25
+ from cosyvoice.utils.file_utils import convert_onnx_to_trt, export_cosyvoice2_vllm
26
+ from cosyvoice.utils.common import TrtContextWrapper
27
+
28
+
29
+ class CosyVoiceModel:
30
+
31
+ def __init__(self,
32
+ llm: torch.nn.Module,
33
+ flow: torch.nn.Module,
34
+ hift: torch.nn.Module,
35
+ fp16: bool = False):
36
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
37
+ self.llm = llm
38
+ self.flow = flow
39
+ self.hift = hift
40
+ self.fp16 = fp16
41
+ if self.fp16 is True:
42
+ self.llm.half()
43
+ self.flow.half()
44
+ self.token_min_hop_len = 2 * self.flow.input_frame_rate
45
+ self.token_max_hop_len = 4 * self.flow.input_frame_rate
46
+ self.token_overlap_len = 20
47
+ # mel fade in out
48
+ self.mel_overlap_len = int(self.token_overlap_len / self.flow.input_frame_rate * 22050 / 256)
49
+ self.mel_window = np.hamming(2 * self.mel_overlap_len)
50
+ # hift cache
51
+ self.mel_cache_len = 20
52
+ self.source_cache_len = int(self.mel_cache_len * 256)
53
+ # speech fade in out
54
+ self.speech_window = np.hamming(2 * self.source_cache_len)
55
+ # rtf and decoding related
56
+ self.stream_scale_factor = 1
57
+ assert self.stream_scale_factor >= 1, 'stream_scale_factor should be greater than 1, change it according to your actual rtf'
58
+ self.llm_context = torch.cuda.stream(torch.cuda.Stream(self.device)) if torch.cuda.is_available() else nullcontext()
59
+ self.lock = threading.Lock()
60
+ # dict used to store session related variable
61
+ self.tts_speech_token_dict = {}
62
+ self.llm_end_dict = {}
63
+ self.mel_overlap_dict = {}
64
+ self.flow_cache_dict = {}
65
+ self.hift_cache_dict = {}
66
+
67
+ def load(self, llm_model, flow_model, hift_model):
68
+ self.llm.load_state_dict(torch.load(llm_model, map_location=self.device), strict=False)
69
+ self.llm.to(self.device).eval()
70
+ self.flow.load_state_dict(torch.load(flow_model, map_location=self.device), strict=False)
71
+ self.flow.to(self.device).eval()
72
+ # in case hift_model is a hifigan model
73
+ hift_state_dict = {k.replace('generator.', ''): v for k, v in torch.load(hift_model, map_location=self.device).items()}
74
+ self.hift.load_state_dict(hift_state_dict, strict=False)
75
+ self.hift.to(self.device).eval()
76
+
77
+ def load_jit(self, llm_text_encoder_model, llm_llm_model, flow_encoder_model):
78
+ llm_text_encoder = torch.jit.load(llm_text_encoder_model, map_location=self.device)
79
+ self.llm.text_encoder = llm_text_encoder
80
+ llm_llm = torch.jit.load(llm_llm_model, map_location=self.device)
81
+ self.llm.llm = llm_llm
82
+ flow_encoder = torch.jit.load(flow_encoder_model, map_location=self.device)
83
+ self.flow.encoder = flow_encoder
84
+
85
+ def load_trt(self, flow_decoder_estimator_model, flow_decoder_onnx_model, trt_concurrent, fp16):
86
+ assert torch.cuda.is_available(), 'tensorrt only supports gpu!'
87
+ if not os.path.exists(flow_decoder_estimator_model) or os.path.getsize(flow_decoder_estimator_model) == 0:
88
+ convert_onnx_to_trt(flow_decoder_estimator_model, self.get_trt_kwargs(), flow_decoder_onnx_model, fp16)
89
+ del self.flow.decoder.estimator
90
+ import tensorrt as trt
91
+ with open(flow_decoder_estimator_model, 'rb') as f:
92
+ estimator_engine = trt.Runtime(trt.Logger(trt.Logger.INFO)).deserialize_cuda_engine(f.read())
93
+ assert estimator_engine is not None, 'failed to load trt {}'.format(flow_decoder_estimator_model)
94
+ self.flow.decoder.estimator = TrtContextWrapper(estimator_engine, trt_concurrent=trt_concurrent, device=self.device)
95
+
96
+ def get_trt_kwargs(self):
97
+ min_shape = [(2, 80, 4), (2, 1, 4), (2, 80, 4), (2, 80, 4)]
98
+ opt_shape = [(2, 80, 500), (2, 1, 500), (2, 80, 500), (2, 80, 500)]
99
+ max_shape = [(2, 80, 3000), (2, 1, 3000), (2, 80, 3000), (2, 80, 3000)]
100
+ input_names = ["x", "mask", "mu", "cond"]
101
+ return {'min_shape': min_shape, 'opt_shape': opt_shape, 'max_shape': max_shape, 'input_names': input_names}
102
+
103
+ def llm_job(self, text, prompt_text, llm_prompt_speech_token, llm_embedding, uuid):
104
+ with self.llm_context, torch.cuda.amp.autocast(self.fp16 is True and hasattr(self.llm, 'vllm') is False):
105
+ if isinstance(text, Generator):
106
+ assert isinstance(self, CosyVoice2Model) and not hasattr(self.llm, 'vllm'), 'streaming input text is only implemented for CosyVoice2 and do not support vllm!'
107
+ for i in self.llm.inference_bistream(text=text,
108
+ prompt_text=prompt_text.to(self.device),
109
+ prompt_text_len=torch.tensor([prompt_text.shape[1]], dtype=torch.int32).to(self.device),
110
+ prompt_speech_token=llm_prompt_speech_token.to(self.device),
111
+ prompt_speech_token_len=torch.tensor([llm_prompt_speech_token.shape[1]], dtype=torch.int32).to(self.device),
112
+ embedding=llm_embedding.to(self.device)):
113
+ self.tts_speech_token_dict[uuid].append(i)
114
+ else:
115
+ for i in self.llm.inference(text=text.to(self.device),
116
+ text_len=torch.tensor([text.shape[1]], dtype=torch.int32).to(self.device),
117
+ prompt_text=prompt_text.to(self.device),
118
+ prompt_text_len=torch.tensor([prompt_text.shape[1]], dtype=torch.int32).to(self.device),
119
+ prompt_speech_token=llm_prompt_speech_token.to(self.device),
120
+ prompt_speech_token_len=torch.tensor([llm_prompt_speech_token.shape[1]], dtype=torch.int32).to(self.device),
121
+ embedding=llm_embedding.to(self.device),
122
+ uuid=uuid):
123
+ self.tts_speech_token_dict[uuid].append(i)
124
+ self.llm_end_dict[uuid] = True
125
+
126
+ def vc_job(self, source_speech_token, uuid):
127
+ self.tts_speech_token_dict[uuid] = source_speech_token.flatten().tolist()
128
+ self.llm_end_dict[uuid] = True
129
+
130
+ def token2wav(self, token, prompt_token, prompt_feat, embedding, uuid, finalize=False, speed=1.0):
131
+ with torch.cuda.amp.autocast(self.fp16):
132
+ tts_mel, self.flow_cache_dict[uuid] = self.flow.inference(token=token.to(self.device),
133
+ token_len=torch.tensor([token.shape[1]], dtype=torch.int32).to(self.device),
134
+ prompt_token=prompt_token.to(self.device),
135
+ prompt_token_len=torch.tensor([prompt_token.shape[1]], dtype=torch.int32).to(self.device),
136
+ prompt_feat=prompt_feat.to(self.device),
137
+ prompt_feat_len=torch.tensor([prompt_feat.shape[1]], dtype=torch.int32).to(self.device),
138
+ embedding=embedding.to(self.device),
139
+ flow_cache=self.flow_cache_dict[uuid])
140
+
141
+ # mel overlap fade in out
142
+ if self.mel_overlap_dict[uuid].shape[2] != 0:
143
+ tts_mel = fade_in_out(tts_mel, self.mel_overlap_dict[uuid], self.mel_window)
144
+ # append hift cache
145
+ if self.hift_cache_dict[uuid] is not None:
146
+ hift_cache_mel, hift_cache_source = self.hift_cache_dict[uuid]['mel'], self.hift_cache_dict[uuid]['source']
147
+ tts_mel = torch.concat([hift_cache_mel, tts_mel], dim=2)
148
+ else:
149
+ hift_cache_source = torch.zeros(1, 1, 0)
150
+ # keep overlap mel and hift cache
151
+ if finalize is False:
152
+ self.mel_overlap_dict[uuid] = tts_mel[:, :, -self.mel_overlap_len:]
153
+ tts_mel = tts_mel[:, :, :-self.mel_overlap_len]
154
+ tts_speech, tts_source = self.hift.inference(speech_feat=tts_mel, cache_source=hift_cache_source)
155
+ if self.hift_cache_dict[uuid] is not None:
156
+ tts_speech = fade_in_out(tts_speech, self.hift_cache_dict[uuid]['speech'], self.speech_window)
157
+ self.hift_cache_dict[uuid] = {'mel': tts_mel[:, :, -self.mel_cache_len:],
158
+ 'source': tts_source[:, :, -self.source_cache_len:],
159
+ 'speech': tts_speech[:, -self.source_cache_len:]}
160
+ tts_speech = tts_speech[:, :-self.source_cache_len]
161
+ else:
162
+ if speed != 1.0:
163
+ assert self.hift_cache_dict[uuid] is None, 'speed change only support non-stream inference mode'
164
+ tts_mel = F.interpolate(tts_mel, size=int(tts_mel.shape[2] / speed), mode='linear')
165
+ tts_speech, tts_source = self.hift.inference(speech_feat=tts_mel, cache_source=hift_cache_source)
166
+ if self.hift_cache_dict[uuid] is not None:
167
+ tts_speech = fade_in_out(tts_speech, self.hift_cache_dict[uuid]['speech'], self.speech_window)
168
+ return tts_speech
169
+
170
+ def tts(self, text=torch.zeros(1, 0, dtype=torch.int32), flow_embedding=torch.zeros(0, 192), llm_embedding=torch.zeros(0, 192),
171
+ prompt_text=torch.zeros(1, 0, dtype=torch.int32),
172
+ llm_prompt_speech_token=torch.zeros(1, 0, dtype=torch.int32),
173
+ flow_prompt_speech_token=torch.zeros(1, 0, dtype=torch.int32),
174
+ prompt_speech_feat=torch.zeros(1, 0, 80), source_speech_token=torch.zeros(1, 0, dtype=torch.int32), stream=False, speed=1.0, **kwargs):
175
+ # this_uuid is used to track variables related to this inference thread
176
+ this_uuid = str(uuid.uuid1())
177
+ with self.lock:
178
+ self.tts_speech_token_dict[this_uuid], self.llm_end_dict[this_uuid] = [], False
179
+ self.hift_cache_dict[this_uuid] = None
180
+ self.mel_overlap_dict[this_uuid] = torch.zeros(1, 80, 0)
181
+ self.flow_cache_dict[this_uuid] = torch.zeros(1, 80, 0, 2)
182
+ if source_speech_token.shape[1] == 0:
183
+ p = threading.Thread(target=self.llm_job, args=(text, prompt_text, llm_prompt_speech_token, llm_embedding, this_uuid))
184
+ else:
185
+ p = threading.Thread(target=self.vc_job, args=(source_speech_token, this_uuid))
186
+ p.start()
187
+ if stream is True:
188
+ token_hop_len = self.token_min_hop_len
189
+ while True:
190
+ time.sleep(0.1)
191
+ if len(self.tts_speech_token_dict[this_uuid]) >= token_hop_len + self.token_overlap_len:
192
+ this_tts_speech_token = torch.tensor(self.tts_speech_token_dict[this_uuid][:token_hop_len + self.token_overlap_len]) \
193
+ .unsqueeze(dim=0)
194
+ this_tts_speech = self.token2wav(token=this_tts_speech_token,
195
+ prompt_token=flow_prompt_speech_token,
196
+ prompt_feat=prompt_speech_feat,
197
+ embedding=flow_embedding,
198
+ uuid=this_uuid,
199
+ finalize=False)
200
+ yield {'tts_speech': this_tts_speech.cpu()}
201
+ with self.lock:
202
+ self.tts_speech_token_dict[this_uuid] = self.tts_speech_token_dict[this_uuid][token_hop_len:]
203
+ # increase token_hop_len for better speech quality
204
+ token_hop_len = min(self.token_max_hop_len, int(token_hop_len * self.stream_scale_factor))
205
+ if self.llm_end_dict[this_uuid] is True and len(self.tts_speech_token_dict[this_uuid]) < token_hop_len + self.token_overlap_len:
206
+ break
207
+ p.join()
208
+ # deal with remain tokens, make sure inference remain token len equals token_hop_len when cache_speech is not None
209
+ this_tts_speech_token = torch.tensor(self.tts_speech_token_dict[this_uuid]).unsqueeze(dim=0)
210
+ this_tts_speech = self.token2wav(token=this_tts_speech_token,
211
+ prompt_token=flow_prompt_speech_token,
212
+ prompt_feat=prompt_speech_feat,
213
+ embedding=flow_embedding,
214
+ uuid=this_uuid,
215
+ finalize=True)
216
+ yield {'tts_speech': this_tts_speech.cpu()}
217
+ else:
218
+ # deal with all tokens
219
+ p.join()
220
+ this_tts_speech_token = torch.tensor(self.tts_speech_token_dict[this_uuid]).unsqueeze(dim=0)
221
+ this_tts_speech = self.token2wav(token=this_tts_speech_token,
222
+ prompt_token=flow_prompt_speech_token,
223
+ prompt_feat=prompt_speech_feat,
224
+ embedding=flow_embedding,
225
+ uuid=this_uuid,
226
+ finalize=True,
227
+ speed=speed)
228
+ yield {'tts_speech': this_tts_speech.cpu()}
229
+ with self.lock:
230
+ self.tts_speech_token_dict.pop(this_uuid)
231
+ self.llm_end_dict.pop(this_uuid)
232
+ self.mel_overlap_dict.pop(this_uuid)
233
+ self.hift_cache_dict.pop(this_uuid)
234
+ self.flow_cache_dict.pop(this_uuid)
235
+ if torch.cuda.is_available():
236
+ torch.cuda.empty_cache()
237
+ torch.cuda.current_stream().synchronize()
238
+
239
+
240
+ class CosyVoice2Model(CosyVoiceModel):
241
+
242
+ def __init__(self,
243
+ llm: torch.nn.Module,
244
+ flow: torch.nn.Module,
245
+ hift: torch.nn.Module,
246
+ fp16: bool = False):
247
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
248
+ self.llm = llm
249
+ self.flow = flow
250
+ self.hift = hift
251
+ self.fp16 = fp16
252
+ if self.fp16 is True:
253
+ self.llm.half()
254
+ self.flow.half()
255
+ # NOTE must matching training static_chunk_size
256
+ self.token_hop_len = 25
257
+ # hift cache
258
+ self.mel_cache_len = 8
259
+ self.source_cache_len = int(self.mel_cache_len * 480)
260
+ # speech fade in out
261
+ self.speech_window = np.hamming(2 * self.source_cache_len)
262
+ # rtf and decoding related
263
+ self.llm_context = torch.cuda.stream(torch.cuda.Stream(self.device)) if torch.cuda.is_available() else nullcontext()
264
+ self.lock = threading.Lock()
265
+ # dict used to store session related variable
266
+ self.tts_speech_token_dict = {}
267
+ self.llm_end_dict = {}
268
+ self.hift_cache_dict = {}
269
+
270
+ def load_jit(self, flow_encoder_model):
271
+ flow_encoder = torch.jit.load(flow_encoder_model, map_location=self.device)
272
+ self.flow.encoder = flow_encoder
273
+
274
+ def load_vllm(self, model_dir):
275
+ export_cosyvoice2_vllm(self.llm, model_dir, self.device)
276
+ from vllm import EngineArgs, LLMEngine
277
+ engine_args = EngineArgs(model=model_dir,
278
+ skip_tokenizer_init=True,
279
+ enable_prompt_embeds=True,
280
+ gpu_memory_utilization=0.2)
281
+ self.llm.vllm = LLMEngine.from_engine_args(engine_args)
282
+ self.llm.lock = threading.Lock()
283
+ del self.llm.llm.model.model.layers
284
+
285
+ def token2wav(self, token, prompt_token, prompt_feat, embedding, token_offset, uuid, stream=False, finalize=False, speed=1.0):
286
+ with torch.cuda.amp.autocast(self.fp16):
287
+ tts_mel, _ = self.flow.inference(token=token.to(self.device),
288
+ token_len=torch.tensor([token.shape[1]], dtype=torch.int32).to(self.device),
289
+ prompt_token=prompt_token.to(self.device),
290
+ prompt_token_len=torch.tensor([prompt_token.shape[1]], dtype=torch.int32).to(self.device),
291
+ prompt_feat=prompt_feat.to(self.device),
292
+ prompt_feat_len=torch.tensor([prompt_feat.shape[1]], dtype=torch.int32).to(self.device),
293
+ embedding=embedding.to(self.device),
294
+ streaming=stream,
295
+ finalize=finalize)
296
+ tts_mel = tts_mel[:, :, token_offset * self.flow.token_mel_ratio:]
297
+ # append hift cache
298
+ if self.hift_cache_dict[uuid] is not None:
299
+ hift_cache_mel, hift_cache_source = self.hift_cache_dict[uuid]['mel'], self.hift_cache_dict[uuid]['source']
300
+ tts_mel = torch.concat([hift_cache_mel, tts_mel], dim=2)
301
+ else:
302
+ hift_cache_source = torch.zeros(1, 1, 0)
303
+ # keep overlap mel and hift cache
304
+ if finalize is False:
305
+ tts_speech, tts_source = self.hift.inference(speech_feat=tts_mel, cache_source=hift_cache_source)
306
+ if self.hift_cache_dict[uuid] is not None:
307
+ tts_speech = fade_in_out(tts_speech, self.hift_cache_dict[uuid]['speech'], self.speech_window)
308
+ self.hift_cache_dict[uuid] = {'mel': tts_mel[:, :, -self.mel_cache_len:],
309
+ 'source': tts_source[:, :, -self.source_cache_len:],
310
+ 'speech': tts_speech[:, -self.source_cache_len:]}
311
+ tts_speech = tts_speech[:, :-self.source_cache_len]
312
+ else:
313
+ if speed != 1.0:
314
+ assert self.hift_cache_dict[uuid] is None, 'speed change only support non-stream inference mode'
315
+ tts_mel = F.interpolate(tts_mel, size=int(tts_mel.shape[2] / speed), mode='linear')
316
+ tts_speech, tts_source = self.hift.inference(speech_feat=tts_mel, cache_source=hift_cache_source)
317
+ if self.hift_cache_dict[uuid] is not None:
318
+ tts_speech = fade_in_out(tts_speech, self.hift_cache_dict[uuid]['speech'], self.speech_window)
319
+ return tts_speech
320
+
321
+ def tts(self, text=torch.zeros(1, 0, dtype=torch.int32), flow_embedding=torch.zeros(0, 192), llm_embedding=torch.zeros(0, 192),
322
+ prompt_text=torch.zeros(1, 0, dtype=torch.int32),
323
+ llm_prompt_speech_token=torch.zeros(1, 0, dtype=torch.int32),
324
+ flow_prompt_speech_token=torch.zeros(1, 0, dtype=torch.int32),
325
+ prompt_speech_feat=torch.zeros(1, 0, 80), source_speech_token=torch.zeros(1, 0, dtype=torch.int32), stream=False, speed=1.0, **kwargs):
326
+ # this_uuid is used to track variables related to this inference thread
327
+ this_uuid = str(uuid.uuid1())
328
+ with self.lock:
329
+ self.tts_speech_token_dict[this_uuid], self.llm_end_dict[this_uuid] = [], False
330
+ self.hift_cache_dict[this_uuid] = None
331
+ if source_speech_token.shape[1] == 0:
332
+ p = threading.Thread(target=self.llm_job, args=(text, prompt_text, llm_prompt_speech_token, llm_embedding, this_uuid))
333
+ else:
334
+ p = threading.Thread(target=self.vc_job, args=(source_speech_token, this_uuid))
335
+ p.start()
336
+ if stream is True:
337
+ token_offset = 0
338
+ prompt_token_pad = int(np.ceil(flow_prompt_speech_token.shape[1] / self.token_hop_len) * self.token_hop_len - flow_prompt_speech_token.shape[1])
339
+ while True:
340
+ time.sleep(0.1)
341
+ this_token_hop_len = self.token_hop_len + prompt_token_pad if token_offset == 0 else self.token_hop_len
342
+ if len(self.tts_speech_token_dict[this_uuid]) - token_offset >= this_token_hop_len + self.flow.pre_lookahead_len:
343
+ this_tts_speech_token = torch.tensor(self.tts_speech_token_dict[this_uuid][:token_offset + this_token_hop_len + self.flow.pre_lookahead_len]).unsqueeze(dim=0)
344
+ this_tts_speech = self.token2wav(token=this_tts_speech_token,
345
+ prompt_token=flow_prompt_speech_token,
346
+ prompt_feat=prompt_speech_feat,
347
+ embedding=flow_embedding,
348
+ token_offset=token_offset,
349
+ uuid=this_uuid,
350
+ stream=stream,
351
+ finalize=False)
352
+ token_offset += this_token_hop_len
353
+ yield {'tts_speech': this_tts_speech.cpu()}
354
+ if self.llm_end_dict[this_uuid] is True and len(self.tts_speech_token_dict[this_uuid]) - token_offset < this_token_hop_len + self.flow.pre_lookahead_len:
355
+ break
356
+ p.join()
357
+ # deal with remain tokens, make sure inference remain token len equals token_hop_len when cache_speech is not None
358
+ this_tts_speech_token = torch.tensor(self.tts_speech_token_dict[this_uuid]).unsqueeze(dim=0)
359
+ this_tts_speech = self.token2wav(token=this_tts_speech_token,
360
+ prompt_token=flow_prompt_speech_token,
361
+ prompt_feat=prompt_speech_feat,
362
+ embedding=flow_embedding,
363
+ token_offset=token_offset,
364
+ uuid=this_uuid,
365
+ finalize=True)
366
+ yield {'tts_speech': this_tts_speech.cpu()}
367
+ else:
368
+ # deal with all tokens
369
+ p.join()
370
+ this_tts_speech_token = torch.tensor(self.tts_speech_token_dict[this_uuid]).unsqueeze(dim=0)
371
+ this_tts_speech = self.token2wav(token=this_tts_speech_token,
372
+ prompt_token=flow_prompt_speech_token,
373
+ prompt_feat=prompt_speech_feat,
374
+ embedding=flow_embedding,
375
+ token_offset=0,
376
+ uuid=this_uuid,
377
+ finalize=True,
378
+ speed=speed)
379
+ yield {'tts_speech': this_tts_speech.cpu()}
380
+ with self.lock:
381
+ self.tts_speech_token_dict.pop(this_uuid)
382
+ self.llm_end_dict.pop(this_uuid)
383
+ self.hift_cache_dict.pop(this_uuid)
384
+ if torch.cuda.is_available():
385
+ torch.cuda.empty_cache()
386
+ torch.cuda.current_stream().synchronize()
cosyvoice/dataset/__init__.py ADDED
File without changes
cosyvoice/dataset/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (160 Bytes). View file
 
cosyvoice/dataset/__pycache__/custom_processor.cpython-310.pyc ADDED
Binary file (10.8 kB). View file
 
cosyvoice/dataset/__pycache__/dataset.cpython-310.pyc ADDED
Binary file (4.44 kB). View file
 
cosyvoice/dataset/__pycache__/processor.cpython-310.pyc ADDED
Binary file (12.7 kB). View file
 
cosyvoice/dataset/custom_processor.py ADDED
@@ -0,0 +1,494 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import logging
15
+ import random
16
+
17
+ import pyarrow.parquet as pq
18
+ from io import BytesIO
19
+ import torch
20
+ import torchaudio
21
+ from torch.nn.utils.rnn import pad_sequence
22
+ import torch.nn.functional as F
23
+ import pyworld as pw
24
+
25
+
26
+ AUDIO_FORMAT_SETS = {'flac', 'mp3', 'm4a', 'ogg', 'opus', 'wav', 'wma'}
27
+
28
+ import json
29
+ from typing import Iterable, Dict, Any
30
+
31
+ def json_line_opener(data: Iterable[Dict[str, Any]], mode='train'):
32
+ """
33
+ data: Iterable[dict] 来自 DataList,里面只有 {'src': 'xxx.txt'}
34
+ 逐行读取 json,yield 出 {'key', 'text', 'text_token', 'speech_token'}
35
+ """
36
+ for sample in data:
37
+ txt_path = sample['src']
38
+ with open(txt_path, 'r', encoding='utf-8') as f:
39
+ for line in f:
40
+ if not line.strip():
41
+ continue
42
+ js = json.loads(line)
43
+ yield {
44
+ 'key': js['key'],
45
+ 'text': js['txt'],
46
+ 'text_token': js['txt'], # 先保留原文本,tokenize 阶段再转 id
47
+ 'speech_token': js['code'], # 已经是 list[int]
48
+ }
49
+
50
+ # def parquet_opener(data, mode='train', tts_data={}):
51
+ # """ Give url or local file, return file descriptor
52
+ # Inplace operation.
53
+
54
+ # Args:
55
+ # data(Iterable[str]): url or local file list
56
+
57
+ # Returns:
58
+ # Iterable[{src, stream}]
59
+ # """
60
+ # for sample in data:
61
+ # assert 'src' in sample
62
+ # url = sample['src']
63
+ # try:
64
+ # for df in pq.ParquetFile(url).iter_batches(batch_size=64):
65
+ # df = df.to_pandas()
66
+ # for i in range(len(df)):
67
+ # sample.update(dict(df.loc[i]))
68
+ # if mode == 'train':
69
+ # # NOTE do not return sample directly, must initialize a new dict
70
+ # yield {**sample}
71
+ # else:
72
+ # for index, text in enumerate(tts_data[df.loc[i, 'utt']]):
73
+ # yield {**sample, 'tts_index': index, 'tts_text': text}
74
+ # except Exception as ex:
75
+ # logging.warning('Failed to open {}, ex info {}'.format(url, ex))
76
+
77
+
78
+ def filter(data,
79
+ max_length=10240,
80
+ min_length=10,
81
+ token_max_length=200,
82
+ token_min_length=1,
83
+ min_output_input_ratio=0.0005,
84
+ max_output_input_ratio=1,
85
+ mode='train'):
86
+ """ Filter sample according to feature and label length
87
+ Inplace operation.
88
+
89
+ Args::
90
+ data: Iterable[{key, wav, label, sample_rate}]
91
+ max_length: drop utterance which is greater than max_length(10ms)
92
+ min_length: drop utterance which is less than min_length(10ms)
93
+ token_max_length: drop utterance which is greater than
94
+ token_max_length, especially when use char unit for
95
+ english modeling
96
+ token_min_length: drop utterance which is
97
+ less than token_max_length
98
+ min_output_input_ratio: minimal ration of
99
+ token_length / feats_length(10ms)
100
+ max_output_input_ratio: maximum ration of
101
+ token_length / feats_length(10ms)
102
+
103
+ Returns:
104
+ Iterable[{key, wav, label, sample_rate}]
105
+ """
106
+ for sample in data:
107
+ sample['speech'], sample['sample_rate'] = torchaudio.load(BytesIO(sample['audio_data']))
108
+ sample['speech'] = sample['speech'].mean(dim=0, keepdim=True)
109
+ del sample['audio_data']
110
+ # sample['wav'] is torch.Tensor, we have 100 frames every second
111
+ num_frames = sample['speech'].size(1) / sample['sample_rate'] * 100
112
+ if num_frames < min_length:
113
+ continue
114
+ if num_frames > max_length:
115
+ continue
116
+ if len(sample['text_token']) < token_min_length:
117
+ continue
118
+ if len(sample['text_token']) > token_max_length:
119
+ continue
120
+ if len(sample['speech_token']) == 0:
121
+ continue
122
+ if 'reject_speech_token' in sample and len(sample['reject_speech_token']) == 0:
123
+ continue
124
+ if num_frames != 0:
125
+ if len(sample['text_token']) / num_frames < min_output_input_ratio:
126
+ continue
127
+ if len(sample['text_token']) / num_frames > max_output_input_ratio:
128
+ continue
129
+ yield sample
130
+
131
+
132
+ def resample(data, resample_rate=22050, min_sample_rate=16000, mode='train'):
133
+ """ Resample data.
134
+ Inplace operation.
135
+
136
+ Args:
137
+ data: Iterable[{key, wav, label, sample_rate}]
138
+ resample_rate: target resample rate
139
+
140
+ Returns:
141
+ Iterable[{key, wav, label, sample_rate}]
142
+ """
143
+ for sample in data:
144
+ assert 'sample_rate' in sample
145
+ assert 'speech' in sample
146
+ sample_rate = sample['sample_rate']
147
+ waveform = sample['speech']
148
+ if sample_rate != resample_rate:
149
+ if sample_rate < min_sample_rate:
150
+ continue
151
+ sample['sample_rate'] = resample_rate
152
+ sample['speech'] = torchaudio.transforms.Resample(
153
+ orig_freq=sample_rate, new_freq=resample_rate)(waveform)
154
+ max_val = sample['speech'].abs().max()
155
+ if max_val > 1:
156
+ sample['speech'] /= max_val
157
+ yield sample
158
+
159
+
160
+ def truncate(data, truncate_length=24576, mode='train'):
161
+ """ Truncate data.
162
+
163
+ Args:
164
+ data: Iterable[{key, wav, label, sample_rate}]
165
+ truncate_length: truncate length
166
+
167
+ Returns:
168
+ Iterable[{key, wav, label, sample_rate}]
169
+ """
170
+ for sample in data:
171
+ waveform = sample['speech']
172
+ if waveform.shape[1] > truncate_length:
173
+ start = random.randint(0, waveform.shape[1] - truncate_length)
174
+ waveform = waveform[:, start: start + truncate_length]
175
+ else:
176
+ waveform = torch.concat([waveform, torch.zeros(1, truncate_length - waveform.shape[1])], dim=1)
177
+ sample['speech'] = waveform
178
+ yield sample
179
+
180
+
181
+ def compute_fbank(data,
182
+ feat_extractor,
183
+ token_mel_ratio=0,
184
+ mode='train'):
185
+ """ Extract fbank
186
+
187
+ Args:
188
+ data: Iterable[{key, wav, label, sample_rate}]
189
+
190
+ Returns:
191
+ Iterable[{key, feat, label}]
192
+ """
193
+ for sample in data:
194
+ assert 'sample_rate' in sample
195
+ assert 'speech' in sample
196
+ assert 'utt' in sample
197
+ assert 'text_token' in sample
198
+ waveform = sample['speech']
199
+ feat = feat_extractor(waveform).squeeze(dim=0).transpose(0, 1)
200
+ if token_mel_ratio != 0:
201
+ # trim to align speech_token and speech_feat
202
+ token_len = int(min(feat.shape[0] / token_mel_ratio, sample["speech_token"].shape[0]))
203
+ feat = feat[:token_mel_ratio * token_len]
204
+ sample["speech_token"] = sample["speech_token"][:token_len]
205
+ sample['speech_feat'] = feat
206
+ yield sample
207
+
208
+
209
+ def compute_f0(data, sample_rate, hop_size, mode='train'):
210
+ """ Extract f0
211
+
212
+ Args:
213
+ data: Iterable[{key, wav, label, sample_rate}]
214
+
215
+ Returns:
216
+ Iterable[{key, feat, label}]
217
+ """
218
+ frame_period = hop_size * 1000 / sample_rate
219
+ for sample in data:
220
+ assert 'sample_rate' in sample
221
+ assert 'speech' in sample
222
+ assert 'utt' in sample
223
+ assert 'text_token' in sample
224
+ waveform = sample['speech']
225
+ _f0, t = pw.harvest(waveform.squeeze(dim=0).numpy().astype('double'), sample_rate, frame_period=frame_period)
226
+ if sum(_f0 != 0) < 5: # this happens when the algorithm fails
227
+ _f0, t = pw.dio(waveform.squeeze(dim=0).numpy().astype('double'), sample_rate, frame_period=frame_period) # if harvest fails, try dio
228
+ f0 = pw.stonemask(waveform.squeeze(dim=0).numpy().astype('double'), _f0, t, sample_rate)
229
+ f0 = F.interpolate(torch.from_numpy(f0).view(1, 1, -1), size=sample['speech_feat'].shape[0], mode='linear').view(-1)
230
+ sample['pitch_feat'] = f0
231
+ yield sample
232
+
233
+
234
+ def parse_embedding(data, normalize, mode='train'):
235
+ """ Parse utt_embedding/spk_embedding
236
+
237
+ Args:
238
+ data: Iterable[{key, wav, label, sample_rate}]
239
+
240
+ Returns:
241
+ Iterable[{key, feat, label}]
242
+ """
243
+ for sample in data:
244
+ sample['utt_embedding'] = torch.tensor(sample['utt_embedding'], dtype=torch.float32)
245
+ sample['spk_embedding'] = torch.tensor(sample['spk_embedding'], dtype=torch.float32)
246
+ if normalize:
247
+ sample['utt_embedding'] = F.normalize(sample['utt_embedding'], dim=0)
248
+ sample['spk_embedding'] = F.normalize(sample['spk_embedding'], dim=0)
249
+ yield sample
250
+
251
+
252
+ def tokenize(data, get_tokenizer, allowed_special, mode='train'):
253
+ """ Decode text to chars or BPE
254
+ Inplace operation
255
+
256
+ Args:
257
+ data: Iterable[{key, wav, txt, sample_rate}]
258
+
259
+ Returns:
260
+ Iterable[{key, wav, txt, tokens, label, sample_rate}]
261
+ """
262
+ tokenizer = get_tokenizer()
263
+ for sample in data:
264
+ assert 'text' in sample
265
+ sample['text_token'] = tokenizer.encode(sample['text'], allowed_special=allowed_special)
266
+ yield sample
267
+
268
+
269
+ def shuffle(data, shuffle_size=10000, mode='train'):
270
+ """ Local shuffle the data
271
+
272
+ Args:
273
+ data: Iterable[{key, feat, label}]
274
+ shuffle_size: buffer size for shuffle
275
+
276
+ Returns:
277
+ Iterable[{key, feat, label}]
278
+ """
279
+ buf = []
280
+ for sample in data:
281
+ buf.append(sample)
282
+ if len(buf) >= shuffle_size:
283
+ random.shuffle(buf)
284
+ for x in buf:
285
+ yield x
286
+ buf = []
287
+ # The sample left over
288
+ random.shuffle(buf)
289
+ for x in buf:
290
+ yield x
291
+
292
+
293
+ def sort(data, sort_size=500, mode='train'):
294
+ """ Sort the data by feature length.
295
+ Sort is used after shuffle and before batch, so we can group
296
+ utts with similar lengths into a batch, and `sort_size` should
297
+ be less than `shuffle_size`
298
+
299
+ Args:
300
+ data: Iterable[{key, feat, label}]
301
+ sort_size: buffer size for sort
302
+
303
+ Returns:
304
+ Iterable[{key, feat, label}]
305
+ """
306
+
307
+ buf = []
308
+ for sample in data:
309
+ buf.append(sample)
310
+ if len(buf) >= sort_size:
311
+ buf.sort(key=lambda x: len(x['speech_token']))
312
+ for x in buf:
313
+ yield x
314
+ buf = []
315
+ # The sample left over
316
+ buf.sort(key=lambda x: len(x['speech_token']))
317
+ for x in buf:
318
+ yield x
319
+
320
+
321
+ def static_batch(data, batch_size=16):
322
+ """ Static batch the data by `batch_size`
323
+
324
+ Args:
325
+ data: Iterable[{key, feat, label}]
326
+ batch_size: batch size
327
+
328
+ Returns:
329
+ Iterable[List[{key, feat, label}]]
330
+ """
331
+ buf = []
332
+ for sample in data:
333
+ buf.append(sample)
334
+ if len(buf) >= batch_size:
335
+ yield buf
336
+ buf = []
337
+ if len(buf) > 0:
338
+ yield buf
339
+
340
+
341
+ def dynamic_batch(data, max_frames_in_batch=12000, mode='train'):
342
+ """ Dynamic batch the data until the total frames in batch
343
+ reach `max_frames_in_batch`
344
+
345
+ Args:
346
+ data: Iterable[{key, feat, label}]
347
+ max_frames_in_batch: max_frames in one batch
348
+
349
+ Returns:
350
+ Iterable[List[{key, feat, label}]]
351
+ """
352
+ buf = []
353
+ longest_frames = 0
354
+ for sample in data:
355
+ # assert 'speech_token' in sample
356
+ # assert isinstance(sample['speech_token'], torch.Tensor)
357
+ new_sample_frames = len(sample['speech_token'])
358
+ longest_frames = max(longest_frames, new_sample_frames)
359
+ frames_after_padding = longest_frames * (len(buf) + 1)
360
+ if frames_after_padding > max_frames_in_batch:
361
+ yield buf
362
+ buf = [sample]
363
+ longest_frames = new_sample_frames
364
+ else:
365
+ buf.append(sample)
366
+ if len(buf) > 0:
367
+ yield buf
368
+
369
+
370
+ def batch(data, batch_type='static', batch_size=16, max_frames_in_batch=12000, mode='train'):
371
+ """ Wrapper for static/dynamic batch
372
+ """
373
+ if batch_type == 'static':
374
+ return static_batch(data, batch_size)
375
+ elif batch_type == 'dynamic':
376
+ return dynamic_batch(data, max_frames_in_batch)
377
+ else:
378
+ logging.fatal('Unsupported batch type {}'.format(batch_type))
379
+
380
+ import torch.distributed as dist
381
+
382
+ def padding(data, **kw):
383
+ """
384
+ padding 同时也承担“空 rank 补偿”职责:
385
+ 如果本 rank 没有产出任何 batch,就 yield 一条 dummy,
386
+ 保证所有 rank 的 DataLoader 迭代次数一致。
387
+ """
388
+ real_yield = False
389
+ for batch in data:
390
+ real_yield = True
391
+ keys = [x['key'] for x in batch]
392
+ text_token = [torch.tensor(x['text_token'], dtype=torch.long) for x in batch]
393
+ speech_token = [torch.tensor(x['speech_token'], dtype=torch.long) for x in batch]
394
+
395
+ text_token_len = torch.tensor([t.size(0) for t in text_token], dtype=torch.long)
396
+ speech_token_len = torch.tensor([s.size(0) for s in speech_token], dtype=torch.long)
397
+
398
+ text_token = pad_sequence(text_token, batch_first=True, padding_value=0)
399
+ speech_token = pad_sequence(speech_token, batch_first=True, padding_value=0)
400
+
401
+ yield {
402
+ 'key': keys,
403
+ 'text_token': text_token,
404
+ 'text_token_len': text_token_len,
405
+ 'speech_token': speech_token,
406
+ 'speech_token_len': speech_token_len,
407
+ }
408
+
409
+ # 如果本 rank 没产出任何 batch
410
+ if dist.is_initialized() and not real_yield:
411
+ dummy = {
412
+ 'key': ['dummy'],
413
+ 'text_token': torch.zeros(1, 1, dtype=torch.long),
414
+ 'text_token_len': torch.tensor([1]),
415
+ 'speech_token': torch.zeros(1, 1, dtype=torch.long),
416
+ 'speech_token_len': torch.tensor([1]),
417
+ }
418
+ yield dummy
419
+
420
+ # def padding(data, use_spk_embedding, mode='train', gan=False, dpo=False):
421
+ # """ Padding the data into training data
422
+
423
+ # Args:
424
+ # data: Iterable[List[{key, feat, label}]]
425
+
426
+ # Returns:
427
+ # Iterable[Tuple(keys, feats, labels, feats lengths, label lengths)]
428
+ # """
429
+ # for sample in data:
430
+ # assert isinstance(sample, list)
431
+ # speech_feat_len = torch.tensor([x['speech_feat'].size(1) for x in sample],
432
+ # dtype=torch.int32)
433
+ # order = torch.argsort(speech_feat_len, descending=True)
434
+
435
+ # utts = [sample[i]['utt'] for i in order]
436
+ # speech = [sample[i]['speech'].squeeze(dim=0) for i in order]
437
+ # speech_len = torch.tensor([i.size(0) for i in speech], dtype=torch.int32)
438
+ # speech = pad_sequence(speech, batch_first=True, padding_value=0)
439
+ # speech_token = [torch.tensor(sample[i]['speech_token']) for i in order]
440
+ # speech_token_len = torch.tensor([i.size(0) for i in speech_token], dtype=torch.int32)
441
+ # speech_token = pad_sequence(speech_token,
442
+ # batch_first=True,
443
+ # padding_value=0)
444
+ # speech_feat = [sample[i]['speech_feat'] for i in order]
445
+ # speech_feat_len = torch.tensor([i.size(0) for i in speech_feat], dtype=torch.int32)
446
+ # speech_feat = pad_sequence(speech_feat,
447
+ # batch_first=True,
448
+ # padding_value=0)
449
+ # text = [sample[i]['text'] for i in order]
450
+ # text_token = [torch.tensor(sample[i]['text_token']) for i in order]
451
+ # text_token_len = torch.tensor([i.size(0) for i in text_token], dtype=torch.int32)
452
+ # text_token = pad_sequence(text_token, batch_first=True, padding_value=0)
453
+ # utt_embedding = torch.stack([sample[i]['utt_embedding'] for i in order], dim=0)
454
+ # spk_embedding = torch.stack([sample[i]['spk_embedding'] for i in order], dim=0)
455
+ # batch = {
456
+ # "utts": utts,
457
+ # "speech": speech,
458
+ # "speech_len": speech_len,
459
+ # "speech_token": speech_token,
460
+ # "speech_token_len": speech_token_len,
461
+ # "speech_feat": speech_feat,
462
+ # "speech_feat_len": speech_feat_len,
463
+ # "text": text,
464
+ # "text_token": text_token,
465
+ # "text_token_len": text_token_len,
466
+ # "utt_embedding": utt_embedding,
467
+ # "spk_embedding": spk_embedding,
468
+ # }
469
+ # if gan is True:
470
+ # # in gan train, we need pitch_feat
471
+ # pitch_feat = [sample[i]['pitch_feat'] for i in order]
472
+ # pitch_feat_len = torch.tensor([i.size(0) for i in pitch_feat], dtype=torch.int32)
473
+ # pitch_feat = pad_sequence(pitch_feat,
474
+ # batch_first=True,
475
+ # padding_value=0)
476
+ # batch["pitch_feat"] = pitch_feat
477
+ # batch["pitch_feat_len"] = pitch_feat_len
478
+ # else:
479
+ # # only gan train needs speech, delete it to save memory
480
+ # del batch["speech"]
481
+ # del batch["speech_len"]
482
+ # if dpo is True:
483
+ # reject_speech_token = [torch.tensor(sample[i]['reject_speech_token']) for i in order]
484
+ # reject_speech_token_len = torch.tensor([i.size(0) for i in reject_speech_token], dtype=torch.int32)
485
+ # reject_speech_token = pad_sequence(reject_speech_token,
486
+ # batch_first=True,
487
+ # padding_value=0)
488
+ # batch['reject_speech_token'] = reject_speech_token
489
+ # batch['reject_speech_token_len'] = reject_speech_token_len
490
+ # if use_spk_embedding is True:
491
+ # batch["embedding"] = batch["spk_embedding"]
492
+ # else:
493
+ # batch["embedding"] = batch["utt_embedding"]
494
+ # yield batch
cosyvoice/dataset/dataset.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2021 Mobvoi Inc. (authors: Binbin Zhang)
2
+ # 2024 Alibaba Inc (authors: Xiang Lyu)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import random
17
+ import math
18
+ from functools import partial
19
+
20
+ import torch
21
+ import torch.distributed as dist
22
+ from torch.utils.data import IterableDataset
23
+ from cosyvoice.utils.file_utils import read_lists
24
+
25
+
26
+ class Processor(IterableDataset):
27
+
28
+ def __init__(self, source, f, *args, **kw):
29
+ assert callable(f)
30
+ self.source = source
31
+ self.f = f
32
+ self.args = args
33
+ self.kw = kw
34
+
35
+ def set_epoch(self, epoch):
36
+ self.source.set_epoch(epoch)
37
+
38
+ def __iter__(self):
39
+ """ Return an iterator over the source dataset processed by the
40
+ given processor.
41
+ """
42
+ assert self.source is not None
43
+ assert callable(self.f)
44
+ return self.f(iter(self.source), *self.args, **self.kw)
45
+
46
+ def apply(self, f):
47
+ assert callable(f)
48
+ return Processor(self, f, *self.args, **self.kw)
49
+
50
+
51
+ class DistributedSampler:
52
+
53
+ def __init__(self, shuffle=True, partition=True):
54
+ self.epoch = -1
55
+ self.update()
56
+ self.shuffle = shuffle
57
+ self.partition = partition
58
+
59
+ def update(self):
60
+ assert dist.is_available()
61
+ if dist.is_initialized():
62
+ self.rank = dist.get_rank()
63
+ self.world_size = dist.get_world_size()
64
+ else:
65
+ self.rank = 0
66
+ self.world_size = 1
67
+ worker_info = torch.utils.data.get_worker_info()
68
+ if worker_info is None:
69
+ self.worker_id = 0
70
+ self.num_workers = 1
71
+ else:
72
+ self.worker_id = worker_info.id
73
+ self.num_workers = worker_info.num_workers
74
+ return dict(rank=self.rank,
75
+ world_size=self.world_size,
76
+ worker_id=self.worker_id,
77
+ num_workers=self.num_workers)
78
+
79
+ def set_epoch(self, epoch):
80
+ self.epoch = epoch
81
+
82
+ def sample(self, data):
83
+ """ Sample data according to rank/world_size/num_workers
84
+
85
+ Args:
86
+ data(List): input data list
87
+
88
+ Returns:
89
+ List: data list after sample
90
+ """
91
+ data = list(range(len(data)))
92
+ # force datalist even
93
+ if self.partition:
94
+ if self.shuffle:
95
+ random.Random(self.epoch).shuffle(data)
96
+ if len(data) < self.world_size:
97
+ data = data * math.ceil(self.world_size / len(data))
98
+ data = data[:self.world_size]
99
+ data = data[self.rank::self.world_size]
100
+ if len(data) < self.num_workers:
101
+ data = data * math.ceil(self.num_workers / len(data))
102
+ data = data[:self.num_workers]
103
+ data = data[self.worker_id::self.num_workers]
104
+ return data
105
+
106
+
107
+ class DataList(IterableDataset):
108
+
109
+ def __init__(self, lists, shuffle=True, partition=True):
110
+ self.lists = lists
111
+ self.sampler = DistributedSampler(shuffle, partition)
112
+
113
+ def set_epoch(self, epoch):
114
+ self.sampler.set_epoch(epoch)
115
+
116
+ def __iter__(self):
117
+ sampler_info = self.sampler.update()
118
+ indexes = self.sampler.sample(self.lists)
119
+ for index in indexes:
120
+ data = dict(src=self.lists[index])
121
+ data.update(sampler_info)
122
+ yield data
123
+
124
+
125
+ def Dataset(data_list_file,
126
+ data_pipeline,
127
+ mode='train',
128
+ gan=False,
129
+ dpo=False,
130
+ shuffle=True,
131
+ partition=True):
132
+ """ Construct dataset from arguments
133
+
134
+ We have two shuffle stage in the Dataset. The first is global
135
+ shuffle at shards tar/raw file level. The second is global shuffle
136
+ at training samples level.
137
+
138
+ Args:
139
+ data_type(str): raw/shard
140
+ tokenizer (BaseTokenizer): tokenizer to tokenize
141
+ partition(bool): whether to do data partition in terms of rank
142
+ """
143
+ lists = read_lists(data_list_file)
144
+ dataset = DataList(lists,
145
+ shuffle=shuffle,
146
+ partition=partition)
147
+ # map partial arg to padding func
148
+ data_pipeline[-1] = partial(data_pipeline[-1], gan=gan, dpo=dpo)
149
+ for func in data_pipeline:
150
+ dataset = Processor(dataset, func, mode=mode)
151
+ return dataset
cosyvoice/dataset/processor.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import logging
15
+ import random
16
+
17
+ import pyarrow.parquet as pq
18
+ from io import BytesIO
19
+ import torch
20
+ import torchaudio
21
+ from torch.nn.utils.rnn import pad_sequence
22
+ import torch.nn.functional as F
23
+ import pyworld as pw
24
+
25
+
26
+ AUDIO_FORMAT_SETS = {'flac', 'mp3', 'm4a', 'ogg', 'opus', 'wav', 'wma'}
27
+
28
+
29
+ def parquet_opener(data, mode='train', tts_data={}):
30
+ """ Give url or local file, return file descriptor
31
+ Inplace operation.
32
+
33
+ Args:
34
+ data(Iterable[str]): url or local file list
35
+
36
+ Returns:
37
+ Iterable[{src, stream}]
38
+ """
39
+ for sample in data:
40
+ assert 'src' in sample
41
+ url = sample['src']
42
+ try:
43
+ for df in pq.ParquetFile(url).iter_batches(batch_size=64):
44
+ df = df.to_pandas()
45
+ for i in range(len(df)):
46
+ sample.update(dict(df.loc[i]))
47
+ if mode == 'train':
48
+ # NOTE do not return sample directly, must initialize a new dict
49
+ yield {**sample}
50
+ else:
51
+ for index, text in enumerate(tts_data[df.loc[i, 'utt']]):
52
+ yield {**sample, 'tts_index': index, 'tts_text': text}
53
+ except Exception as ex:
54
+ logging.warning('Failed to open {}, ex info {}'.format(url, ex))
55
+
56
+
57
+ def filter(data,
58
+ max_length=10240,
59
+ min_length=10,
60
+ token_max_length=200,
61
+ token_min_length=1,
62
+ min_output_input_ratio=0.0005,
63
+ max_output_input_ratio=1,
64
+ mode='train'):
65
+ """ Filter sample according to feature and label length
66
+ Inplace operation.
67
+
68
+ Args::
69
+ data: Iterable[{key, wav, label, sample_rate}]
70
+ max_length: drop utterance which is greater than max_length(10ms)
71
+ min_length: drop utterance which is less than min_length(10ms)
72
+ token_max_length: drop utterance which is greater than
73
+ token_max_length, especially when use char unit for
74
+ english modeling
75
+ token_min_length: drop utterance which is
76
+ less than token_max_length
77
+ min_output_input_ratio: minimal ration of
78
+ token_length / feats_length(10ms)
79
+ max_output_input_ratio: maximum ration of
80
+ token_length / feats_length(10ms)
81
+
82
+ Returns:
83
+ Iterable[{key, wav, label, sample_rate}]
84
+ """
85
+ for sample in data:
86
+ sample['speech'], sample['sample_rate'] = torchaudio.load(BytesIO(sample['audio_data']))
87
+ sample['speech'] = sample['speech'].mean(dim=0, keepdim=True)
88
+ del sample['audio_data']
89
+ # sample['wav'] is torch.Tensor, we have 100 frames every second
90
+ num_frames = sample['speech'].size(1) / sample['sample_rate'] * 100
91
+ if num_frames < min_length:
92
+ continue
93
+ if num_frames > max_length:
94
+ continue
95
+ if len(sample['text_token']) < token_min_length:
96
+ continue
97
+ if len(sample['text_token']) > token_max_length:
98
+ continue
99
+ if len(sample['speech_token']) == 0:
100
+ continue
101
+ if 'reject_speech_token' in sample and len(sample['reject_speech_token']) == 0:
102
+ continue
103
+ if num_frames != 0:
104
+ if len(sample['text_token']) / num_frames < min_output_input_ratio:
105
+ continue
106
+ if len(sample['text_token']) / num_frames > max_output_input_ratio:
107
+ continue
108
+ yield sample
109
+
110
+
111
+ def resample(data, resample_rate=22050, min_sample_rate=16000, mode='train'):
112
+ """ Resample data.
113
+ Inplace operation.
114
+
115
+ Args:
116
+ data: Iterable[{key, wav, label, sample_rate}]
117
+ resample_rate: target resample rate
118
+
119
+ Returns:
120
+ Iterable[{key, wav, label, sample_rate}]
121
+ """
122
+ for sample in data:
123
+ assert 'sample_rate' in sample
124
+ assert 'speech' in sample
125
+ sample_rate = sample['sample_rate']
126
+ waveform = sample['speech']
127
+ if sample_rate != resample_rate:
128
+ if sample_rate < min_sample_rate:
129
+ continue
130
+ sample['sample_rate'] = resample_rate
131
+ sample['speech'] = torchaudio.transforms.Resample(
132
+ orig_freq=sample_rate, new_freq=resample_rate)(waveform)
133
+ max_val = sample['speech'].abs().max()
134
+ if max_val > 1:
135
+ sample['speech'] /= max_val
136
+ yield sample
137
+
138
+
139
+ def truncate(data, truncate_length=24576, mode='train'):
140
+ """ Truncate data.
141
+
142
+ Args:
143
+ data: Iterable[{key, wav, label, sample_rate}]
144
+ truncate_length: truncate length
145
+
146
+ Returns:
147
+ Iterable[{key, wav, label, sample_rate}]
148
+ """
149
+ for sample in data:
150
+ waveform = sample['speech']
151
+ if waveform.shape[1] > truncate_length:
152
+ start = random.randint(0, waveform.shape[1] - truncate_length)
153
+ waveform = waveform[:, start: start + truncate_length]
154
+ else:
155
+ waveform = torch.concat([waveform, torch.zeros(1, truncate_length - waveform.shape[1])], dim=1)
156
+ sample['speech'] = waveform
157
+ yield sample
158
+
159
+
160
+ def compute_fbank(data,
161
+ feat_extractor,
162
+ token_mel_ratio=0,
163
+ mode='train'):
164
+ """ Extract fbank
165
+
166
+ Args:
167
+ data: Iterable[{key, wav, label, sample_rate}]
168
+
169
+ Returns:
170
+ Iterable[{key, feat, label}]
171
+ """
172
+ for sample in data:
173
+ assert 'sample_rate' in sample
174
+ assert 'speech' in sample
175
+ assert 'utt' in sample
176
+ assert 'text_token' in sample
177
+ waveform = sample['speech']
178
+ feat = feat_extractor(waveform).squeeze(dim=0).transpose(0, 1)
179
+ if token_mel_ratio != 0:
180
+ # trim to align speech_token and speech_feat
181
+ token_len = int(min(feat.shape[0] / token_mel_ratio, sample["speech_token"].shape[0]))
182
+ feat = feat[:token_mel_ratio * token_len]
183
+ sample["speech_token"] = sample["speech_token"][:token_len]
184
+ sample['speech_feat'] = feat
185
+ yield sample
186
+
187
+
188
+ def compute_f0(data, sample_rate, hop_size, mode='train'):
189
+ """ Extract f0
190
+
191
+ Args:
192
+ data: Iterable[{key, wav, label, sample_rate}]
193
+
194
+ Returns:
195
+ Iterable[{key, feat, label}]
196
+ """
197
+ frame_period = hop_size * 1000 / sample_rate
198
+ for sample in data:
199
+ assert 'sample_rate' in sample
200
+ assert 'speech' in sample
201
+ assert 'utt' in sample
202
+ assert 'text_token' in sample
203
+ waveform = sample['speech']
204
+ _f0, t = pw.harvest(waveform.squeeze(dim=0).numpy().astype('double'), sample_rate, frame_period=frame_period)
205
+ if sum(_f0 != 0) < 5: # this happens when the algorithm fails
206
+ _f0, t = pw.dio(waveform.squeeze(dim=0).numpy().astype('double'), sample_rate, frame_period=frame_period) # if harvest fails, try dio
207
+ f0 = pw.stonemask(waveform.squeeze(dim=0).numpy().astype('double'), _f0, t, sample_rate)
208
+ f0 = F.interpolate(torch.from_numpy(f0).view(1, 1, -1), size=sample['speech_feat'].shape[0], mode='linear').view(-1)
209
+ sample['pitch_feat'] = f0
210
+ yield sample
211
+
212
+
213
+ def parse_embedding(data, normalize, mode='train'):
214
+ """ Parse utt_embedding/spk_embedding
215
+
216
+ Args:
217
+ data: Iterable[{key, wav, label, sample_rate}]
218
+
219
+ Returns:
220
+ Iterable[{key, feat, label}]
221
+ """
222
+ for sample in data:
223
+ sample['utt_embedding'] = torch.tensor(sample['utt_embedding'], dtype=torch.float32)
224
+ sample['spk_embedding'] = torch.tensor(sample['spk_embedding'], dtype=torch.float32)
225
+ if normalize:
226
+ sample['utt_embedding'] = F.normalize(sample['utt_embedding'], dim=0)
227
+ sample['spk_embedding'] = F.normalize(sample['spk_embedding'], dim=0)
228
+ yield sample
229
+
230
+
231
+ def tokenize(data, get_tokenizer, allowed_special, mode='train'):
232
+ """ Decode text to chars or BPE
233
+ Inplace operation
234
+
235
+ Args:
236
+ data: Iterable[{key, wav, txt, sample_rate}]
237
+
238
+ Returns:
239
+ Iterable[{key, wav, txt, tokens, label, sample_rate}]
240
+ """
241
+ tokenizer = get_tokenizer()
242
+ for sample in data:
243
+ assert 'text' in sample
244
+ sample['text_token'] = tokenizer.encode(sample['text'], allowed_special=allowed_special)
245
+ yield sample
246
+
247
+
248
+ def shuffle(data, shuffle_size=10000, mode='train'):
249
+ """ Local shuffle the data
250
+
251
+ Args:
252
+ data: Iterable[{key, feat, label}]
253
+ shuffle_size: buffer size for shuffle
254
+
255
+ Returns:
256
+ Iterable[{key, feat, label}]
257
+ """
258
+ buf = []
259
+ for sample in data:
260
+ buf.append(sample)
261
+ if len(buf) >= shuffle_size:
262
+ random.shuffle(buf)
263
+ for x in buf:
264
+ yield x
265
+ buf = []
266
+ # The sample left over
267
+ random.shuffle(buf)
268
+ for x in buf:
269
+ yield x
270
+
271
+
272
+ def sort(data, sort_size=500, mode='train'):
273
+ """ Sort the data by feature length.
274
+ Sort is used after shuffle and before batch, so we can group
275
+ utts with similar lengths into a batch, and `sort_size` should
276
+ be less than `shuffle_size`
277
+
278
+ Args:
279
+ data: Iterable[{key, feat, label}]
280
+ sort_size: buffer size for sort
281
+
282
+ Returns:
283
+ Iterable[{key, feat, label}]
284
+ """
285
+
286
+ buf = []
287
+ for sample in data:
288
+ buf.append(sample)
289
+ if len(buf) >= sort_size:
290
+ buf.sort(key=lambda x: x['speech_feat'].size(0))
291
+ for x in buf:
292
+ yield x
293
+ buf = []
294
+ # The sample left over
295
+ buf.sort(key=lambda x: x['speech_feat'].size(0))
296
+ for x in buf:
297
+ yield x
298
+
299
+
300
+ def static_batch(data, batch_size=16):
301
+ """ Static batch the data by `batch_size`
302
+
303
+ Args:
304
+ data: Iterable[{key, feat, label}]
305
+ batch_size: batch size
306
+
307
+ Returns:
308
+ Iterable[List[{key, feat, label}]]
309
+ """
310
+ buf = []
311
+ for sample in data:
312
+ buf.append(sample)
313
+ if len(buf) >= batch_size:
314
+ yield buf
315
+ buf = []
316
+ if len(buf) > 0:
317
+ yield buf
318
+
319
+
320
+ def dynamic_batch(data, max_frames_in_batch=12000, mode='train'):
321
+ """ Dynamic batch the data until the total frames in batch
322
+ reach `max_frames_in_batch`
323
+
324
+ Args:
325
+ data: Iterable[{key, feat, label}]
326
+ max_frames_in_batch: max_frames in one batch
327
+
328
+ Returns:
329
+ Iterable[List[{key, feat, label}]]
330
+ """
331
+ buf = []
332
+ longest_frames = 0
333
+ for sample in data:
334
+ assert 'speech_feat' in sample
335
+ assert isinstance(sample['speech_feat'], torch.Tensor)
336
+ new_sample_frames = sample['speech_feat'].size(0)
337
+ longest_frames = max(longest_frames, new_sample_frames)
338
+ frames_after_padding = longest_frames * (len(buf) + 1)
339
+ if frames_after_padding > max_frames_in_batch:
340
+ yield buf
341
+ buf = [sample]
342
+ longest_frames = new_sample_frames
343
+ else:
344
+ buf.append(sample)
345
+ if len(buf) > 0:
346
+ yield buf
347
+
348
+
349
+ def batch(data, batch_type='static', batch_size=16, max_frames_in_batch=12000, mode='train'):
350
+ """ Wrapper for static/dynamic batch
351
+ """
352
+ if batch_type == 'static':
353
+ return static_batch(data, batch_size)
354
+ elif batch_type == 'dynamic':
355
+ return dynamic_batch(data, max_frames_in_batch)
356
+ else:
357
+ logging.fatal('Unsupported batch type {}'.format(batch_type))
358
+
359
+
360
+ def padding(data, use_spk_embedding, mode='train', gan=False, dpo=False):
361
+ """ Padding the data into training data
362
+
363
+ Args:
364
+ data: Iterable[List[{key, feat, label}]]
365
+
366
+ Returns:
367
+ Iterable[Tuple(keys, feats, labels, feats lengths, label lengths)]
368
+ """
369
+ for sample in data:
370
+ assert isinstance(sample, list)
371
+ speech_feat_len = torch.tensor([x['speech_feat'].size(1) for x in sample],
372
+ dtype=torch.int32)
373
+ order = torch.argsort(speech_feat_len, descending=True)
374
+
375
+ utts = [sample[i]['utt'] for i in order]
376
+ speech = [sample[i]['speech'].squeeze(dim=0) for i in order]
377
+ speech_len = torch.tensor([i.size(0) for i in speech], dtype=torch.int32)
378
+ speech = pad_sequence(speech, batch_first=True, padding_value=0)
379
+ speech_token = [torch.tensor(sample[i]['speech_token']) for i in order]
380
+ speech_token_len = torch.tensor([i.size(0) for i in speech_token], dtype=torch.int32)
381
+ speech_token = pad_sequence(speech_token,
382
+ batch_first=True,
383
+ padding_value=0)
384
+ speech_feat = [sample[i]['speech_feat'] for i in order]
385
+ speech_feat_len = torch.tensor([i.size(0) for i in speech_feat], dtype=torch.int32)
386
+ speech_feat = pad_sequence(speech_feat,
387
+ batch_first=True,
388
+ padding_value=0)
389
+ text = [sample[i]['text'] for i in order]
390
+ text_token = [torch.tensor(sample[i]['text_token']) for i in order]
391
+ text_token_len = torch.tensor([i.size(0) for i in text_token], dtype=torch.int32)
392
+ text_token = pad_sequence(text_token, batch_first=True, padding_value=0)
393
+ utt_embedding = torch.stack([sample[i]['utt_embedding'] for i in order], dim=0)
394
+ spk_embedding = torch.stack([sample[i]['spk_embedding'] for i in order], dim=0)
395
+ batch = {
396
+ "utts": utts,
397
+ "speech": speech,
398
+ "speech_len": speech_len,
399
+ "speech_token": speech_token,
400
+ "speech_token_len": speech_token_len,
401
+ "speech_feat": speech_feat,
402
+ "speech_feat_len": speech_feat_len,
403
+ "text": text,
404
+ "text_token": text_token,
405
+ "text_token_len": text_token_len,
406
+ "utt_embedding": utt_embedding,
407
+ "spk_embedding": spk_embedding,
408
+ }
409
+ if gan is True:
410
+ # in gan train, we need pitch_feat
411
+ pitch_feat = [sample[i]['pitch_feat'] for i in order]
412
+ pitch_feat_len = torch.tensor([i.size(0) for i in pitch_feat], dtype=torch.int32)
413
+ pitch_feat = pad_sequence(pitch_feat,
414
+ batch_first=True,
415
+ padding_value=0)
416
+ batch["pitch_feat"] = pitch_feat
417
+ batch["pitch_feat_len"] = pitch_feat_len
418
+ else:
419
+ # only gan train needs speech, delete it to save memory
420
+ del batch["speech"]
421
+ del batch["speech_len"]
422
+ if dpo is True:
423
+ reject_speech_token = [torch.tensor(sample[i]['reject_speech_token']) for i in order]
424
+ reject_speech_token_len = torch.tensor([i.size(0) for i in reject_speech_token], dtype=torch.int32)
425
+ reject_speech_token = pad_sequence(reject_speech_token,
426
+ batch_first=True,
427
+ padding_value=0)
428
+ batch['reject_speech_token'] = reject_speech_token
429
+ batch['reject_speech_token_len'] = reject_speech_token_len
430
+ if use_spk_embedding is True:
431
+ batch["embedding"] = batch["spk_embedding"]
432
+ else:
433
+ batch["embedding"] = batch["utt_embedding"]
434
+ yield batch
cosyvoice/flow/__pycache__/decoder.cpython-310.pyc ADDED
Binary file (11.1 kB). View file
 
cosyvoice/flow/__pycache__/flow.cpython-310.pyc ADDED
Binary file (7.24 kB). View file
 
cosyvoice/flow/__pycache__/flow_matching.cpython-310.pyc ADDED
Binary file (7.29 kB). View file
 
cosyvoice/flow/decoder.py ADDED
@@ -0,0 +1,494 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Zhihao Du)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Tuple
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+ from einops import pack, rearrange, repeat
19
+ from cosyvoice.utils.common import mask_to_bias
20
+ from cosyvoice.utils.mask import add_optional_chunk_mask
21
+ from matcha.models.components.decoder import SinusoidalPosEmb, Block1D, ResnetBlock1D, Downsample1D, TimestepEmbedding, Upsample1D
22
+ from matcha.models.components.transformer import BasicTransformerBlock
23
+
24
+
25
+ class Transpose(torch.nn.Module):
26
+ def __init__(self, dim0: int, dim1: int):
27
+ super().__init__()
28
+ self.dim0 = dim0
29
+ self.dim1 = dim1
30
+
31
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
32
+ x = torch.transpose(x, self.dim0, self.dim1)
33
+ return x
34
+
35
+
36
+ class CausalConv1d(torch.nn.Conv1d):
37
+ def __init__(
38
+ self,
39
+ in_channels: int,
40
+ out_channels: int,
41
+ kernel_size: int,
42
+ stride: int = 1,
43
+ dilation: int = 1,
44
+ groups: int = 1,
45
+ bias: bool = True,
46
+ padding_mode: str = 'zeros',
47
+ device=None,
48
+ dtype=None
49
+ ) -> None:
50
+ super(CausalConv1d, self).__init__(in_channels, out_channels,
51
+ kernel_size, stride,
52
+ padding=0, dilation=dilation,
53
+ groups=groups, bias=bias,
54
+ padding_mode=padding_mode,
55
+ device=device, dtype=dtype)
56
+ assert stride == 1
57
+ self.causal_padding = kernel_size - 1
58
+
59
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
60
+ x = F.pad(x, (self.causal_padding, 0), value=0.0)
61
+ x = super(CausalConv1d, self).forward(x)
62
+ return x
63
+
64
+
65
+ class CausalBlock1D(Block1D):
66
+ def __init__(self, dim: int, dim_out: int):
67
+ super(CausalBlock1D, self).__init__(dim, dim_out)
68
+ self.block = torch.nn.Sequential(
69
+ CausalConv1d(dim, dim_out, 3),
70
+ Transpose(1, 2),
71
+ nn.LayerNorm(dim_out),
72
+ Transpose(1, 2),
73
+ nn.Mish(),
74
+ )
75
+
76
+ def forward(self, x: torch.Tensor, mask: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
77
+ output = self.block(x * mask)
78
+ return output * mask
79
+
80
+
81
+ class CausalResnetBlock1D(ResnetBlock1D):
82
+ def __init__(self, dim: int, dim_out: int, time_emb_dim: int, groups: int = 8):
83
+ super(CausalResnetBlock1D, self).__init__(dim, dim_out, time_emb_dim, groups)
84
+ self.block1 = CausalBlock1D(dim, dim_out)
85
+ self.block2 = CausalBlock1D(dim_out, dim_out)
86
+
87
+
88
+ class ConditionalDecoder(nn.Module):
89
+ def __init__(
90
+ self,
91
+ in_channels,
92
+ out_channels,
93
+ channels=(256, 256),
94
+ dropout=0.05,
95
+ attention_head_dim=64,
96
+ n_blocks=1,
97
+ num_mid_blocks=2,
98
+ num_heads=4,
99
+ act_fn="snake",
100
+ ):
101
+ """
102
+ This decoder requires an input with the same shape of the target. So, if your text content
103
+ is shorter or longer than the outputs, please re-sampling it before feeding to the decoder.
104
+ """
105
+ super().__init__()
106
+ channels = tuple(channels)
107
+ self.in_channels = in_channels
108
+ self.out_channels = out_channels
109
+
110
+ self.time_embeddings = SinusoidalPosEmb(in_channels)
111
+ time_embed_dim = channels[0] * 4
112
+ self.time_mlp = TimestepEmbedding(
113
+ in_channels=in_channels,
114
+ time_embed_dim=time_embed_dim,
115
+ act_fn="silu",
116
+ )
117
+ self.down_blocks = nn.ModuleList([])
118
+ self.mid_blocks = nn.ModuleList([])
119
+ self.up_blocks = nn.ModuleList([])
120
+
121
+ output_channel = in_channels
122
+ for i in range(len(channels)): # pylint: disable=consider-using-enumerate
123
+ input_channel = output_channel
124
+ output_channel = channels[i]
125
+ is_last = i == len(channels) - 1
126
+ resnet = ResnetBlock1D(dim=input_channel, dim_out=output_channel, time_emb_dim=time_embed_dim)
127
+ transformer_blocks = nn.ModuleList(
128
+ [
129
+ BasicTransformerBlock(
130
+ dim=output_channel,
131
+ num_attention_heads=num_heads,
132
+ attention_head_dim=attention_head_dim,
133
+ dropout=dropout,
134
+ activation_fn=act_fn,
135
+ )
136
+ for _ in range(n_blocks)
137
+ ]
138
+ )
139
+ downsample = (
140
+ Downsample1D(output_channel) if not is_last else nn.Conv1d(output_channel, output_channel, 3, padding=1)
141
+ )
142
+ self.down_blocks.append(nn.ModuleList([resnet, transformer_blocks, downsample]))
143
+
144
+ for _ in range(num_mid_blocks):
145
+ input_channel = channels[-1]
146
+ out_channels = channels[-1]
147
+ resnet = ResnetBlock1D(dim=input_channel, dim_out=output_channel, time_emb_dim=time_embed_dim)
148
+
149
+ transformer_blocks = nn.ModuleList(
150
+ [
151
+ BasicTransformerBlock(
152
+ dim=output_channel,
153
+ num_attention_heads=num_heads,
154
+ attention_head_dim=attention_head_dim,
155
+ dropout=dropout,
156
+ activation_fn=act_fn,
157
+ )
158
+ for _ in range(n_blocks)
159
+ ]
160
+ )
161
+
162
+ self.mid_blocks.append(nn.ModuleList([resnet, transformer_blocks]))
163
+
164
+ channels = channels[::-1] + (channels[0],)
165
+ for i in range(len(channels) - 1):
166
+ input_channel = channels[i] * 2
167
+ output_channel = channels[i + 1]
168
+ is_last = i == len(channels) - 2
169
+ resnet = ResnetBlock1D(
170
+ dim=input_channel,
171
+ dim_out=output_channel,
172
+ time_emb_dim=time_embed_dim,
173
+ )
174
+ transformer_blocks = nn.ModuleList(
175
+ [
176
+ BasicTransformerBlock(
177
+ dim=output_channel,
178
+ num_attention_heads=num_heads,
179
+ attention_head_dim=attention_head_dim,
180
+ dropout=dropout,
181
+ activation_fn=act_fn,
182
+ )
183
+ for _ in range(n_blocks)
184
+ ]
185
+ )
186
+ upsample = (
187
+ Upsample1D(output_channel, use_conv_transpose=True)
188
+ if not is_last
189
+ else nn.Conv1d(output_channel, output_channel, 3, padding=1)
190
+ )
191
+ self.up_blocks.append(nn.ModuleList([resnet, transformer_blocks, upsample]))
192
+ self.final_block = Block1D(channels[-1], channels[-1])
193
+ self.final_proj = nn.Conv1d(channels[-1], self.out_channels, 1)
194
+ self.initialize_weights()
195
+
196
+ def initialize_weights(self):
197
+ for m in self.modules():
198
+ if isinstance(m, nn.Conv1d):
199
+ nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
200
+ if m.bias is not None:
201
+ nn.init.constant_(m.bias, 0)
202
+ elif isinstance(m, nn.GroupNorm):
203
+ nn.init.constant_(m.weight, 1)
204
+ nn.init.constant_(m.bias, 0)
205
+ elif isinstance(m, nn.Linear):
206
+ nn.init.kaiming_normal_(m.weight, nonlinearity="relu")
207
+ if m.bias is not None:
208
+ nn.init.constant_(m.bias, 0)
209
+
210
+ def forward(self, x, mask, mu, t, spks=None, cond=None, streaming=False):
211
+ """Forward pass of the UNet1DConditional model.
212
+
213
+ Args:
214
+ x (torch.Tensor): shape (batch_size, in_channels, time)
215
+ mask (_type_): shape (batch_size, 1, time)
216
+ t (_type_): shape (batch_size)
217
+ spks (_type_, optional): shape: (batch_size, condition_channels). Defaults to None.
218
+ cond (_type_, optional): placeholder for future use. Defaults to None.
219
+
220
+ Raises:
221
+ ValueError: _description_
222
+ ValueError: _description_
223
+
224
+ Returns:
225
+ _type_: _description_
226
+ """
227
+
228
+ t = self.time_embeddings(t).to(t.dtype)
229
+ t = self.time_mlp(t)
230
+
231
+ x = pack([x, mu], "b * t")[0]
232
+
233
+ if spks is not None:
234
+ spks = repeat(spks, "b c -> b c t", t=x.shape[-1])
235
+ x = pack([x, spks], "b * t")[0]
236
+ if cond is not None:
237
+ x = pack([x, cond], "b * t")[0]
238
+
239
+ hiddens = []
240
+ masks = [mask]
241
+ for resnet, transformer_blocks, downsample in self.down_blocks:
242
+ mask_down = masks[-1]
243
+ x = resnet(x, mask_down, t)
244
+ x = rearrange(x, "b c t -> b t c").contiguous()
245
+ attn_mask = add_optional_chunk_mask(x, mask_down.bool(), False, False, 0, 0, -1).repeat(1, x.size(1), 1)
246
+ attn_mask = mask_to_bias(attn_mask, x.dtype)
247
+ for transformer_block in transformer_blocks:
248
+ x = transformer_block(
249
+ hidden_states=x,
250
+ attention_mask=attn_mask,
251
+ timestep=t,
252
+ )
253
+ x = rearrange(x, "b t c -> b c t").contiguous()
254
+ hiddens.append(x) # Save hidden states for skip connections
255
+ x = downsample(x * mask_down)
256
+ masks.append(mask_down[:, :, ::2])
257
+ masks = masks[:-1]
258
+ mask_mid = masks[-1]
259
+
260
+ for resnet, transformer_blocks in self.mid_blocks:
261
+ x = resnet(x, mask_mid, t)
262
+ x = rearrange(x, "b c t -> b t c").contiguous()
263
+ attn_mask = add_optional_chunk_mask(x, mask_mid.bool(), False, False, 0, 0, -1).repeat(1, x.size(1), 1)
264
+ attn_mask = mask_to_bias(attn_mask, x.dtype)
265
+ for transformer_block in transformer_blocks:
266
+ x = transformer_block(
267
+ hidden_states=x,
268
+ attention_mask=attn_mask,
269
+ timestep=t,
270
+ )
271
+ x = rearrange(x, "b t c -> b c t").contiguous()
272
+
273
+ for resnet, transformer_blocks, upsample in self.up_blocks:
274
+ mask_up = masks.pop()
275
+ skip = hiddens.pop()
276
+ x = pack([x[:, :, :skip.shape[-1]], skip], "b * t")[0]
277
+ x = resnet(x, mask_up, t)
278
+ x = rearrange(x, "b c t -> b t c").contiguous()
279
+ attn_mask = add_optional_chunk_mask(x, mask_up.bool(), False, False, 0, 0, -1).repeat(1, x.size(1), 1)
280
+ attn_mask = mask_to_bias(attn_mask, x.dtype)
281
+ for transformer_block in transformer_blocks:
282
+ x = transformer_block(
283
+ hidden_states=x,
284
+ attention_mask=attn_mask,
285
+ timestep=t,
286
+ )
287
+ x = rearrange(x, "b t c -> b c t").contiguous()
288
+ x = upsample(x * mask_up)
289
+ x = self.final_block(x, mask_up)
290
+ output = self.final_proj(x * mask_up)
291
+ return output * mask
292
+
293
+
294
+ class CausalConditionalDecoder(ConditionalDecoder):
295
+ def __init__(
296
+ self,
297
+ in_channels,
298
+ out_channels,
299
+ channels=(256, 256),
300
+ dropout=0.05,
301
+ attention_head_dim=64,
302
+ n_blocks=1,
303
+ num_mid_blocks=2,
304
+ num_heads=4,
305
+ act_fn="snake",
306
+ static_chunk_size=50,
307
+ num_decoding_left_chunks=2,
308
+ ):
309
+ """
310
+ This decoder requires an input with the same shape of the target. So, if your text content
311
+ is shorter or longer than the outputs, please re-sampling it before feeding to the decoder.
312
+ """
313
+ torch.nn.Module.__init__(self)
314
+ channels = tuple(channels)
315
+ self.in_channels = in_channels
316
+ self.out_channels = out_channels
317
+ self.time_embeddings = SinusoidalPosEmb(in_channels)
318
+ time_embed_dim = channels[0] * 4
319
+ self.time_mlp = TimestepEmbedding(
320
+ in_channels=in_channels,
321
+ time_embed_dim=time_embed_dim,
322
+ act_fn="silu",
323
+ )
324
+ self.static_chunk_size = static_chunk_size
325
+ self.num_decoding_left_chunks = num_decoding_left_chunks
326
+ self.down_blocks = nn.ModuleList([])
327
+ self.mid_blocks = nn.ModuleList([])
328
+ self.up_blocks = nn.ModuleList([])
329
+
330
+ output_channel = in_channels
331
+ for i in range(len(channels)): # pylint: disable=consider-using-enumerate
332
+ input_channel = output_channel
333
+ output_channel = channels[i]
334
+ is_last = i == len(channels) - 1
335
+ resnet = CausalResnetBlock1D(dim=input_channel, dim_out=output_channel, time_emb_dim=time_embed_dim)
336
+ transformer_blocks = nn.ModuleList(
337
+ [
338
+ BasicTransformerBlock(
339
+ dim=output_channel,
340
+ num_attention_heads=num_heads,
341
+ attention_head_dim=attention_head_dim,
342
+ dropout=dropout,
343
+ activation_fn=act_fn,
344
+ )
345
+ for _ in range(n_blocks)
346
+ ]
347
+ )
348
+ downsample = (
349
+ Downsample1D(output_channel) if not is_last else CausalConv1d(output_channel, output_channel, 3)
350
+ )
351
+ self.down_blocks.append(nn.ModuleList([resnet, transformer_blocks, downsample]))
352
+
353
+ for _ in range(num_mid_blocks):
354
+ input_channel = channels[-1]
355
+ out_channels = channels[-1]
356
+ resnet = CausalResnetBlock1D(dim=input_channel, dim_out=output_channel, time_emb_dim=time_embed_dim)
357
+
358
+ transformer_blocks = nn.ModuleList(
359
+ [
360
+ BasicTransformerBlock(
361
+ dim=output_channel,
362
+ num_attention_heads=num_heads,
363
+ attention_head_dim=attention_head_dim,
364
+ dropout=dropout,
365
+ activation_fn=act_fn,
366
+ )
367
+ for _ in range(n_blocks)
368
+ ]
369
+ )
370
+
371
+ self.mid_blocks.append(nn.ModuleList([resnet, transformer_blocks]))
372
+
373
+ channels = channels[::-1] + (channels[0],)
374
+ for i in range(len(channels) - 1):
375
+ input_channel = channels[i] * 2
376
+ output_channel = channels[i + 1]
377
+ is_last = i == len(channels) - 2
378
+ resnet = CausalResnetBlock1D(
379
+ dim=input_channel,
380
+ dim_out=output_channel,
381
+ time_emb_dim=time_embed_dim,
382
+ )
383
+ transformer_blocks = nn.ModuleList(
384
+ [
385
+ BasicTransformerBlock(
386
+ dim=output_channel,
387
+ num_attention_heads=num_heads,
388
+ attention_head_dim=attention_head_dim,
389
+ dropout=dropout,
390
+ activation_fn=act_fn,
391
+ )
392
+ for _ in range(n_blocks)
393
+ ]
394
+ )
395
+ upsample = (
396
+ Upsample1D(output_channel, use_conv_transpose=True)
397
+ if not is_last
398
+ else CausalConv1d(output_channel, output_channel, 3)
399
+ )
400
+ self.up_blocks.append(nn.ModuleList([resnet, transformer_blocks, upsample]))
401
+ self.final_block = CausalBlock1D(channels[-1], channels[-1])
402
+ self.final_proj = nn.Conv1d(channels[-1], self.out_channels, 1)
403
+ self.initialize_weights()
404
+
405
+ def forward(self, x, mask, mu, t, spks=None, cond=None, streaming=False):
406
+ """Forward pass of the UNet1DConditional model.
407
+
408
+ Args:
409
+ x (torch.Tensor): shape (batch_size, in_channels, time)
410
+ mask (_type_): shape (batch_size, 1, time)
411
+ t (_type_): shape (batch_size)
412
+ spks (_type_, optional): shape: (batch_size, condition_channels). Defaults to None.
413
+ cond (_type_, optional): placeholder for future use. Defaults to None.
414
+
415
+ Raises:
416
+ ValueError: _description_
417
+ ValueError: _description_
418
+
419
+ Returns:
420
+ _type_: _description_
421
+ """
422
+ t = self.time_embeddings(t).to(t.dtype)
423
+ t = self.time_mlp(t)
424
+
425
+ x = pack([x, mu], "b * t")[0]
426
+
427
+ if spks is not None:
428
+ spks = repeat(spks, "b c -> b c t", t=x.shape[-1])
429
+ x = pack([x, spks], "b * t")[0]
430
+ if cond is not None:
431
+ x = pack([x, cond], "b * t")[0]
432
+
433
+ hiddens = []
434
+ masks = [mask]
435
+ for resnet, transformer_blocks, downsample in self.down_blocks:
436
+ mask_down = masks[-1]
437
+ x = resnet(x, mask_down, t)
438
+ x = rearrange(x, "b c t -> b t c").contiguous()
439
+ if streaming is True:
440
+ attn_mask = add_optional_chunk_mask(x, mask_down.bool(), False, False, 0, self.static_chunk_size, -1)
441
+ else:
442
+ attn_mask = add_optional_chunk_mask(x, mask_down.bool(), False, False, 0, 0, -1).repeat(1, x.size(1), 1)
443
+ attn_mask = mask_to_bias(attn_mask, x.dtype)
444
+ for transformer_block in transformer_blocks:
445
+ x = transformer_block(
446
+ hidden_states=x,
447
+ attention_mask=attn_mask,
448
+ timestep=t,
449
+ )
450
+ x = rearrange(x, "b t c -> b c t").contiguous()
451
+ hiddens.append(x) # Save hidden states for skip connections
452
+ x = downsample(x * mask_down)
453
+ masks.append(mask_down[:, :, ::2])
454
+ masks = masks[:-1]
455
+ mask_mid = masks[-1]
456
+
457
+ for resnet, transformer_blocks in self.mid_blocks:
458
+ x = resnet(x, mask_mid, t)
459
+ x = rearrange(x, "b c t -> b t c").contiguous()
460
+ if streaming is True:
461
+ attn_mask = add_optional_chunk_mask(x, mask_mid.bool(), False, False, 0, self.static_chunk_size, -1)
462
+ else:
463
+ attn_mask = add_optional_chunk_mask(x, mask_mid.bool(), False, False, 0, 0, -1).repeat(1, x.size(1), 1)
464
+ attn_mask = mask_to_bias(attn_mask, x.dtype)
465
+ for transformer_block in transformer_blocks:
466
+ x = transformer_block(
467
+ hidden_states=x,
468
+ attention_mask=attn_mask,
469
+ timestep=t,
470
+ )
471
+ x = rearrange(x, "b t c -> b c t").contiguous()
472
+
473
+ for resnet, transformer_blocks, upsample in self.up_blocks:
474
+ mask_up = masks.pop()
475
+ skip = hiddens.pop()
476
+ x = pack([x[:, :, :skip.shape[-1]], skip], "b * t")[0]
477
+ x = resnet(x, mask_up, t)
478
+ x = rearrange(x, "b c t -> b t c").contiguous()
479
+ if streaming is True:
480
+ attn_mask = add_optional_chunk_mask(x, mask_up.bool(), False, False, 0, self.static_chunk_size, -1)
481
+ else:
482
+ attn_mask = add_optional_chunk_mask(x, mask_up.bool(), False, False, 0, 0, -1).repeat(1, x.size(1), 1)
483
+ attn_mask = mask_to_bias(attn_mask, x.dtype)
484
+ for transformer_block in transformer_blocks:
485
+ x = transformer_block(
486
+ hidden_states=x,
487
+ attention_mask=attn_mask,
488
+ timestep=t,
489
+ )
490
+ x = rearrange(x, "b t c -> b c t").contiguous()
491
+ x = upsample(x * mask_up)
492
+ x = self.final_block(x, mask_up)
493
+ output = self.final_proj(x * mask_up)
494
+ return output * mask
cosyvoice/flow/flow.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Zhihao Du)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import logging
15
+ import random
16
+ from typing import Dict, Optional
17
+ import torch
18
+ import torch.nn as nn
19
+ from torch.nn import functional as F
20
+ from omegaconf import DictConfig
21
+ from cosyvoice.utils.mask import make_pad_mask
22
+
23
+
24
+ class MaskedDiffWithXvec(torch.nn.Module):
25
+ def __init__(self,
26
+ input_size: int = 512,
27
+ output_size: int = 80,
28
+ spk_embed_dim: int = 192,
29
+ output_type: str = "mel",
30
+ vocab_size: int = 4096,
31
+ input_frame_rate: int = 50,
32
+ only_mask_loss: bool = True,
33
+ encoder: torch.nn.Module = None,
34
+ length_regulator: torch.nn.Module = None,
35
+ decoder: torch.nn.Module = None,
36
+ decoder_conf: Dict = {'in_channels': 240, 'out_channel': 80, 'spk_emb_dim': 80, 'n_spks': 1,
37
+ 'cfm_params': DictConfig({'sigma_min': 1e-06, 'solver': 'euler', 't_scheduler': 'cosine',
38
+ 'training_cfg_rate': 0.2, 'inference_cfg_rate': 0.7, 'reg_loss_type': 'l1'}),
39
+ 'decoder_params': {'channels': [256, 256], 'dropout': 0.0, 'attention_head_dim': 64,
40
+ 'n_blocks': 4, 'num_mid_blocks': 12, 'num_heads': 8, 'act_fn': 'gelu'}},
41
+ mel_feat_conf: Dict = {'n_fft': 1024, 'num_mels': 80, 'sampling_rate': 22050,
42
+ 'hop_size': 256, 'win_size': 1024, 'fmin': 0, 'fmax': 8000}):
43
+ super().__init__()
44
+ self.input_size = input_size
45
+ self.output_size = output_size
46
+ self.decoder_conf = decoder_conf
47
+ self.mel_feat_conf = mel_feat_conf
48
+ self.vocab_size = vocab_size
49
+ self.output_type = output_type
50
+ self.input_frame_rate = input_frame_rate
51
+ logging.info(f"input frame rate={self.input_frame_rate}")
52
+ self.input_embedding = nn.Embedding(vocab_size, input_size)
53
+ self.spk_embed_affine_layer = torch.nn.Linear(spk_embed_dim, output_size)
54
+ self.encoder = encoder
55
+ self.encoder_proj = torch.nn.Linear(self.encoder.output_size(), output_size)
56
+ self.decoder = decoder
57
+ self.length_regulator = length_regulator
58
+ self.only_mask_loss = only_mask_loss
59
+
60
+ def forward(
61
+ self,
62
+ batch: dict,
63
+ device: torch.device,
64
+ ) -> Dict[str, Optional[torch.Tensor]]:
65
+ token = batch['speech_token'].to(device)
66
+ token_len = batch['speech_token_len'].to(device)
67
+ feat = batch['speech_feat'].to(device)
68
+ feat_len = batch['speech_feat_len'].to(device)
69
+ embedding = batch['embedding'].to(device)
70
+
71
+ # xvec projection
72
+ embedding = F.normalize(embedding, dim=1)
73
+ embedding = self.spk_embed_affine_layer(embedding)
74
+
75
+ # concat text and prompt_text
76
+ mask = (~make_pad_mask(token_len)).float().unsqueeze(-1).to(device)
77
+ token = self.input_embedding(torch.clamp(token, min=0)) * mask
78
+
79
+ # text encode
80
+ h, h_lengths = self.encoder(token, token_len)
81
+ h = self.encoder_proj(h)
82
+ h, h_lengths = self.length_regulator(h, feat_len)
83
+
84
+ # get conditions
85
+ conds = torch.zeros(feat.shape, device=token.device)
86
+ for i, j in enumerate(feat_len):
87
+ if random.random() < 0.5:
88
+ continue
89
+ index = random.randint(0, int(0.3 * j))
90
+ conds[i, :index] = feat[i, :index]
91
+ conds = conds.transpose(1, 2)
92
+
93
+ mask = (~make_pad_mask(feat_len)).to(h)
94
+ # NOTE this is unnecessary, feat/h already same shape
95
+ loss, _ = self.decoder.compute_loss(
96
+ feat.transpose(1, 2).contiguous(),
97
+ mask.unsqueeze(1),
98
+ h.transpose(1, 2).contiguous(),
99
+ embedding,
100
+ cond=conds
101
+ )
102
+ return {'loss': loss}
103
+
104
+ @torch.inference_mode()
105
+ def inference(self,
106
+ token,
107
+ token_len,
108
+ prompt_token,
109
+ prompt_token_len,
110
+ prompt_feat,
111
+ prompt_feat_len,
112
+ embedding,
113
+ flow_cache):
114
+ assert token.shape[0] == 1
115
+ # xvec projection
116
+ embedding = F.normalize(embedding, dim=1)
117
+ embedding = self.spk_embed_affine_layer(embedding)
118
+
119
+ # concat speech token and prompt speech token
120
+ token_len1, token_len2 = prompt_token.shape[1], token.shape[1]
121
+ token, token_len = torch.concat([prompt_token, token], dim=1), prompt_token_len + token_len
122
+ mask = (~make_pad_mask(token_len)).unsqueeze(-1).to(embedding)
123
+ token = self.input_embedding(torch.clamp(token, min=0)) * mask
124
+
125
+ # text encode
126
+ h, h_lengths = self.encoder(token, token_len)
127
+ h = self.encoder_proj(h)
128
+ mel_len1, mel_len2 = prompt_feat.shape[1], int(token_len2 / self.input_frame_rate * 22050 / 256)
129
+ h, h_lengths = self.length_regulator.inference(h[:, :token_len1], h[:, token_len1:], mel_len1, mel_len2, self.input_frame_rate)
130
+
131
+ # get conditions
132
+ conds = torch.zeros([1, mel_len1 + mel_len2, self.output_size], device=token.device).to(h.dtype)
133
+ conds[:, :mel_len1] = prompt_feat
134
+ conds = conds.transpose(1, 2)
135
+
136
+ mask = (~make_pad_mask(torch.tensor([mel_len1 + mel_len2]))).to(h)
137
+ feat, flow_cache = self.decoder(
138
+ mu=h.transpose(1, 2).contiguous(),
139
+ mask=mask.unsqueeze(1),
140
+ spks=embedding,
141
+ cond=conds,
142
+ n_timesteps=10,
143
+ prompt_len=mel_len1,
144
+ cache=flow_cache
145
+ )
146
+ feat = feat[:, :, mel_len1:]
147
+ assert feat.shape[2] == mel_len2
148
+ return feat.float(), flow_cache
149
+
150
+
151
+ class CausalMaskedDiffWithXvec(torch.nn.Module):
152
+ def __init__(self,
153
+ input_size: int = 512,
154
+ output_size: int = 80,
155
+ spk_embed_dim: int = 192,
156
+ output_type: str = "mel",
157
+ vocab_size: int = 4096,
158
+ input_frame_rate: int = 50,
159
+ only_mask_loss: bool = True,
160
+ token_mel_ratio: int = 2,
161
+ pre_lookahead_len: int = 3,
162
+ encoder: torch.nn.Module = None,
163
+ decoder: torch.nn.Module = None,
164
+ decoder_conf: Dict = {'in_channels': 240, 'out_channel': 80, 'spk_emb_dim': 80, 'n_spks': 1,
165
+ 'cfm_params': DictConfig({'sigma_min': 1e-06, 'solver': 'euler', 't_scheduler': 'cosine',
166
+ 'training_cfg_rate': 0.2, 'inference_cfg_rate': 0.7, 'reg_loss_type': 'l1'}),
167
+ 'decoder_params': {'channels': [256, 256], 'dropout': 0.0, 'attention_head_dim': 64,
168
+ 'n_blocks': 4, 'num_mid_blocks': 12, 'num_heads': 8, 'act_fn': 'gelu'}},
169
+ mel_feat_conf: Dict = {'n_fft': 1024, 'num_mels': 80, 'sampling_rate': 22050,
170
+ 'hop_size': 256, 'win_size': 1024, 'fmin': 0, 'fmax': 8000}):
171
+ super().__init__()
172
+ self.input_size = input_size
173
+ self.output_size = output_size
174
+ self.decoder_conf = decoder_conf
175
+ self.mel_feat_conf = mel_feat_conf
176
+ self.vocab_size = vocab_size
177
+ self.output_type = output_type
178
+ self.input_frame_rate = input_frame_rate
179
+ logging.info(f"input frame rate={self.input_frame_rate}")
180
+ self.input_embedding = nn.Embedding(vocab_size, input_size)
181
+ self.spk_embed_affine_layer = torch.nn.Linear(spk_embed_dim, output_size)
182
+ self.encoder = encoder
183
+ self.encoder_proj = torch.nn.Linear(self.encoder.output_size(), output_size)
184
+ self.decoder = decoder
185
+ self.only_mask_loss = only_mask_loss
186
+ self.token_mel_ratio = token_mel_ratio
187
+ self.pre_lookahead_len = pre_lookahead_len
188
+
189
+ def forward(
190
+ self,
191
+ batch: dict,
192
+ device: torch.device,
193
+ ) -> Dict[str, Optional[torch.Tensor]]:
194
+ token = batch['speech_token'].to(device)
195
+ token_len = batch['speech_token_len'].to(device)
196
+ feat = batch['speech_feat'].to(device)
197
+ feat_len = batch['speech_feat_len'].to(device)
198
+ embedding = batch['embedding'].to(device)
199
+
200
+ # NOTE unified training, static_chunk_size > 0 or = 0
201
+ streaming = True if random.random() < 0.5 else False
202
+
203
+ # xvec projection
204
+ embedding = F.normalize(embedding, dim=1)
205
+ embedding = self.spk_embed_affine_layer(embedding)
206
+
207
+ # concat text and prompt_text
208
+ mask = (~make_pad_mask(token_len)).float().unsqueeze(-1).to(device)
209
+ token = self.input_embedding(torch.clamp(token, min=0)) * mask
210
+
211
+ # text encode
212
+ h, h_lengths = self.encoder(token, token_len, streaming=streaming)
213
+ h = self.encoder_proj(h)
214
+
215
+ # get conditions
216
+ conds = torch.zeros(feat.shape, device=token.device)
217
+ for i, j in enumerate(feat_len):
218
+ if random.random() < 0.5:
219
+ continue
220
+ index = random.randint(0, int(0.3 * j))
221
+ conds[i, :index] = feat[i, :index]
222
+ conds = conds.transpose(1, 2)
223
+
224
+ mask = (~make_pad_mask(h_lengths.sum(dim=-1).squeeze(dim=1))).to(h)
225
+ loss, _ = self.decoder.compute_loss(
226
+ feat.transpose(1, 2).contiguous(),
227
+ mask.unsqueeze(1),
228
+ h.transpose(1, 2).contiguous(),
229
+ embedding,
230
+ cond=conds,
231
+ streaming=streaming,
232
+ )
233
+ return {'loss': loss}
234
+
235
+ @torch.inference_mode()
236
+ def inference(self,
237
+ token,
238
+ token_len,
239
+ prompt_token,
240
+ prompt_token_len,
241
+ prompt_feat,
242
+ prompt_feat_len,
243
+ embedding,
244
+ streaming,
245
+ finalize):
246
+ assert token.shape[0] == 1
247
+ # xvec projection
248
+ embedding = F.normalize(embedding, dim=1)
249
+ embedding = self.spk_embed_affine_layer(embedding)
250
+
251
+ # concat text and prompt_text
252
+ token, token_len = torch.concat([prompt_token, token], dim=1), prompt_token_len + token_len
253
+ mask = (~make_pad_mask(token_len)).unsqueeze(-1).to(embedding)
254
+ token = self.input_embedding(torch.clamp(token, min=0)) * mask
255
+
256
+ # text encode
257
+ if finalize is True:
258
+ h, h_lengths = self.encoder(token, token_len, streaming=streaming)
259
+ else:
260
+ token, context = token[:, :-self.pre_lookahead_len], token[:, -self.pre_lookahead_len:]
261
+ h, h_lengths = self.encoder(token, token_len, context=context, streaming=streaming)
262
+ mel_len1, mel_len2 = prompt_feat.shape[1], h.shape[1] - prompt_feat.shape[1]
263
+ h = self.encoder_proj(h)
264
+
265
+ # get conditions
266
+ conds = torch.zeros([1, mel_len1 + mel_len2, self.output_size], device=token.device).to(h.dtype)
267
+ conds[:, :mel_len1] = prompt_feat
268
+ conds = conds.transpose(1, 2)
269
+
270
+ mask = (~make_pad_mask(torch.tensor([mel_len1 + mel_len2]))).to(h)
271
+ feat, _ = self.decoder(
272
+ mu=h.transpose(1, 2).contiguous(),
273
+ mask=mask.unsqueeze(1),
274
+ spks=embedding,
275
+ cond=conds,
276
+ n_timesteps=10,
277
+ streaming=streaming
278
+ )
279
+ feat = feat[:, :, mel_len1:]
280
+ assert feat.shape[2] == mel_len2
281
+ return feat.float(), None
cosyvoice/flow/flow_matching.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Zhihao Du)
2
+ # 2025 Alibaba Inc (authors: Xiang Lyu, Bofan Zhou)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import torch
16
+ import torch.nn.functional as F
17
+ from matcha.models.components.flow_matching import BASECFM
18
+ from cosyvoice.utils.common import set_all_random_seed
19
+
20
+
21
+ class ConditionalCFM(BASECFM):
22
+ def __init__(self, in_channels, cfm_params, n_spks=1, spk_emb_dim=64, estimator: torch.nn.Module = None):
23
+ super().__init__(
24
+ n_feats=in_channels,
25
+ cfm_params=cfm_params,
26
+ n_spks=n_spks,
27
+ spk_emb_dim=spk_emb_dim,
28
+ )
29
+ self.t_scheduler = cfm_params.t_scheduler
30
+ self.training_cfg_rate = cfm_params.training_cfg_rate
31
+ self.inference_cfg_rate = cfm_params.inference_cfg_rate
32
+ in_channels = in_channels + (spk_emb_dim if n_spks > 0 else 0)
33
+ # Just change the architecture of the estimator here
34
+ self.estimator = estimator
35
+
36
+ @torch.inference_mode()
37
+ def forward(self, mu, mask, n_timesteps, temperature=1.0, spks=None, cond=None, prompt_len=0, cache=torch.zeros(1, 80, 0, 2)):
38
+ """Forward diffusion
39
+
40
+ Args:
41
+ mu (torch.Tensor): output of encoder
42
+ shape: (batch_size, n_feats, mel_timesteps)
43
+ mask (torch.Tensor): output_mask
44
+ shape: (batch_size, 1, mel_timesteps)
45
+ n_timesteps (int): number of diffusion steps
46
+ temperature (float, optional): temperature for scaling noise. Defaults to 1.0.
47
+ spks (torch.Tensor, optional): speaker ids. Defaults to None.
48
+ shape: (batch_size, spk_emb_dim)
49
+ cond: Not used but kept for future purposes
50
+
51
+ Returns:
52
+ sample: generated mel-spectrogram
53
+ shape: (batch_size, n_feats, mel_timesteps)
54
+ """
55
+
56
+ z = torch.randn_like(mu).to(mu.device).to(mu.dtype) * temperature
57
+ cache_size = cache.shape[2]
58
+ # fix prompt and overlap part mu and z
59
+ if cache_size != 0:
60
+ z[:, :, :cache_size] = cache[:, :, :, 0]
61
+ mu[:, :, :cache_size] = cache[:, :, :, 1]
62
+ z_cache = torch.concat([z[:, :, :prompt_len], z[:, :, -34:]], dim=2)
63
+ mu_cache = torch.concat([mu[:, :, :prompt_len], mu[:, :, -34:]], dim=2)
64
+ cache = torch.stack([z_cache, mu_cache], dim=-1)
65
+
66
+ t_span = torch.linspace(0, 1, n_timesteps + 1, device=mu.device, dtype=mu.dtype)
67
+ if self.t_scheduler == 'cosine':
68
+ t_span = 1 - torch.cos(t_span * 0.5 * torch.pi)
69
+ return self.solve_euler(z, t_span=t_span, mu=mu, mask=mask, spks=spks, cond=cond), cache
70
+
71
+ def solve_euler(self, x, t_span, mu, mask, spks, cond, streaming=False):
72
+ """
73
+ Fixed euler solver for ODEs.
74
+ Args:
75
+ x (torch.Tensor): random noise
76
+ t_span (torch.Tensor): n_timesteps interpolated
77
+ shape: (n_timesteps + 1,)
78
+ mu (torch.Tensor): output of encoder
79
+ shape: (batch_size, n_feats, mel_timesteps)
80
+ mask (torch.Tensor): output_mask
81
+ shape: (batch_size, 1, mel_timesteps)
82
+ spks (torch.Tensor, optional): speaker ids. Defaults to None.
83
+ shape: (batch_size, spk_emb_dim)
84
+ cond: Not used but kept for future purposes
85
+ """
86
+ t, _, dt = t_span[0], t_span[-1], t_span[1] - t_span[0]
87
+ t = t.unsqueeze(dim=0)
88
+
89
+ # I am storing this because I can later plot it by putting a debugger here and saving it to a file
90
+ # Or in future might add like a return_all_steps flag
91
+ sol = []
92
+
93
+ # Do not use concat, it may cause memory format changed and trt infer with wrong results!
94
+ x_in = torch.zeros([2, 80, x.size(2)], device=x.device, dtype=x.dtype)
95
+ mask_in = torch.zeros([2, 1, x.size(2)], device=x.device, dtype=x.dtype)
96
+ mu_in = torch.zeros([2, 80, x.size(2)], device=x.device, dtype=x.dtype)
97
+ t_in = torch.zeros([2], device=x.device, dtype=x.dtype)
98
+ spks_in = torch.zeros([2, 80], device=x.device, dtype=x.dtype)
99
+ cond_in = torch.zeros([2, 80, x.size(2)], device=x.device, dtype=x.dtype)
100
+ for step in range(1, len(t_span)):
101
+ # Classifier-Free Guidance inference introduced in VoiceBox
102
+ x_in[:] = x
103
+ mask_in[:] = mask
104
+ mu_in[0] = mu
105
+ t_in[:] = t.unsqueeze(0)
106
+ spks_in[0] = spks
107
+ cond_in[0] = cond
108
+ dphi_dt = self.forward_estimator(
109
+ x_in, mask_in,
110
+ mu_in, t_in,
111
+ spks_in,
112
+ cond_in,
113
+ streaming
114
+ )
115
+ dphi_dt, cfg_dphi_dt = torch.split(dphi_dt, [x.size(0), x.size(0)], dim=0)
116
+ dphi_dt = ((1.0 + self.inference_cfg_rate) * dphi_dt - self.inference_cfg_rate * cfg_dphi_dt)
117
+ x = x + dt * dphi_dt
118
+ t = t + dt
119
+ sol.append(x)
120
+ if step < len(t_span) - 1:
121
+ dt = t_span[step + 1] - t
122
+
123
+ return sol[-1].float()
124
+
125
+ def forward_estimator(self, x, mask, mu, t, spks, cond, streaming=False):
126
+ if isinstance(self.estimator, torch.nn.Module):
127
+ return self.estimator(x, mask, mu, t, spks, cond, streaming=streaming)
128
+ else:
129
+ [estimator, stream], trt_engine = self.estimator.acquire_estimator()
130
+ # NOTE need to synchronize when switching stream
131
+ torch.cuda.current_stream().synchronize()
132
+ with stream:
133
+ estimator.set_input_shape('x', (2, 80, x.size(2)))
134
+ estimator.set_input_shape('mask', (2, 1, x.size(2)))
135
+ estimator.set_input_shape('mu', (2, 80, x.size(2)))
136
+ estimator.set_input_shape('t', (2,))
137
+ estimator.set_input_shape('spks', (2, 80))
138
+ estimator.set_input_shape('cond', (2, 80, x.size(2)))
139
+ data_ptrs = [x.contiguous().data_ptr(),
140
+ mask.contiguous().data_ptr(),
141
+ mu.contiguous().data_ptr(),
142
+ t.contiguous().data_ptr(),
143
+ spks.contiguous().data_ptr(),
144
+ cond.contiguous().data_ptr(),
145
+ x.data_ptr()]
146
+ for i, j in enumerate(data_ptrs):
147
+ estimator.set_tensor_address(trt_engine.get_tensor_name(i), j)
148
+ # run trt engine
149
+ assert estimator.execute_async_v3(torch.cuda.current_stream().cuda_stream) is True
150
+ torch.cuda.current_stream().synchronize()
151
+ self.estimator.release_estimator(estimator, stream)
152
+ return x
153
+
154
+ def compute_loss(self, x1, mask, mu, spks=None, cond=None, streaming=False):
155
+ """Computes diffusion loss
156
+
157
+ Args:
158
+ x1 (torch.Tensor): Target
159
+ shape: (batch_size, n_feats, mel_timesteps)
160
+ mask (torch.Tensor): target mask
161
+ shape: (batch_size, 1, mel_timesteps)
162
+ mu (torch.Tensor): output of encoder
163
+ shape: (batch_size, n_feats, mel_timesteps)
164
+ spks (torch.Tensor, optional): speaker embedding. Defaults to None.
165
+ shape: (batch_size, spk_emb_dim)
166
+
167
+ Returns:
168
+ loss: conditional flow matching loss
169
+ y: conditional flow
170
+ shape: (batch_size, n_feats, mel_timesteps)
171
+ """
172
+ b, _, t = mu.shape
173
+
174
+ # random timestep
175
+ t = torch.rand([b, 1, 1], device=mu.device, dtype=mu.dtype)
176
+ if self.t_scheduler == 'cosine':
177
+ t = 1 - torch.cos(t * 0.5 * torch.pi)
178
+ # sample noise p(x_0)
179
+ z = torch.randn_like(x1)
180
+
181
+ y = (1 - (1 - self.sigma_min) * t) * z + t * x1
182
+ u = x1 - (1 - self.sigma_min) * z
183
+
184
+ # during training, we randomly drop condition to trade off mode coverage and sample fidelity
185
+ if self.training_cfg_rate > 0:
186
+ cfg_mask = torch.rand(b, device=x1.device) > self.training_cfg_rate
187
+ mu = mu * cfg_mask.view(-1, 1, 1)
188
+ spks = spks * cfg_mask.view(-1, 1)
189
+ cond = cond * cfg_mask.view(-1, 1, 1)
190
+
191
+ pred = self.estimator(y, mask, mu, t.squeeze(), spks, cond, streaming=streaming)
192
+ loss = F.mse_loss(pred * mask, u * mask, reduction="sum") / (torch.sum(mask) * u.shape[1])
193
+ return loss, y
194
+
195
+
196
+ class CausalConditionalCFM(ConditionalCFM):
197
+ def __init__(self, in_channels, cfm_params, n_spks=1, spk_emb_dim=64, estimator: torch.nn.Module = None):
198
+ super().__init__(in_channels, cfm_params, n_spks, spk_emb_dim, estimator)
199
+ set_all_random_seed(0)
200
+ self.rand_noise = torch.randn([1, 80, 50 * 300])
201
+
202
+ @torch.inference_mode()
203
+ def forward(self, mu, mask, n_timesteps, temperature=1.0, spks=None, cond=None, streaming=False):
204
+ """Forward diffusion
205
+
206
+ Args:
207
+ mu (torch.Tensor): output of encoder
208
+ shape: (batch_size, n_feats, mel_timesteps)
209
+ mask (torch.Tensor): output_mask
210
+ shape: (batch_size, 1, mel_timesteps)
211
+ n_timesteps (int): number of diffusion steps
212
+ temperature (float, optional): temperature for scaling noise. Defaults to 1.0.
213
+ spks (torch.Tensor, optional): speaker ids. Defaults to None.
214
+ shape: (batch_size, spk_emb_dim)
215
+ cond: Not used but kept for future purposes
216
+
217
+ Returns:
218
+ sample: generated mel-spectrogram
219
+ shape: (batch_size, n_feats, mel_timesteps)
220
+ """
221
+
222
+ z = self.rand_noise[:, :, :mu.size(2)].to(mu.device).to(mu.dtype) * temperature
223
+ # fix prompt and overlap part mu and z
224
+ t_span = torch.linspace(0, 1, n_timesteps + 1, device=mu.device, dtype=mu.dtype)
225
+ if self.t_scheduler == 'cosine':
226
+ t_span = 1 - torch.cos(t_span * 0.5 * torch.pi)
227
+ return self.solve_euler(z, t_span=t_span, mu=mu, mask=mask, spks=spks, cond=cond, streaming=streaming), None
cosyvoice/flow/length_regulator.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Zhihao Du)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from typing import Tuple
15
+ import torch.nn as nn
16
+ import torch
17
+ from torch.nn import functional as F
18
+ from cosyvoice.utils.mask import make_pad_mask
19
+
20
+
21
+ class InterpolateRegulator(nn.Module):
22
+ def __init__(
23
+ self,
24
+ channels: int,
25
+ sampling_ratios: Tuple,
26
+ out_channels: int = None,
27
+ groups: int = 1,
28
+ ):
29
+ super().__init__()
30
+ self.sampling_ratios = sampling_ratios
31
+ out_channels = out_channels or channels
32
+ model = nn.ModuleList([])
33
+ if len(sampling_ratios) > 0:
34
+ for _ in sampling_ratios:
35
+ module = nn.Conv1d(channels, channels, 3, 1, 1)
36
+ norm = nn.GroupNorm(groups, channels)
37
+ act = nn.Mish()
38
+ model.extend([module, norm, act])
39
+ model.append(
40
+ nn.Conv1d(channels, out_channels, 1, 1)
41
+ )
42
+ self.model = nn.Sequential(*model)
43
+
44
+ def forward(self, x, ylens=None):
45
+ # x in (B, T, D)
46
+ mask = (~make_pad_mask(ylens)).to(x).unsqueeze(-1)
47
+ x = F.interpolate(x.transpose(1, 2).contiguous(), size=ylens.max(), mode='linear')
48
+ out = self.model(x).transpose(1, 2).contiguous()
49
+ olens = ylens
50
+ return out * mask, olens
51
+
52
+ def inference(self, x1, x2, mel_len1, mel_len2, input_frame_rate=50):
53
+ # in inference mode, interploate prompt token and token(head/mid/tail) seprately, so we can get a clear separation point of mel
54
+ # NOTE 20 corresponds to token_overlap_len in cosyvoice/cli/model.py
55
+ # x in (B, T, D)
56
+ if x2.shape[1] > 40:
57
+ x2_head = F.interpolate(x2[:, :20].transpose(1, 2).contiguous(), size=int(20 / input_frame_rate * 22050 / 256), mode='linear')
58
+ x2_mid = F.interpolate(x2[:, 20:-20].transpose(1, 2).contiguous(), size=mel_len2 - int(20 / input_frame_rate * 22050 / 256) * 2,
59
+ mode='linear')
60
+ x2_tail = F.interpolate(x2[:, -20:].transpose(1, 2).contiguous(), size=int(20 / input_frame_rate * 22050 / 256), mode='linear')
61
+ x2 = torch.concat([x2_head, x2_mid, x2_tail], dim=2)
62
+ else:
63
+ x2 = F.interpolate(x2.transpose(1, 2).contiguous(), size=mel_len2, mode='linear')
64
+ if x1.shape[1] != 0:
65
+ x1 = F.interpolate(x1.transpose(1, 2).contiguous(), size=mel_len1, mode='linear')
66
+ x = torch.concat([x1, x2], dim=2)
67
+ else:
68
+ x = x2
69
+ out = self.model(x).transpose(1, 2).contiguous()
70
+ return out, mel_len1 + mel_len2
cosyvoice/hifigan/__pycache__/discriminator.cpython-310.pyc ADDED
Binary file (8.75 kB). View file
 
cosyvoice/hifigan/__pycache__/f0_predictor.cpython-310.pyc ADDED
Binary file (1.45 kB). View file
 
cosyvoice/hifigan/__pycache__/generator.cpython-310.pyc ADDED
Binary file (14.9 kB). View file
 
cosyvoice/hifigan/__pycache__/hifigan.cpython-310.pyc ADDED
Binary file (2.59 kB). View file
 
cosyvoice/hifigan/discriminator.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ try:
5
+ from torch.nn.utils.parametrizations import weight_norm, spectral_norm
6
+ except ImportError:
7
+ from torch.nn.utils import weight_norm, spectral_norm
8
+ from typing import List, Optional, Tuple
9
+ from einops import rearrange
10
+ from torchaudio.transforms import Spectrogram
11
+
12
+ LRELU_SLOPE = 0.1
13
+
14
+
15
+ class MultipleDiscriminator(nn.Module):
16
+ def __init__(
17
+ self, mpd: nn.Module, mrd: nn.Module
18
+ ):
19
+ super().__init__()
20
+ self.mpd = mpd
21
+ self.mrd = mrd
22
+
23
+ def forward(self, y: torch.Tensor, y_hat: torch.Tensor):
24
+ y_d_rs, y_d_gs, fmap_rs, fmap_gs = [], [], [], []
25
+ this_y_d_rs, this_y_d_gs, this_fmap_rs, this_fmap_gs = self.mpd(y.unsqueeze(dim=1), y_hat.unsqueeze(dim=1))
26
+ y_d_rs += this_y_d_rs
27
+ y_d_gs += this_y_d_gs
28
+ fmap_rs += this_fmap_rs
29
+ fmap_gs += this_fmap_gs
30
+ this_y_d_rs, this_y_d_gs, this_fmap_rs, this_fmap_gs = self.mrd(y, y_hat)
31
+ y_d_rs += this_y_d_rs
32
+ y_d_gs += this_y_d_gs
33
+ fmap_rs += this_fmap_rs
34
+ fmap_gs += this_fmap_gs
35
+ return y_d_rs, y_d_gs, fmap_rs, fmap_gs
36
+
37
+
38
+ class MultiResolutionDiscriminator(nn.Module):
39
+ def __init__(
40
+ self,
41
+ fft_sizes: Tuple[int, ...] = (2048, 1024, 512),
42
+ num_embeddings: Optional[int] = None,
43
+ ):
44
+ """
45
+ Multi-Resolution Discriminator module adapted from https://github.com/descriptinc/descript-audio-codec.
46
+ Additionally, it allows incorporating conditional information with a learned embeddings table.
47
+
48
+ Args:
49
+ fft_sizes (tuple[int]): Tuple of window lengths for FFT. Defaults to (2048, 1024, 512).
50
+ num_embeddings (int, optional): Number of embeddings. None means non-conditional discriminator.
51
+ Defaults to None.
52
+ """
53
+
54
+ super().__init__()
55
+ self.discriminators = nn.ModuleList(
56
+ [DiscriminatorR(window_length=w, num_embeddings=num_embeddings) for w in fft_sizes]
57
+ )
58
+
59
+ def forward(
60
+ self, y: torch.Tensor, y_hat: torch.Tensor, bandwidth_id: torch.Tensor = None
61
+ ) -> Tuple[List[torch.Tensor], List[torch.Tensor], List[List[torch.Tensor]], List[List[torch.Tensor]]]:
62
+ y_d_rs = []
63
+ y_d_gs = []
64
+ fmap_rs = []
65
+ fmap_gs = []
66
+
67
+ for d in self.discriminators:
68
+ y_d_r, fmap_r = d(x=y, cond_embedding_id=bandwidth_id)
69
+ y_d_g, fmap_g = d(x=y_hat, cond_embedding_id=bandwidth_id)
70
+ y_d_rs.append(y_d_r)
71
+ fmap_rs.append(fmap_r)
72
+ y_d_gs.append(y_d_g)
73
+ fmap_gs.append(fmap_g)
74
+
75
+ return y_d_rs, y_d_gs, fmap_rs, fmap_gs
76
+
77
+
78
+ class DiscriminatorR(nn.Module):
79
+ def __init__(
80
+ self,
81
+ window_length: int,
82
+ num_embeddings: Optional[int] = None,
83
+ channels: int = 32,
84
+ hop_factor: float = 0.25,
85
+ bands: Tuple[Tuple[float, float], ...] = ((0.0, 0.1), (0.1, 0.25), (0.25, 0.5), (0.5, 0.75), (0.75, 1.0)),
86
+ ):
87
+ super().__init__()
88
+ self.window_length = window_length
89
+ self.hop_factor = hop_factor
90
+ self.spec_fn = Spectrogram(
91
+ n_fft=window_length, hop_length=int(window_length * hop_factor), win_length=window_length, power=None
92
+ )
93
+ n_fft = window_length // 2 + 1
94
+ bands = [(int(b[0] * n_fft), int(b[1] * n_fft)) for b in bands]
95
+ self.bands = bands
96
+ convs = lambda: nn.ModuleList(
97
+ [
98
+ weight_norm(nn.Conv2d(2, channels, (3, 9), (1, 1), padding=(1, 4))),
99
+ weight_norm(nn.Conv2d(channels, channels, (3, 9), (1, 2), padding=(1, 4))),
100
+ weight_norm(nn.Conv2d(channels, channels, (3, 9), (1, 2), padding=(1, 4))),
101
+ weight_norm(nn.Conv2d(channels, channels, (3, 9), (1, 2), padding=(1, 4))),
102
+ weight_norm(nn.Conv2d(channels, channels, (3, 3), (1, 1), padding=(1, 1))),
103
+ ]
104
+ )
105
+ self.band_convs = nn.ModuleList([convs() for _ in range(len(self.bands))])
106
+
107
+ if num_embeddings is not None:
108
+ self.emb = torch.nn.Embedding(num_embeddings=num_embeddings, embedding_dim=channels)
109
+ torch.nn.init.zeros_(self.emb.weight)
110
+
111
+ self.conv_post = weight_norm(nn.Conv2d(channels, 1, (3, 3), (1, 1), padding=(1, 1)))
112
+
113
+ def spectrogram(self, x):
114
+ # Remove DC offset
115
+ x = x - x.mean(dim=-1, keepdims=True)
116
+ # Peak normalize the volume of input audio
117
+ x = 0.8 * x / (x.abs().max(dim=-1, keepdim=True)[0] + 1e-9)
118
+ x = self.spec_fn(x)
119
+ x = torch.view_as_real(x)
120
+ x = rearrange(x, "b f t c -> b c t f")
121
+ # Split into bands
122
+ x_bands = [x[..., b[0]: b[1]] for b in self.bands]
123
+ return x_bands
124
+
125
+ def forward(self, x: torch.Tensor, cond_embedding_id: torch.Tensor = None):
126
+ x_bands = self.spectrogram(x)
127
+ fmap = []
128
+ x = []
129
+ for band, stack in zip(x_bands, self.band_convs):
130
+ for i, layer in enumerate(stack):
131
+ band = layer(band)
132
+ band = torch.nn.functional.leaky_relu(band, 0.1)
133
+ if i > 0:
134
+ fmap.append(band)
135
+ x.append(band)
136
+ x = torch.cat(x, dim=-1)
137
+ if cond_embedding_id is not None:
138
+ emb = self.emb(cond_embedding_id)
139
+ h = (emb.view(1, -1, 1, 1) * x).sum(dim=1, keepdims=True)
140
+ else:
141
+ h = 0
142
+ x = self.conv_post(x)
143
+ fmap.append(x)
144
+ x += h
145
+
146
+ return x, fmap
147
+
148
+
149
+ class MultiResSpecDiscriminator(torch.nn.Module):
150
+
151
+ def __init__(self,
152
+ fft_sizes=[1024, 2048, 512],
153
+ hop_sizes=[120, 240, 50],
154
+ win_lengths=[600, 1200, 240],
155
+ window="hann_window"):
156
+
157
+ super(MultiResSpecDiscriminator, self).__init__()
158
+ self.discriminators = nn.ModuleList([
159
+ SpecDiscriminator(fft_sizes[0], hop_sizes[0], win_lengths[0], window),
160
+ SpecDiscriminator(fft_sizes[1], hop_sizes[1], win_lengths[1], window),
161
+ SpecDiscriminator(fft_sizes[2], hop_sizes[2], win_lengths[2], window)])
162
+
163
+ def forward(self, y, y_hat):
164
+ y_d_rs = []
165
+ y_d_gs = []
166
+ fmap_rs = []
167
+ fmap_gs = []
168
+ for _, d in enumerate(self.discriminators):
169
+ y_d_r, fmap_r = d(y)
170
+ y_d_g, fmap_g = d(y_hat)
171
+ y_d_rs.append(y_d_r)
172
+ fmap_rs.append(fmap_r)
173
+ y_d_gs.append(y_d_g)
174
+ fmap_gs.append(fmap_g)
175
+
176
+ return y_d_rs, y_d_gs, fmap_rs, fmap_gs
177
+
178
+
179
+ def stft(x, fft_size, hop_size, win_length, window):
180
+ """Perform STFT and convert to magnitude spectrogram.
181
+ Args:
182
+ x (Tensor): Input signal tensor (B, T).
183
+ fft_size (int): FFT size.
184
+ hop_size (int): Hop size.
185
+ win_length (int): Window length.
186
+ window (str): Window function type.
187
+ Returns:
188
+ Tensor: Magnitude spectrogram (B, #frames, fft_size // 2 + 1).
189
+ """
190
+ x_stft = torch.stft(x, fft_size, hop_size, win_length, window, return_complex=True)
191
+
192
+ # NOTE(kan-bayashi): clamp is needed to avoid nan or inf
193
+ return torch.abs(x_stft).transpose(2, 1)
194
+
195
+
196
+ class SpecDiscriminator(nn.Module):
197
+ """docstring for Discriminator."""
198
+
199
+ def __init__(self, fft_size=1024, shift_size=120, win_length=600, window="hann_window", use_spectral_norm=False):
200
+ super(SpecDiscriminator, self).__init__()
201
+ norm_f = weight_norm if use_spectral_norm is False else spectral_norm
202
+ self.fft_size = fft_size
203
+ self.shift_size = shift_size
204
+ self.win_length = win_length
205
+ self.window = getattr(torch, window)(win_length)
206
+ self.discriminators = nn.ModuleList([
207
+ norm_f(nn.Conv2d(1, 32, kernel_size=(3, 9), padding=(1, 4))),
208
+ norm_f(nn.Conv2d(32, 32, kernel_size=(3, 9), stride=(1, 2), padding=(1, 4))),
209
+ norm_f(nn.Conv2d(32, 32, kernel_size=(3, 9), stride=(1, 2), padding=(1, 4))),
210
+ norm_f(nn.Conv2d(32, 32, kernel_size=(3, 9), stride=(1, 2), padding=(1, 4))),
211
+ norm_f(nn.Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))),
212
+ ])
213
+
214
+ self.out = norm_f(nn.Conv2d(32, 1, 3, 1, 1))
215
+
216
+ def forward(self, y):
217
+
218
+ fmap = []
219
+ y = y.squeeze(1)
220
+ y = stft(y, self.fft_size, self.shift_size, self.win_length, self.window.to(y.device))
221
+ y = y.unsqueeze(1)
222
+ for _, d in enumerate(self.discriminators):
223
+ y = d(y)
224
+ y = F.leaky_relu(y, LRELU_SLOPE)
225
+ fmap.append(y)
226
+
227
+ y = self.out(y)
228
+ fmap.append(y)
229
+
230
+ return torch.flatten(y, 1, -1), fmap
cosyvoice/hifigan/f0_predictor.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Kai Hu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import torch
15
+ import torch.nn as nn
16
+ try:
17
+ from torch.nn.utils.parametrizations import weight_norm
18
+ except ImportError:
19
+ from torch.nn.utils import weight_norm
20
+
21
+
22
+ class ConvRNNF0Predictor(nn.Module):
23
+ def __init__(self,
24
+ num_class: int = 1,
25
+ in_channels: int = 80,
26
+ cond_channels: int = 512
27
+ ):
28
+ super().__init__()
29
+
30
+ self.num_class = num_class
31
+ self.condnet = nn.Sequential(
32
+ weight_norm(
33
+ nn.Conv1d(in_channels, cond_channels, kernel_size=3, padding=1)
34
+ ),
35
+ nn.ELU(),
36
+ weight_norm(
37
+ nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1)
38
+ ),
39
+ nn.ELU(),
40
+ weight_norm(
41
+ nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1)
42
+ ),
43
+ nn.ELU(),
44
+ weight_norm(
45
+ nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1)
46
+ ),
47
+ nn.ELU(),
48
+ weight_norm(
49
+ nn.Conv1d(cond_channels, cond_channels, kernel_size=3, padding=1)
50
+ ),
51
+ nn.ELU(),
52
+ )
53
+ self.classifier = nn.Linear(in_features=cond_channels, out_features=self.num_class)
54
+
55
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
56
+ x = self.condnet(x)
57
+ x = x.transpose(1, 2)
58
+ return torch.abs(self.classifier(x).squeeze(-1))
cosyvoice/hifigan/generator.py ADDED
@@ -0,0 +1,582 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Kai Hu)
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """HIFI-GAN"""
16
+
17
+ from typing import Dict, Optional, List
18
+ import numpy as np
19
+ from scipy.signal import get_window
20
+ import torch
21
+ import torch.nn as nn
22
+ import torch.nn.functional as F
23
+ from torch.nn import Conv1d
24
+ from torch.nn import ConvTranspose1d
25
+ from torch.nn.utils import remove_weight_norm
26
+ try:
27
+ from torch.nn.utils.parametrizations import weight_norm
28
+ except ImportError:
29
+ from torch.nn.utils import weight_norm
30
+ from torch.distributions.uniform import Uniform
31
+
32
+ from cosyvoice.transformer.activation import Snake
33
+ from cosyvoice.utils.common import get_padding
34
+ from cosyvoice.utils.common import init_weights
35
+
36
+
37
+ """hifigan based generator implementation.
38
+
39
+ This code is modified from https://github.com/jik876/hifi-gan
40
+ ,https://github.com/kan-bayashi/ParallelWaveGAN and
41
+ https://github.com/NVIDIA/BigVGAN
42
+
43
+ """
44
+
45
+
46
+ class ResBlock(torch.nn.Module):
47
+ """Residual block module in HiFiGAN/BigVGAN."""
48
+ def __init__(
49
+ self,
50
+ channels: int = 512,
51
+ kernel_size: int = 3,
52
+ dilations: List[int] = [1, 3, 5],
53
+ ):
54
+ super(ResBlock, self).__init__()
55
+ self.convs1 = nn.ModuleList()
56
+ self.convs2 = nn.ModuleList()
57
+
58
+ for dilation in dilations:
59
+ self.convs1.append(
60
+ weight_norm(
61
+ Conv1d(
62
+ channels,
63
+ channels,
64
+ kernel_size,
65
+ 1,
66
+ dilation=dilation,
67
+ padding=get_padding(kernel_size, dilation)
68
+ )
69
+ )
70
+ )
71
+ self.convs2.append(
72
+ weight_norm(
73
+ Conv1d(
74
+ channels,
75
+ channels,
76
+ kernel_size,
77
+ 1,
78
+ dilation=1,
79
+ padding=get_padding(kernel_size, 1)
80
+ )
81
+ )
82
+ )
83
+ self.convs1.apply(init_weights)
84
+ self.convs2.apply(init_weights)
85
+ self.activations1 = nn.ModuleList([
86
+ Snake(channels, alpha_logscale=False)
87
+ for _ in range(len(self.convs1))
88
+ ])
89
+ self.activations2 = nn.ModuleList([
90
+ Snake(channels, alpha_logscale=False)
91
+ for _ in range(len(self.convs2))
92
+ ])
93
+
94
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
95
+ for idx in range(len(self.convs1)):
96
+ xt = self.activations1[idx](x)
97
+ xt = self.convs1[idx](xt)
98
+ xt = self.activations2[idx](xt)
99
+ xt = self.convs2[idx](xt)
100
+ x = xt + x
101
+ return x
102
+
103
+ def remove_weight_norm(self):
104
+ for idx in range(len(self.convs1)):
105
+ remove_weight_norm(self.convs1[idx])
106
+ remove_weight_norm(self.convs2[idx])
107
+
108
+
109
+ class SineGen(torch.nn.Module):
110
+ """ Definition of sine generator
111
+ SineGen(samp_rate, harmonic_num = 0,
112
+ sine_amp = 0.1, noise_std = 0.003,
113
+ voiced_threshold = 0,
114
+ flag_for_pulse=False)
115
+ samp_rate: sampling rate in Hz
116
+ harmonic_num: number of harmonic overtones (default 0)
117
+ sine_amp: amplitude of sine-wavefrom (default 0.1)
118
+ noise_std: std of Gaussian noise (default 0.003)
119
+ voiced_thoreshold: F0 threshold for U/V classification (default 0)
120
+ flag_for_pulse: this SinGen is used inside PulseGen (default False)
121
+ Note: when flag_for_pulse is True, the first time step of a voiced
122
+ segment is always sin(np.pi) or cos(0)
123
+ """
124
+
125
+ def __init__(self, samp_rate, harmonic_num=0,
126
+ sine_amp=0.1, noise_std=0.003,
127
+ voiced_threshold=0):
128
+ super(SineGen, self).__init__()
129
+ self.sine_amp = sine_amp
130
+ self.noise_std = noise_std
131
+ self.harmonic_num = harmonic_num
132
+ self.sampling_rate = samp_rate
133
+ self.voiced_threshold = voiced_threshold
134
+
135
+ def _f02uv(self, f0):
136
+ # generate uv signal
137
+ uv = (f0 > self.voiced_threshold).type(torch.float32)
138
+ return uv
139
+
140
+ @torch.no_grad()
141
+ def forward(self, f0):
142
+ """
143
+ :param f0: [B, 1, sample_len], Hz
144
+ :return: [B, 1, sample_len]
145
+ """
146
+
147
+ F_mat = torch.zeros((f0.size(0), self.harmonic_num + 1, f0.size(-1))).to(f0.device)
148
+ for i in range(self.harmonic_num + 1):
149
+ F_mat[:, i: i + 1, :] = f0 * (i + 1) / self.sampling_rate
150
+
151
+ theta_mat = 2 * np.pi * (torch.cumsum(F_mat, dim=-1) % 1)
152
+ u_dist = Uniform(low=-np.pi, high=np.pi)
153
+ phase_vec = u_dist.sample(sample_shape=(f0.size(0), self.harmonic_num + 1, 1)).to(F_mat.device)
154
+ phase_vec[:, 0, :] = 0
155
+
156
+ # generate sine waveforms
157
+ sine_waves = self.sine_amp * torch.sin(theta_mat + phase_vec)
158
+
159
+ # generate uv signal
160
+ uv = self._f02uv(f0)
161
+
162
+ # noise: for unvoiced should be similar to sine_amp
163
+ # std = self.sine_amp/3 -> max value ~ self.sine_amp
164
+ # . for voiced regions is self.noise_std
165
+ noise_amp = uv * self.noise_std + (1 - uv) * self.sine_amp / 3
166
+ noise = noise_amp * torch.randn_like(sine_waves)
167
+
168
+ # first: set the unvoiced part to 0 by uv
169
+ # then: additive noise
170
+ sine_waves = sine_waves * uv + noise
171
+ return sine_waves, uv, noise
172
+
173
+
174
+ class SourceModuleHnNSF(torch.nn.Module):
175
+ """ SourceModule for hn-nsf
176
+ SourceModule(sampling_rate, harmonic_num=0, sine_amp=0.1,
177
+ add_noise_std=0.003, voiced_threshod=0)
178
+ sampling_rate: sampling_rate in Hz
179
+ harmonic_num: number of harmonic above F0 (default: 0)
180
+ sine_amp: amplitude of sine source signal (default: 0.1)
181
+ add_noise_std: std of additive Gaussian noise (default: 0.003)
182
+ note that amplitude of noise in unvoiced is decided
183
+ by sine_amp
184
+ voiced_threshold: threhold to set U/V given F0 (default: 0)
185
+ Sine_source, noise_source = SourceModuleHnNSF(F0_sampled)
186
+ F0_sampled (batchsize, length, 1)
187
+ Sine_source (batchsize, length, 1)
188
+ noise_source (batchsize, length 1)
189
+ uv (batchsize, length, 1)
190
+ """
191
+
192
+ def __init__(self, sampling_rate, upsample_scale, harmonic_num=0, sine_amp=0.1,
193
+ add_noise_std=0.003, voiced_threshod=0):
194
+ super(SourceModuleHnNSF, self).__init__()
195
+
196
+ self.sine_amp = sine_amp
197
+ self.noise_std = add_noise_std
198
+
199
+ # to produce sine waveforms
200
+ self.l_sin_gen = SineGen(sampling_rate, harmonic_num,
201
+ sine_amp, add_noise_std, voiced_threshod)
202
+
203
+ # to merge source harmonics into a single excitation
204
+ self.l_linear = torch.nn.Linear(harmonic_num + 1, 1)
205
+ self.l_tanh = torch.nn.Tanh()
206
+
207
+ def forward(self, x):
208
+ """
209
+ Sine_source, noise_source = SourceModuleHnNSF(F0_sampled)
210
+ F0_sampled (batchsize, length, 1)
211
+ Sine_source (batchsize, length, 1)
212
+ noise_source (batchsize, length 1)
213
+ """
214
+ # source for harmonic branch
215
+ with torch.no_grad():
216
+ sine_wavs, uv, _ = self.l_sin_gen(x.transpose(1, 2))
217
+ sine_wavs = sine_wavs.transpose(1, 2)
218
+ uv = uv.transpose(1, 2)
219
+ sine_merge = self.l_tanh(self.l_linear(sine_wavs))
220
+
221
+ # source for noise branch, in the same shape as uv
222
+ noise = torch.randn_like(uv) * self.sine_amp / 3
223
+ return sine_merge, noise, uv
224
+
225
+
226
+ class SineGen2(torch.nn.Module):
227
+ """ Definition of sine generator
228
+ SineGen(samp_rate, harmonic_num = 0,
229
+ sine_amp = 0.1, noise_std = 0.003,
230
+ voiced_threshold = 0,
231
+ flag_for_pulse=False)
232
+ samp_rate: sampling rate in Hz
233
+ harmonic_num: number of harmonic overtones (default 0)
234
+ sine_amp: amplitude of sine-wavefrom (default 0.1)
235
+ noise_std: std of Gaussian noise (default 0.003)
236
+ voiced_thoreshold: F0 threshold for U/V classification (default 0)
237
+ flag_for_pulse: this SinGen is used inside PulseGen (default False)
238
+ Note: when flag_for_pulse is True, the first time step of a voiced
239
+ segment is always sin(np.pi) or cos(0)
240
+ """
241
+
242
+ def __init__(self, samp_rate, upsample_scale, harmonic_num=0,
243
+ sine_amp=0.1, noise_std=0.003,
244
+ voiced_threshold=0,
245
+ flag_for_pulse=False):
246
+ super(SineGen2, self).__init__()
247
+ self.sine_amp = sine_amp
248
+ self.noise_std = noise_std
249
+ self.harmonic_num = harmonic_num
250
+ self.dim = self.harmonic_num + 1
251
+ self.sampling_rate = samp_rate
252
+ self.voiced_threshold = voiced_threshold
253
+ self.flag_for_pulse = flag_for_pulse
254
+ self.upsample_scale = upsample_scale
255
+
256
+ def _f02uv(self, f0):
257
+ # generate uv signal
258
+ uv = (f0 > self.voiced_threshold).type(torch.float32)
259
+ return uv
260
+
261
+ def _f02sine(self, f0_values):
262
+ """ f0_values: (batchsize, length, dim)
263
+ where dim indicates fundamental tone and overtones
264
+ """
265
+ # convert to F0 in rad. The interger part n can be ignored
266
+ # because 2 * np.pi * n doesn't affect phase
267
+ rad_values = (f0_values / self.sampling_rate) % 1
268
+
269
+ # initial phase noise (no noise for fundamental component)
270
+ rand_ini = torch.rand(f0_values.shape[0], f0_values.shape[2], device=f0_values.device)
271
+ rand_ini[:, 0] = 0
272
+ rad_values[:, 0, :] = rad_values[:, 0, :] + rand_ini
273
+
274
+ # instantanouse phase sine[t] = sin(2*pi \sum_i=1 ^{t} rad)
275
+ if not self.flag_for_pulse:
276
+ rad_values = torch.nn.functional.interpolate(rad_values.transpose(1, 2),
277
+ scale_factor=1 / self.upsample_scale,
278
+ mode="linear").transpose(1, 2)
279
+
280
+ phase = torch.cumsum(rad_values, dim=1) * 2 * np.pi
281
+ phase = torch.nn.functional.interpolate(phase.transpose(1, 2) * self.upsample_scale,
282
+ scale_factor=self.upsample_scale, mode="linear").transpose(1, 2)
283
+ sines = torch.sin(phase)
284
+ else:
285
+ # If necessary, make sure that the first time step of every
286
+ # voiced segments is sin(pi) or cos(0)
287
+ # This is used for pulse-train generation
288
+
289
+ # identify the last time step in unvoiced segments
290
+ uv = self._f02uv(f0_values)
291
+ uv_1 = torch.roll(uv, shifts=-1, dims=1)
292
+ uv_1[:, -1, :] = 1
293
+ u_loc = (uv < 1) * (uv_1 > 0)
294
+
295
+ # get the instantanouse phase
296
+ tmp_cumsum = torch.cumsum(rad_values, dim=1)
297
+ # different batch needs to be processed differently
298
+ for idx in range(f0_values.shape[0]):
299
+ temp_sum = tmp_cumsum[idx, u_loc[idx, :, 0], :]
300
+ temp_sum[1:, :] = temp_sum[1:, :] - temp_sum[0:-1, :]
301
+ # stores the accumulation of i.phase within
302
+ # each voiced segments
303
+ tmp_cumsum[idx, :, :] = 0
304
+ tmp_cumsum[idx, u_loc[idx, :, 0], :] = temp_sum
305
+
306
+ # rad_values - tmp_cumsum: remove the accumulation of i.phase
307
+ # within the previous voiced segment.
308
+ i_phase = torch.cumsum(rad_values - tmp_cumsum, dim=1)
309
+
310
+ # get the sines
311
+ sines = torch.cos(i_phase * 2 * np.pi)
312
+ return sines
313
+
314
+ def forward(self, f0):
315
+ """ sine_tensor, uv = forward(f0)
316
+ input F0: tensor(batchsize=1, length, dim=1)
317
+ f0 for unvoiced steps should be 0
318
+ output sine_tensor: tensor(batchsize=1, length, dim)
319
+ output uv: tensor(batchsize=1, length, 1)
320
+ """
321
+ # fundamental component
322
+ fn = torch.multiply(f0, torch.FloatTensor([[range(1, self.harmonic_num + 2)]]).to(f0.device))
323
+
324
+ # generate sine waveforms
325
+ sine_waves = self._f02sine(fn) * self.sine_amp
326
+
327
+ # generate uv signal
328
+ uv = self._f02uv(f0)
329
+
330
+ # noise: for unvoiced should be similar to sine_amp
331
+ # std = self.sine_amp/3 -> max value ~ self.sine_amp
332
+ # . for voiced regions is self.noise_std
333
+ noise_amp = uv * self.noise_std + (1 - uv) * self.sine_amp / 3
334
+ noise = noise_amp * torch.randn_like(sine_waves)
335
+
336
+ # first: set the unvoiced part to 0 by uv
337
+ # then: additive noise
338
+ sine_waves = sine_waves * uv + noise
339
+ return sine_waves, uv, noise
340
+
341
+
342
+ class SourceModuleHnNSF2(torch.nn.Module):
343
+ """ SourceModule for hn-nsf
344
+ SourceModule(sampling_rate, harmonic_num=0, sine_amp=0.1,
345
+ add_noise_std=0.003, voiced_threshod=0)
346
+ sampling_rate: sampling_rate in Hz
347
+ harmonic_num: number of harmonic above F0 (default: 0)
348
+ sine_amp: amplitude of sine source signal (default: 0.1)
349
+ add_noise_std: std of additive Gaussian noise (default: 0.003)
350
+ note that amplitude of noise in unvoiced is decided
351
+ by sine_amp
352
+ voiced_threshold: threhold to set U/V given F0 (default: 0)
353
+ Sine_source, noise_source = SourceModuleHnNSF(F0_sampled)
354
+ F0_sampled (batchsize, length, 1)
355
+ Sine_source (batchsize, length, 1)
356
+ noise_source (batchsize, length 1)
357
+ uv (batchsize, length, 1)
358
+ """
359
+
360
+ def __init__(self, sampling_rate, upsample_scale, harmonic_num=0, sine_amp=0.1,
361
+ add_noise_std=0.003, voiced_threshod=0):
362
+ super(SourceModuleHnNSF2, self).__init__()
363
+
364
+ self.sine_amp = sine_amp
365
+ self.noise_std = add_noise_std
366
+
367
+ # to produce sine waveforms
368
+ self.l_sin_gen = SineGen2(sampling_rate, upsample_scale, harmonic_num,
369
+ sine_amp, add_noise_std, voiced_threshod)
370
+
371
+ # to merge source harmonics into a single excitation
372
+ self.l_linear = torch.nn.Linear(harmonic_num + 1, 1)
373
+ self.l_tanh = torch.nn.Tanh()
374
+
375
+ def forward(self, x):
376
+ """
377
+ Sine_source, noise_source = SourceModuleHnNSF(F0_sampled)
378
+ F0_sampled (batchsize, length, 1)
379
+ Sine_source (batchsize, length, 1)
380
+ noise_source (batchsize, length 1)
381
+ """
382
+ # source for harmonic branch
383
+ with torch.no_grad():
384
+ sine_wavs, uv, _ = self.l_sin_gen(x)
385
+ sine_merge = self.l_tanh(self.l_linear(sine_wavs))
386
+
387
+ # source for noise branch, in the same shape as uv
388
+ noise = torch.randn_like(uv) * self.sine_amp / 3
389
+ return sine_merge, noise, uv
390
+
391
+
392
+ class HiFTGenerator(nn.Module):
393
+ """
394
+ HiFTNet Generator: Neural Source Filter + ISTFTNet
395
+ https://arxiv.org/abs/2309.09493
396
+ """
397
+ def __init__(
398
+ self,
399
+ in_channels: int = 80,
400
+ base_channels: int = 512,
401
+ nb_harmonics: int = 8,
402
+ sampling_rate: int = 22050,
403
+ nsf_alpha: float = 0.1,
404
+ nsf_sigma: float = 0.003,
405
+ nsf_voiced_threshold: float = 10,
406
+ upsample_rates: List[int] = [8, 8],
407
+ upsample_kernel_sizes: List[int] = [16, 16],
408
+ istft_params: Dict[str, int] = {"n_fft": 16, "hop_len": 4},
409
+ resblock_kernel_sizes: List[int] = [3, 7, 11],
410
+ resblock_dilation_sizes: List[List[int]] = [[1, 3, 5], [1, 3, 5], [1, 3, 5]],
411
+ source_resblock_kernel_sizes: List[int] = [7, 11],
412
+ source_resblock_dilation_sizes: List[List[int]] = [[1, 3, 5], [1, 3, 5]],
413
+ lrelu_slope: float = 0.1,
414
+ audio_limit: float = 0.99,
415
+ f0_predictor: torch.nn.Module = None,
416
+ ):
417
+ super(HiFTGenerator, self).__init__()
418
+
419
+ self.out_channels = 1
420
+ self.nb_harmonics = nb_harmonics
421
+ self.sampling_rate = sampling_rate
422
+ self.istft_params = istft_params
423
+ self.lrelu_slope = lrelu_slope
424
+ self.audio_limit = audio_limit
425
+
426
+ self.num_kernels = len(resblock_kernel_sizes)
427
+ self.num_upsamples = len(upsample_rates)
428
+ # NOTE in CosyVoice2, we use the original SourceModuleHnNSF implementation
429
+ this_SourceModuleHnNSF = SourceModuleHnNSF if self.sampling_rate == 22050 else SourceModuleHnNSF2
430
+ self.m_source = this_SourceModuleHnNSF(
431
+ sampling_rate=sampling_rate,
432
+ upsample_scale=np.prod(upsample_rates) * istft_params["hop_len"],
433
+ harmonic_num=nb_harmonics,
434
+ sine_amp=nsf_alpha,
435
+ add_noise_std=nsf_sigma,
436
+ voiced_threshod=nsf_voiced_threshold)
437
+ self.f0_upsamp = torch.nn.Upsample(scale_factor=np.prod(upsample_rates) * istft_params["hop_len"])
438
+
439
+ self.conv_pre = weight_norm(
440
+ Conv1d(in_channels, base_channels, 7, 1, padding=3)
441
+ )
442
+
443
+ # Up
444
+ self.ups = nn.ModuleList()
445
+ for i, (u, k) in enumerate(zip(upsample_rates, upsample_kernel_sizes)):
446
+ self.ups.append(
447
+ weight_norm(
448
+ ConvTranspose1d(
449
+ base_channels // (2**i),
450
+ base_channels // (2**(i + 1)),
451
+ k,
452
+ u,
453
+ padding=(k - u) // 2,
454
+ )
455
+ )
456
+ )
457
+
458
+ # Down
459
+ self.source_downs = nn.ModuleList()
460
+ self.source_resblocks = nn.ModuleList()
461
+ downsample_rates = [1] + upsample_rates[::-1][:-1]
462
+ downsample_cum_rates = np.cumprod(downsample_rates)
463
+ for i, (u, k, d) in enumerate(zip(downsample_cum_rates[::-1], source_resblock_kernel_sizes, source_resblock_dilation_sizes)):
464
+ if u == 1:
465
+ self.source_downs.append(
466
+ Conv1d(istft_params["n_fft"] + 2, base_channels // (2 ** (i + 1)), 1, 1)
467
+ )
468
+ else:
469
+ self.source_downs.append(
470
+ Conv1d(istft_params["n_fft"] + 2, base_channels // (2 ** (i + 1)), u * 2, u, padding=(u // 2))
471
+ )
472
+
473
+ self.source_resblocks.append(
474
+ ResBlock(base_channels // (2 ** (i + 1)), k, d)
475
+ )
476
+
477
+ self.resblocks = nn.ModuleList()
478
+ for i in range(len(self.ups)):
479
+ ch = base_channels // (2**(i + 1))
480
+ for _, (k, d) in enumerate(zip(resblock_kernel_sizes, resblock_dilation_sizes)):
481
+ self.resblocks.append(ResBlock(ch, k, d))
482
+
483
+ self.conv_post = weight_norm(Conv1d(ch, istft_params["n_fft"] + 2, 7, 1, padding=3))
484
+ self.ups.apply(init_weights)
485
+ self.conv_post.apply(init_weights)
486
+ self.reflection_pad = nn.ReflectionPad1d((1, 0))
487
+ self.stft_window = torch.from_numpy(get_window("hann", istft_params["n_fft"], fftbins=True).astype(np.float32))
488
+ self.f0_predictor = f0_predictor
489
+
490
+ def remove_weight_norm(self):
491
+ print('Removing weight norm...')
492
+ for l in self.ups:
493
+ remove_weight_norm(l)
494
+ for l in self.resblocks:
495
+ l.remove_weight_norm()
496
+ remove_weight_norm(self.conv_pre)
497
+ remove_weight_norm(self.conv_post)
498
+ self.m_source.remove_weight_norm()
499
+ for l in self.source_downs:
500
+ remove_weight_norm(l)
501
+ for l in self.source_resblocks:
502
+ l.remove_weight_norm()
503
+
504
+ def _stft(self, x):
505
+ spec = torch.stft(
506
+ x,
507
+ self.istft_params["n_fft"], self.istft_params["hop_len"], self.istft_params["n_fft"], window=self.stft_window.to(x.device),
508
+ return_complex=True)
509
+ spec = torch.view_as_real(spec) # [B, F, TT, 2]
510
+ return spec[..., 0], spec[..., 1]
511
+
512
+ def _istft(self, magnitude, phase):
513
+ magnitude = torch.clip(magnitude, max=1e2)
514
+ real = magnitude * torch.cos(phase)
515
+ img = magnitude * torch.sin(phase)
516
+ inverse_transform = torch.istft(torch.complex(real, img), self.istft_params["n_fft"], self.istft_params["hop_len"],
517
+ self.istft_params["n_fft"], window=self.stft_window.to(magnitude.device))
518
+ return inverse_transform
519
+
520
+ def decode(self, x: torch.Tensor, s: torch.Tensor = torch.zeros(1, 1, 0)) -> torch.Tensor:
521
+ s_stft_real, s_stft_imag = self._stft(s.squeeze(1))
522
+ s_stft = torch.cat([s_stft_real, s_stft_imag], dim=1)
523
+
524
+ x = self.conv_pre(x)
525
+ for i in range(self.num_upsamples):
526
+ x = F.leaky_relu(x, self.lrelu_slope)
527
+ x = self.ups[i](x)
528
+
529
+ if i == self.num_upsamples - 1:
530
+ x = self.reflection_pad(x)
531
+
532
+ # fusion
533
+ si = self.source_downs[i](s_stft)
534
+ si = self.source_resblocks[i](si)
535
+ x = x + si
536
+
537
+ xs = None
538
+ for j in range(self.num_kernels):
539
+ if xs is None:
540
+ xs = self.resblocks[i * self.num_kernels + j](x)
541
+ else:
542
+ xs += self.resblocks[i * self.num_kernels + j](x)
543
+ x = xs / self.num_kernels
544
+
545
+ x = F.leaky_relu(x)
546
+ x = self.conv_post(x)
547
+ magnitude = torch.exp(x[:, :self.istft_params["n_fft"] // 2 + 1, :])
548
+ phase = torch.sin(x[:, self.istft_params["n_fft"] // 2 + 1:, :]) # actually, sin is redundancy
549
+
550
+ x = self._istft(magnitude, phase)
551
+ x = torch.clamp(x, -self.audio_limit, self.audio_limit)
552
+ return x
553
+
554
+ def forward(
555
+ self,
556
+ batch: dict,
557
+ device: torch.device,
558
+ ) -> Dict[str, Optional[torch.Tensor]]:
559
+ speech_feat = batch['speech_feat'].transpose(1, 2).to(device)
560
+ # mel->f0
561
+ f0 = self.f0_predictor(speech_feat)
562
+ # f0->source
563
+ s = self.f0_upsamp(f0[:, None]).transpose(1, 2) # bs,n,t
564
+ s, _, _ = self.m_source(s)
565
+ s = s.transpose(1, 2)
566
+ # mel+source->speech
567
+ generated_speech = self.decode(x=speech_feat, s=s)
568
+ return generated_speech, f0
569
+
570
+ @torch.inference_mode()
571
+ def inference(self, speech_feat: torch.Tensor, cache_source: torch.Tensor = torch.zeros(1, 1, 0)) -> torch.Tensor:
572
+ # mel->f0
573
+ f0 = self.f0_predictor(speech_feat)
574
+ # f0->source
575
+ s = self.f0_upsamp(f0[:, None]).transpose(1, 2) # bs,n,t
576
+ s, _, _ = self.m_source(s)
577
+ s = s.transpose(1, 2)
578
+ # use cache_source to avoid glitch
579
+ if cache_source.shape[2] != 0:
580
+ s[:, :, :cache_source.shape[2]] = cache_source
581
+ generated_speech = self.decode(x=speech_feat, s=s)
582
+ return generated_speech, s
cosyvoice/hifigan/hifigan.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Optional
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ from matcha.hifigan.models import feature_loss, generator_loss, discriminator_loss
6
+ from cosyvoice.utils.losses import tpr_loss, mel_loss
7
+
8
+
9
+ class HiFiGan(nn.Module):
10
+ def __init__(self, generator, discriminator, mel_spec_transform,
11
+ multi_mel_spectral_recon_loss_weight=45, feat_match_loss_weight=2.0,
12
+ tpr_loss_weight=1.0, tpr_loss_tau=0.04):
13
+ super(HiFiGan, self).__init__()
14
+ self.generator = generator
15
+ self.discriminator = discriminator
16
+ self.mel_spec_transform = mel_spec_transform
17
+ self.multi_mel_spectral_recon_loss_weight = multi_mel_spectral_recon_loss_weight
18
+ self.feat_match_loss_weight = feat_match_loss_weight
19
+ self.tpr_loss_weight = tpr_loss_weight
20
+ self.tpr_loss_tau = tpr_loss_tau
21
+
22
+ def forward(
23
+ self,
24
+ batch: dict,
25
+ device: torch.device,
26
+ ) -> Dict[str, Optional[torch.Tensor]]:
27
+ if batch['turn'] == 'generator':
28
+ return self.forward_generator(batch, device)
29
+ else:
30
+ return self.forward_discriminator(batch, device)
31
+
32
+ def forward_generator(self, batch, device):
33
+ real_speech = batch['speech'].to(device)
34
+ pitch_feat = batch['pitch_feat'].to(device)
35
+ # 1. calculate generator outputs
36
+ generated_speech, generated_f0 = self.generator(batch, device)
37
+ # 2. calculate discriminator outputs
38
+ y_d_rs, y_d_gs, fmap_rs, fmap_gs = self.discriminator(real_speech, generated_speech)
39
+ # 3. calculate generator losses, feature loss, mel loss, tpr losses [Optional]
40
+ loss_gen, _ = generator_loss(y_d_gs)
41
+ loss_fm = feature_loss(fmap_rs, fmap_gs)
42
+ loss_mel = mel_loss(real_speech, generated_speech, self.mel_spec_transform)
43
+ if self.tpr_loss_weight != 0:
44
+ loss_tpr = tpr_loss(y_d_gs, y_d_rs, self.tpr_loss_tau)
45
+ else:
46
+ loss_tpr = torch.zeros(1).to(device)
47
+ loss_f0 = F.l1_loss(generated_f0, pitch_feat)
48
+ loss = loss_gen + self.feat_match_loss_weight * loss_fm + \
49
+ self.multi_mel_spectral_recon_loss_weight * loss_mel + \
50
+ self.tpr_loss_weight * loss_tpr + loss_f0
51
+ return {'loss': loss, 'loss_gen': loss_gen, 'loss_fm': loss_fm, 'loss_mel': loss_mel, 'loss_tpr': loss_tpr, 'loss_f0': loss_f0}
52
+
53
+ def forward_discriminator(self, batch, device):
54
+ real_speech = batch['speech'].to(device)
55
+ # 1. calculate generator outputs
56
+ with torch.no_grad():
57
+ generated_speech, generated_f0 = self.generator(batch, device)
58
+ # 2. calculate discriminator outputs
59
+ y_d_rs, y_d_gs, fmap_rs, fmap_gs = self.discriminator(real_speech, generated_speech.detach())
60
+ # 3. calculate discriminator losses, tpr losses [Optional]
61
+ loss_disc, _, _ = discriminator_loss(y_d_rs, y_d_gs)
62
+ if self.tpr_loss_weight != 0:
63
+ loss_tpr = tpr_loss(y_d_rs, y_d_gs, self.tpr_loss_tau)
64
+ else:
65
+ loss_tpr = torch.zeros(1).to(device)
66
+ loss = loss_disc + self.tpr_loss_weight * loss_tpr
67
+ return {'loss': loss, 'loss_disc': loss_disc, 'loss_tpr': loss_tpr}
cosyvoice/llm/__pycache__/llm.cpython-310.pyc ADDED
Binary file (17 kB). View file
 
cosyvoice/llm/llm.py ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024 Alibaba Inc (authors: Xiang Lyu, Zhihao Du)
2
+ # 2025 Alibaba Inc (authors: Xiang Lyu, Yabin Li, Qihua, Shengqiang Li)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import queue
16
+ import random
17
+ import time
18
+ import threading
19
+ from typing import Dict, Optional, Callable, List, Generator
20
+ import torch
21
+ from torch import nn
22
+ import torch.nn.functional as F
23
+ from transformers import Qwen2ForCausalLM
24
+ from torch.nn.utils.rnn import pad_sequence, unpad_sequence
25
+ from cosyvoice.utils.common import IGNORE_ID
26
+ from cosyvoice.transformer.label_smoothing_loss import LabelSmoothingLoss
27
+ from cosyvoice.utils.common import th_accuracy
28
+ from cosyvoice.utils.file_utils import logging
29
+ from cosyvoice.utils.mask import make_pad_mask
30
+
31
+
32
+ class TransformerLM(torch.nn.Module):
33
+ def __init__(
34
+ self,
35
+ text_encoder_input_size: int,
36
+ llm_input_size: int,
37
+ llm_output_size: int,
38
+ text_token_size: int,
39
+ speech_token_size: int,
40
+ text_encoder: torch.nn.Module,
41
+ llm: torch.nn.Module,
42
+ sampling: Callable,
43
+ length_normalized_loss: bool = True,
44
+ lsm_weight: float = 0.0,
45
+ spk_embed_dim: int = 192,
46
+ ):
47
+ super().__init__()
48
+ self.llm_input_size = llm_input_size
49
+ self.speech_token_size = speech_token_size
50
+ # 1. build text token inputs related modules
51
+ self.text_embedding = torch.nn.Embedding(text_token_size, text_encoder_input_size)
52
+ self.text_encoder = text_encoder
53
+ self.text_encoder_affine_layer = nn.Linear(
54
+ self.text_encoder.output_size(),
55
+ llm_input_size
56
+ )
57
+
58
+ # 2. build speech token language model related modules
59
+ self.sos_eos = 0
60
+ self.task_id = 1
61
+ self.llm_embedding = torch.nn.Embedding(2, llm_input_size)
62
+ self.llm = llm
63
+ self.llm_decoder = nn.Linear(llm_output_size, speech_token_size + 1)
64
+ self.criterion_ce = LabelSmoothingLoss(
65
+ size=speech_token_size + 1,
66
+ padding_idx=IGNORE_ID,
67
+ smoothing=lsm_weight,
68
+ normalize_length=length_normalized_loss,
69
+ )
70
+
71
+ # 3. [Optional] build speech token related modules
72
+ self.speech_embedding = torch.nn.Embedding(speech_token_size, llm_input_size)
73
+ self.spk_embed_affine_layer = torch.nn.Linear(spk_embed_dim, llm_input_size)
74
+
75
+ # 4. sampling method
76
+ self.sampling = sampling
77
+
78
+ def encode(
79
+ self,
80
+ text: torch.Tensor,
81
+ text_lengths: torch.Tensor,
82
+ ):
83
+ encoder_out, encoder_mask = self.text_encoder(text, text_lengths, decoding_chunk_size=1, num_decoding_left_chunks=-1)
84
+ encoder_out_lens = encoder_mask.squeeze(1).sum(1)
85
+ encoder_out = self.text_encoder_affine_layer(encoder_out)
86
+ return encoder_out, encoder_out_lens
87
+
88
+ def pad_unpad_sequence(self, sos_eos_emb, embedding, text_token, text_token_len, task_id_emb, speech_token, speech_token_len):
89
+ text_token = unpad_sequence(text_token, text_token_len.cpu(), batch_first=True)
90
+ speech_token = unpad_sequence(speech_token, speech_token_len.cpu(), batch_first=True)
91
+ lm_input = [torch.concat([sos_eos_emb.squeeze(dim=0), embedding[i], text_token[i], task_id_emb.squeeze(dim=0), speech_token[i]], dim=0)
92
+ for i in range(len(text_token))]
93
+ lm_input_len = torch.tensor([i.size(0) for i in lm_input], dtype=torch.int32)
94
+ lm_input = pad_sequence(lm_input, batch_first=True, padding_value=IGNORE_ID)
95
+ return lm_input, lm_input_len
96
+
97
+ def forward(
98
+ self,
99
+ batch: dict,
100
+ device: torch.device,
101
+ ) -> Dict[str, Optional[torch.Tensor]]:
102
+ """
103
+ Args:
104
+ text: (B, L, D)
105
+ text_lengths: (B,)
106
+ audio: (B, T, N) or (B, T)
107
+ audio_lengths: (B,)
108
+ """
109
+ text_token = batch['text_token'].to(device)
110
+ text_token_len = batch['text_token_len'].to(device)
111
+ speech_token = batch['speech_token'].to(device)
112
+ speech_token_len = batch['speech_token_len'].to(device)
113
+ embedding = batch['embedding'].to(device)
114
+
115
+ # 1. prepare llm_target
116
+ lm_target = [torch.tensor([IGNORE_ID] * (2 + text_token_len[i]) + speech_token[i, :speech_token_len[i]].tolist() +
117
+ [self.speech_token_size]) for i in range(text_token.size(0))]
118
+ lm_target = pad_sequence(lm_target, batch_first=True, padding_value=IGNORE_ID).to(device)
119
+
120
+ # 1. encode text_token
121
+ text_token = self.text_embedding(text_token)
122
+ text_token, text_token_len = self.encode(text_token, text_token_len)
123
+
124
+ # 2. embedding projection
125
+ embedding = F.normalize(embedding, dim=1)
126
+ embedding = self.spk_embed_affine_layer(embedding)
127
+ embedding = embedding.unsqueeze(1)
128
+
129
+ # 3. eos and task_id
130
+ sos_eos_emb = self.llm_embedding.weight[self.sos_eos].reshape(1, 1, -1)
131
+ task_id_emb = self.llm_embedding.weight[self.task_id].reshape(1, 1, -1)
132
+
133
+ # 4. encode speech_token
134
+ speech_token = self.speech_embedding(speech_token)
135
+
136
+ # 5. unpad and pad
137
+ lm_input, lm_input_len = self.pad_unpad_sequence(sos_eos_emb, embedding, text_token, text_token_len,
138
+ task_id_emb, speech_token, speech_token_len)
139
+
140
+ # 6. run lm forward
141
+ lm_output, lm_output_mask = self.llm(lm_input, lm_input_len.to(device))
142
+ logits = self.llm_decoder(lm_output)
143
+ loss = self.criterion_ce(logits, lm_target)
144
+ acc = th_accuracy(logits.view(-1, self.speech_token_size + 1), lm_target, ignore_label=IGNORE_ID)
145
+ return {'loss': loss, 'acc': acc}
146
+
147
+ def sampling_ids(
148
+ self,
149
+ weighted_scores: torch.Tensor,
150
+ decoded_tokens: List,
151
+ sampling: int,
152
+ ignore_eos: bool = True,
153
+ ):
154
+ num_trials, max_trials = 0, 100
155
+ while True:
156
+ top_ids = self.sampling(weighted_scores, decoded_tokens, sampling)
157
+ if (not ignore_eos) or (self.speech_token_size not in top_ids):
158
+ break
159
+ num_trials += 1
160
+ if num_trials > max_trials:
161
+ raise RuntimeError('sampling reaches max_trials {} and still get eos when ignore_eos is True, check your input!'.format(max_trials))
162
+ return top_ids
163
+
164
+ @torch.inference_mode()
165
+ def inference(
166
+ self,
167
+ text: torch.Tensor,
168
+ text_len: torch.Tensor,
169
+ prompt_text: torch.Tensor,
170
+ prompt_text_len: torch.Tensor,
171
+ prompt_speech_token: torch.Tensor,
172
+ prompt_speech_token_len: torch.Tensor,
173
+ embedding: torch.Tensor,
174
+ sampling: int = 25,
175
+ max_token_text_ratio: float = 20,
176
+ min_token_text_ratio: float = 2,
177
+ uuid: str = '',
178
+ ) -> Generator[torch.Tensor, None, None]:
179
+ device = text.device
180
+ text = torch.concat([prompt_text, text], dim=1)
181
+ text_len += prompt_text_len
182
+ text = self.text_embedding(text)
183
+
184
+ # 1. encode text
185
+ text, text_len = self.encode(text, text_len)
186
+
187
+ # 2. encode embedding
188
+ if embedding.shape[0] != 0:
189
+ embedding = F.normalize(embedding, dim=1)
190
+ embedding = self.spk_embed_affine_layer(embedding)
191
+ embedding = embedding.unsqueeze(dim=1)
192
+ else:
193
+ embedding = torch.zeros(1, 0, self.llm_input_size, dtype=text.dtype).to(device).to(text.dtype)
194
+
195
+ # 3. concat llm_input
196
+ sos_eos_emb = self.llm_embedding.weight[self.sos_eos].reshape(1, 1, -1)
197
+ task_id_emb = self.llm_embedding.weight[self.task_id].reshape(1, 1, -1)
198
+ if prompt_speech_token_len != 0:
199
+ prompt_speech_token_emb = self.speech_embedding(prompt_speech_token)
200
+ else:
201
+ prompt_speech_token_emb = torch.zeros(1, 0, self.llm_input_size, dtype=text.dtype).to(device)
202
+ lm_input = torch.concat([sos_eos_emb, embedding, text, task_id_emb, prompt_speech_token_emb], dim=1)
203
+
204
+ # 4. cal min/max_length
205
+ min_len = int((text_len - prompt_text_len) * min_token_text_ratio)
206
+ max_len = int((text_len - prompt_text_len) * max_token_text_ratio)
207
+
208
+ # 5. step by step decode
209
+ out_tokens = []
210
+ offset = 0
211
+ att_cache, cnn_cache = torch.zeros((0, 0, 0, 0), device=lm_input.device), torch.zeros((0, 0, 0, 0), device=lm_input.device)
212
+ for i in range(max_len):
213
+ y_pred, att_cache, cnn_cache = self.llm.forward_chunk(lm_input, offset=offset, required_cache_size=-1,
214
+ att_cache=att_cache, cnn_cache=cnn_cache,
215
+ att_mask=torch.tril(torch.ones((1, lm_input.shape[1], lm_input.shape[1]),
216
+ device=lm_input.device)).to(torch.bool))
217
+ logp = self.llm_decoder(y_pred[:, -1]).log_softmax(dim=-1)
218
+ # force continue decode first token
219
+ if i == 0:
220
+ logp[:, self.speech_token_size] = -float('inf')
221
+ top_ids = self.sampling_ids(logp.squeeze(dim=0), out_tokens, sampling, ignore_eos=True if i < min_len else False).item()
222
+ if top_ids == self.speech_token_size:
223
+ break
224
+ # in stream mode, yield token one by one
225
+ yield top_ids
226
+ out_tokens.append(top_ids)
227
+ offset += lm_input.size(1)
228
+ lm_input = self.speech_embedding.weight[top_ids].reshape(1, 1, -1)
229
+
230
+
231
+ class Qwen2Encoder(torch.nn.Module):
232
+ def __init__(self, pretrain_path):
233
+ super().__init__()
234
+ self.model = Qwen2ForCausalLM.from_pretrained(pretrain_path)
235
+
236
+ def forward(self, xs: torch.Tensor, xs_lens: torch.Tensor):
237
+ T = xs.size(1)
238
+ masks = ~make_pad_mask(xs_lens, T)
239
+ outs = self.model(
240
+ inputs_embeds=xs,
241
+ attention_mask=masks,
242
+ output_hidden_states=True,
243
+ return_dict=True,
244
+ )
245
+ return outs.hidden_states[-1], masks.unsqueeze(1)
246
+
247
+ def forward_one_step(self, xs, masks, cache=None):
248
+ input_masks = masks[:, -1, :]
249
+ outs = self.model(
250
+ inputs_embeds=xs,
251
+ attention_mask=input_masks,
252
+ output_hidden_states=True,
253
+ return_dict=True,
254
+ use_cache=True,
255
+ past_key_values=cache,
256
+ )
257
+ xs = outs.hidden_states[-1]
258
+ new_cache = outs.past_key_values
259
+ return xs, new_cache
260
+
261
+
262
+ class Qwen2LM(TransformerLM):
263
+ def __init__(
264
+ self,
265
+ llm_input_size: int,
266
+ llm_output_size: int,
267
+ speech_token_size: int,
268
+ llm: torch.nn.Module,
269
+ sampling: Callable,
270
+ length_normalized_loss: bool = True,
271
+ lsm_weight: float = 0.0,
272
+ mix_ratio: List[int] = [5, 15],
273
+ ):
274
+ torch.nn.Module.__init__(self)
275
+ self.llm_input_size = llm_input_size
276
+ self.llm_output_size = llm_output_size
277
+ self.speech_token_size = speech_token_size
278
+ # 2. build speech token language model related modules
279
+ self.sos_eos = 0
280
+ self.task_id = 1
281
+ self.fill_token = 2
282
+
283
+ self.llm_embedding = torch.nn.Embedding(2, llm_input_size)
284
+ self.llm = llm
285
+ self.llm_decoder = nn.Linear(llm_output_size, speech_token_size + 3)
286
+ self.criterion_ce = LabelSmoothingLoss(
287
+ size=speech_token_size + 3,
288
+ padding_idx=IGNORE_ID,
289
+ smoothing=lsm_weight,
290
+ normalize_length=length_normalized_loss,
291
+ )
292
+
293
+ # 3. [Optional] build speech token related modules
294
+ self.speech_embedding = torch.nn.Embedding(speech_token_size + 3, llm_input_size)
295
+
296
+ # 4. sampling method
297
+ self.sampling = sampling
298
+ self.mix_ratio = mix_ratio
299
+
300
+ # 5. vllm related
301
+ self.stop_token_ids = [speech_token_size + i for i in range(3)]
302
+ self.vllm_output_queue = {}
303
+
304
+ def prepare_lm_input_target(self, text_token, text_token_emb, text_token_len, speech_token, speech_token_emb, speech_token_len):
305
+ lm_target, lm_input = [], []
306
+ text_token = unpad_sequence(text_token, text_token_len.cpu(), batch_first=True)
307
+ speech_token = unpad_sequence(speech_token, speech_token_len.cpu(), batch_first=True)
308
+ text_token_emb = unpad_sequence(text_token_emb, text_token_len.cpu(), batch_first=True)
309
+ speech_token_emb = unpad_sequence(speech_token_emb, speech_token_len.cpu(), batch_first=True)
310
+ for i in range(len(text_token)):
311
+ # bistream sequence
312
+ if random.random() < 0.5 and speech_token_len[i] / text_token_len[i] > self.mix_ratio[1] / self.mix_ratio[0]:
313
+ this_lm_target, this_lm_input = [], []
314
+ this_lm_target.append(IGNORE_ID)
315
+ this_lm_input.append(self.llm_embedding.weight[self.sos_eos].reshape(1, -1))
316
+ for j in range(((text_token_len[i] + 1) / self.mix_ratio[0]).ceil().int().item()):
317
+ this_text_token = text_token[i][j * self.mix_ratio[0]: (j + 1) * self.mix_ratio[0]].tolist()
318
+ this_speech_token = speech_token[i][j * self.mix_ratio[1]: (j + 1) * self.mix_ratio[1]].tolist()
319
+ if len(this_text_token) == self.mix_ratio[0]:
320
+ assert len(this_speech_token) == self.mix_ratio[1]
321
+ this_lm_target += [IGNORE_ID] * (self.mix_ratio[0] - 1)
322
+ this_lm_target += this_speech_token
323
+ this_lm_target.append(self.speech_token_size + 2)
324
+ this_lm_input.append(text_token_emb[i][j * self.mix_ratio[0]: (j + 1) * self.mix_ratio[0]])
325
+ this_lm_input.append(speech_token_emb[i][j * self.mix_ratio[1]: (j + 1) * self.mix_ratio[1]])
326
+ else:
327
+ this_lm_target += [-1] * len(this_text_token)
328
+ this_lm_target += speech_token[i][j * self.mix_ratio[1]:].tolist()
329
+ this_lm_target.append(self.speech_token_size)
330
+ this_lm_input.append(text_token_emb[i][j * self.mix_ratio[0]:])
331
+ this_lm_input.append(self.llm_embedding.weight[self.task_id].reshape(1, -1))
332
+ this_lm_input.append(speech_token_emb[i][j * self.mix_ratio[1]:])
333
+ this_lm_target, this_lm_input = torch.tensor(this_lm_target), torch.concat(this_lm_input, dim=0)
334
+ # unistream sequence
335
+ else:
336
+ this_lm_target = torch.tensor([IGNORE_ID] * (1 + text_token_len[i]) + speech_token[i].tolist() + [self.speech_token_size])
337
+ this_lm_input = torch.concat([self.llm_embedding.weight[self.sos_eos].reshape(1, -1), text_token_emb[i],
338
+ self.llm_embedding.weight[self.task_id].reshape(1, -1), speech_token_emb[i]], dim=0)
339
+ lm_target.append(this_lm_target)
340
+ lm_input.append(this_lm_input)
341
+ lm_input_len = torch.tensor([i.size(0) for i in lm_input], dtype=torch.int32)
342
+ lm_input = pad_sequence(lm_input, batch_first=True, padding_value=IGNORE_ID)
343
+ lm_target = pad_sequence(lm_target, batch_first=True, padding_value=IGNORE_ID)
344
+ return lm_target, lm_input, lm_input_len
345
+
346
+ def forward(
347
+ self,
348
+ batch: dict,
349
+ device: torch.device,
350
+ ) -> Dict[str, Optional[torch.Tensor]]:
351
+ """
352
+ Args:
353
+ text: (B, L, D)
354
+ text_lengths: (B,)
355
+ audio: (B, T, N) or (B, T)
356
+ audio_lengths: (B,)
357
+ """
358
+ text_token = batch['text_token'].to(device)
359
+ text_token_len = batch['text_token_len'].to(device)
360
+ speech_token = batch['speech_token'].to(device)
361
+ speech_token_len = batch['speech_token_len'].to(device)
362
+
363
+ # 1. encode text_token
364
+ text_token_emb = self.llm.model.model.embed_tokens(text_token)
365
+
366
+ # 2. encode speech_token
367
+ speech_token_emb = self.speech_embedding(speech_token)
368
+
369
+ # 3. prepare llm_input/target
370
+ lm_target, lm_input, lm_input_len = self.prepare_lm_input_target(text_token, text_token_emb, text_token_len, speech_token, speech_token_emb, speech_token_len)
371
+ lm_target = lm_target.to(device)
372
+
373
+ # 4. run lm forward
374
+ lm_output, lm_output_mask = self.llm(lm_input, lm_input_len.to(device))
375
+ logits = self.llm_decoder(lm_output)
376
+ loss = self.criterion_ce(logits, lm_target.to(device))
377
+ acc = th_accuracy(logits.view(-1, self.speech_token_size + 3), lm_target, ignore_label=IGNORE_ID)
378
+ return {'loss': loss, 'acc': acc}
379
+
380
+ def forward_dpo(
381
+ self,
382
+ batch: dict,
383
+ device: torch.device,
384
+ ) -> Dict[str, Optional[torch.Tensor]]:
385
+ text_token = batch['text_token'].to(device)
386
+ text_token_len = batch['text_token_len'].to(device)
387
+ speech_token = batch['speech_token'].to(device)
388
+ speech_token_len = batch['speech_token_len'].to(device)
389
+ reject_speech_token = batch['reject_speech_token'].to(device)
390
+ reject_speech_token_len = batch['reject_speech_token_len'].to(device)
391
+
392
+ # 1. encode text_token
393
+ text_token_emb = self.llm.model.model.embed_tokens(text_token)
394
+
395
+ # 2. encode speech_token
396
+ speech_token = unpad_sequence(speech_token, speech_token_len.cpu(), batch_first=True)
397
+ reject_speech_token = unpad_sequence(reject_speech_token, reject_speech_token_len.cpu(), batch_first=True)
398
+ speech_token_combined = speech_token + reject_speech_token
399
+ speech_token_combined = pad_sequence(speech_token_combined, batch_first=True, padding_value=0)
400
+ speech_token_combined_len = torch.concat([speech_token_len, reject_speech_token_len], dim=0)
401
+ speech_token_combined_emb = self.speech_embedding(speech_token_combined)
402
+
403
+ # 3. prepare llm_input/target
404
+ lm_target, lm_input, lm_input_len = self.prepare_lm_input_target(text_token.repeat(2, 1), text_token_emb.repeat(2, 1, 1), text_token_len.repeat(2),
405
+ speech_token_combined, speech_token_combined_emb, speech_token_combined_len)
406
+ lm_target = lm_target.to(device)
407
+
408
+ # 4. run lm forward
409
+ lm_output, lm_output_mask = self.llm(lm_input, lm_input_len.to(device))
410
+ logits = self.llm_decoder(lm_output)
411
+ chosen_logits = logits[:text_token.shape[0]]
412
+ rejected_logits = logits[text_token.shape[0]:]
413
+ chosen_lm_target = lm_target[:text_token.shape[0]]
414
+ rejected_lm_target = lm_target[text_token.shape[0]:]
415
+ loss = self.criterion_ce(chosen_logits, chosen_lm_target.to(device))
416
+ acc = th_accuracy(chosen_logits.view(-1, self.speech_token_size + 3), chosen_lm_target, ignore_label=IGNORE_ID)
417
+
418
+ # 5. calculate dpo logits
419
+ chosen_lm_mask = chosen_lm_target == IGNORE_ID
420
+ rejected_lm_mask = rejected_lm_target == IGNORE_ID
421
+ chosen_logps = torch.gather(chosen_logits.log_softmax(dim=-1), dim=2, index=chosen_lm_target.masked_fill(chosen_lm_mask, 0).unsqueeze(dim=-1)).squeeze(dim=-1)
422
+ rejected_logps = torch.gather(rejected_logits.log_softmax(dim=-1), dim=2, index=rejected_lm_target.masked_fill(rejected_lm_mask, 0).unsqueeze(dim=-1)).squeeze(dim=-1)
423
+ chosen_logps = (chosen_logps * chosen_lm_mask).sum(dim=-1) / chosen_lm_mask.sum(dim=-1)
424
+ rejected_logps = (rejected_logps * rejected_lm_mask).sum(dim=-1) / rejected_lm_mask.sum(dim=-1)
425
+ return {'loss': loss, 'acc': acc, 'chosen_logps': chosen_logps, 'rejected_logps': rejected_logps}
426
+
427
+ @torch.inference_mode()
428
+ def inference(
429
+ self,
430
+ text: torch.Tensor,
431
+ text_len: torch.Tensor,
432
+ prompt_text: torch.Tensor,
433
+ prompt_text_len: torch.Tensor,
434
+ prompt_speech_token: torch.Tensor,
435
+ prompt_speech_token_len: torch.Tensor,
436
+ embedding: torch.Tensor,
437
+ sampling: int = 25,
438
+ max_token_text_ratio: float = 20,
439
+ min_token_text_ratio: float = 2,
440
+ uuid: str = '',
441
+ ) -> Generator[torch.Tensor, None, None]:
442
+ device = text.device
443
+ text = torch.concat([prompt_text, text], dim=1)
444
+ text_len += prompt_text_len
445
+ text = self.llm.model.model.embed_tokens(text)
446
+
447
+ # 3. concat llm_input
448
+ sos_eos_emb = self.llm_embedding.weight[self.sos_eos].reshape(1, 1, -1)
449
+ task_id_emb = self.llm_embedding.weight[self.task_id].reshape(1, 1, -1)
450
+ if prompt_speech_token_len != 0:
451
+ prompt_speech_token_emb = self.speech_embedding(prompt_speech_token)
452
+ else:
453
+ prompt_speech_token_emb = torch.zeros(1, 0, self.llm_input_size, dtype=text.dtype).to(device)
454
+ lm_input = torch.concat([sos_eos_emb, text, task_id_emb, prompt_speech_token_emb], dim=1)
455
+
456
+ # 4. cal min/max_length
457
+ min_len = int((text_len - prompt_text_len) * min_token_text_ratio)
458
+ max_len = int((text_len - prompt_text_len) * max_token_text_ratio)
459
+
460
+ # 5. step by step decode
461
+ for token in self.inference_wrapper(lm_input, sampling, min_len, max_len, uuid):
462
+ yield token
463
+
464
+ @torch.inference_mode()
465
+ def inference_wrapper(self, lm_input, sampling, min_len, max_len, uuid):
466
+ if hasattr(self, 'vllm'):
467
+ from vllm import SamplingParams, RequestOutput
468
+ sampling_params = SamplingParams(top_k=sampling,
469
+ stop_token_ids=self.stop_token_ids,
470
+ min_tokens=min_len,
471
+ max_tokens=max_len)
472
+ with self.lock:
473
+ self.vllm.add_request(uuid, {"prompt_embeds": lm_input.squeeze(0).to(torch.bfloat16).to(lm_input.device)}, sampling_params)
474
+ self.vllm_output_queue[uuid] = queue.Queue()
475
+ out_tokens = []
476
+ while True:
477
+ with self.lock:
478
+ if self.vllm_output_queue[uuid].empty() is True:
479
+ request_outputs: List[RequestOutput] = self.vllm.step()
480
+ for request_output in request_outputs:
481
+ top_ids = list(request_output.outputs[0].token_ids)[-1]
482
+ self.vllm_output_queue[request_output.request_id].put(top_ids)
483
+ if self.vllm_output_queue[uuid].empty() is False:
484
+ top_ids = self.vllm_output_queue[uuid].get()
485
+ if top_ids in self.stop_token_ids:
486
+ break
487
+ # in stream mode, yield token one by one
488
+ yield top_ids
489
+ out_tokens.append(top_ids)
490
+ if len(out_tokens) == max_len:
491
+ break
492
+ time.sleep(0.001)
493
+ with self.lock:
494
+ self.vllm_output_queue.pop(uuid)
495
+ else:
496
+ out_tokens = []
497
+ cache = None
498
+ for i in range(max_len):
499
+ y_pred, cache = self.llm.forward_one_step(lm_input,
500
+ masks=torch.tril(torch.ones((1, lm_input.shape[1], lm_input.shape[1]), device=lm_input.device)).to(torch.bool),
501
+ cache=cache)
502
+ logp = self.llm_decoder(y_pred[:, -1]).log_softmax(dim=-1)
503
+ top_ids = self.sampling_ids(logp.squeeze(dim=0), out_tokens, sampling, ignore_eos=True if i < min_len else False).item()
504
+ if top_ids == self.speech_token_size:
505
+ break
506
+ if top_ids > self.speech_token_size:
507
+ continue
508
+ # in stream mode, yield token one by one
509
+ yield top_ids
510
+ out_tokens.append(top_ids)
511
+ lm_input = self.speech_embedding.weight[top_ids].reshape(1, 1, -1)
512
+
513
+ @torch.inference_mode()
514
+ def inference_bistream(
515
+ self,
516
+ text: Generator,
517
+ prompt_text: torch.Tensor,
518
+ prompt_text_len: torch.Tensor,
519
+ prompt_speech_token: torch.Tensor,
520
+ prompt_speech_token_len: torch.Tensor,
521
+ embedding: torch.Tensor,
522
+ sampling: int = 25,
523
+ max_token_text_ratio: float = 20,
524
+ min_token_text_ratio: float = 2,
525
+ ) -> Generator[torch.Tensor, None, None]:
526
+
527
+ device = prompt_text.device
528
+ # 1. prepare input
529
+ sos_eos_emb = self.llm_embedding.weight[self.sos_eos].reshape(1, 1, -1)
530
+ task_id_emb = self.llm_embedding.weight[self.task_id].reshape(1, 1, -1)
531
+ if prompt_speech_token_len != 0:
532
+ prompt_speech_token_emb = self.speech_embedding(prompt_speech_token)
533
+ else:
534
+ prompt_speech_token_emb = torch.zeros(1, 0, self.llm_input_size, dtype=prompt_text.dtype).to(device)
535
+ lm_input = torch.concat([sos_eos_emb], dim=1)
536
+
537
+ # 2. iterate text
538
+ out_tokens = []
539
+ cache = None
540
+ # NOTE init prompt_text as text_cache as it is basically impossible prompt_speech_token/prompt_text < 15/5
541
+ text_cache = self.llm.model.model.embed_tokens(prompt_text)
542
+ next_fill_index = -1
543
+ for this_text in text:
544
+ text_cache = torch.concat([text_cache, self.llm.model.model.embed_tokens(this_text)], dim=1)
545
+ # prompt_speech_token_emb not empty, try append to lm_input
546
+ while prompt_speech_token_emb.size(1) != 0:
547
+ if text_cache.size(1) >= self.mix_ratio[0]:
548
+ lm_input_text, lm_input_speech = text_cache[:, :self.mix_ratio[0]], prompt_speech_token_emb[:, :self.mix_ratio[1]]
549
+ logging.info('append {} text token {} speech token'.format(lm_input_text.size(1), lm_input_speech.size(1)))
550
+ lm_input = torch.concat([lm_input, lm_input_text, lm_input_speech], dim=1)
551
+ text_cache, prompt_speech_token_emb = text_cache[:, self.mix_ratio[0]:], prompt_speech_token_emb[:, self.mix_ratio[1]:]
552
+ else:
553
+ logging.info('not enough text token to decode, wait for more')
554
+ break
555
+ # no prompt_speech_token_emb remain, can decode some speech token
556
+ if prompt_speech_token_emb.size(1) == 0:
557
+ if (len(out_tokens) != 0 and out_tokens[-1] == self.speech_token_size + 2) or (len(out_tokens) == 0 and lm_input.size(1) == 1):
558
+ logging.info('get fill token, need to append more text token')
559
+ if text_cache.size(1) >= self.mix_ratio[0]:
560
+ lm_input_text = text_cache[:, :self.mix_ratio[0]]
561
+ logging.info('append {} text token'.format(lm_input_text.size(1)))
562
+ if len(out_tokens) != 0 and out_tokens[-1] == self.speech_token_size + 2:
563
+ lm_input = lm_input_text
564
+ else:
565
+ lm_input = torch.concat([lm_input, lm_input_text], dim=1)
566
+ text_cache = text_cache[:, self.mix_ratio[0]:]
567
+ else:
568
+ logging.info('not enough text token to decode, wait for more')
569
+ continue
570
+ while True:
571
+ seq_len = lm_input.shape[1] if cache is None else lm_input.shape[1] + cache[0][0].size(2)
572
+ y_pred, cache = self.llm.forward_one_step(lm_input,
573
+ masks=torch.tril(torch.ones((1, seq_len, seq_len), device=lm_input.device)).to(torch.bool),
574
+ cache=cache)
575
+ logp = self.llm_decoder(y_pred[:, -1]).log_softmax(dim=-1)
576
+ if next_fill_index != -1 and len(out_tokens) == next_fill_index:
577
+ top_ids = self.speech_token_size + 2
578
+ next_fill_index += (self.mix_ratio[1] + 1)
579
+ else:
580
+ top_ids = self.sampling_ids(logp.squeeze(dim=0), out_tokens, sampling, ignore_eos=True).item()
581
+ if top_ids == self.speech_token_size + 2:
582
+ next_fill_index = len(out_tokens) + self.mix_ratio[1] + 1
583
+ logging.info('fill_token index {} next fill_token index {}'.format(len(out_tokens), next_fill_index))
584
+ out_tokens.append(top_ids)
585
+ if top_ids >= self.speech_token_size:
586
+ if top_ids == self.speech_token_size + 2:
587
+ break
588
+ else:
589
+ raise ValueError('should not get token {}'.format(top_ids))
590
+ yield top_ids
591
+ lm_input = self.speech_embedding.weight[top_ids].reshape(1, 1, -1)
592
+
593
+ # 3. final decode
594
+ lm_input = torch.concat([lm_input, text_cache, task_id_emb], dim=1)
595
+ logging.info('no more text token, decode until met eos')
596
+ while True:
597
+ seq_len = lm_input.shape[1] if cache is None else lm_input.shape[1] + cache[0][0].size(2)
598
+ y_pred, cache = self.llm.forward_one_step(lm_input,
599
+ masks=torch.tril(torch.ones((1, seq_len, seq_len), device=lm_input.device)).to(torch.bool),
600
+ cache=cache)
601
+ logp = self.llm_decoder(y_pred[:, -1]).log_softmax(dim=-1)
602
+ top_ids = self.sampling_ids(logp.squeeze(dim=0), out_tokens, sampling, ignore_eos=False).item()
603
+ out_tokens.append(top_ids)
604
+ if top_ids >= self.speech_token_size:
605
+ if top_ids == self.speech_token_size:
606
+ break
607
+ else:
608
+ raise ValueError('should not get token {}'.format(top_ids))
609
+ # in stream mode, yield token one by one
610
+ yield top_ids
611
+ lm_input = self.speech_embedding.weight[top_ids].reshape(1, 1, -1)
cosyvoice/tokenizer/__pycache__/tokenizer.cpython-310.pyc ADDED
Binary file (7.87 kB). View file
 
cosyvoice/tokenizer/assets/multilingual_zh_ja_yue_char_del.tiktoken ADDED
The diff for this file is too large to render. See raw diff
 
cosyvoice/tokenizer/tokenizer.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+ from functools import lru_cache
4
+ from typing import Optional
5
+ import torch
6
+ from transformers import AutoTokenizer
7
+ from whisper.tokenizer import Tokenizer
8
+
9
+ import tiktoken
10
+
11
+ LANGUAGES = {
12
+ "en": "english",
13
+ "zh": "chinese",
14
+ "de": "german",
15
+ "es": "spanish",
16
+ "ru": "russian",
17
+ "ko": "korean",
18
+ "fr": "french",
19
+ "ja": "japanese",
20
+ "pt": "portuguese",
21
+ "tr": "turkish",
22
+ "pl": "polish",
23
+ "ca": "catalan",
24
+ "nl": "dutch",
25
+ "ar": "arabic",
26
+ "sv": "swedish",
27
+ "it": "italian",
28
+ "id": "indonesian",
29
+ "hi": "hindi",
30
+ "fi": "finnish",
31
+ "vi": "vietnamese",
32
+ "he": "hebrew",
33
+ "uk": "ukrainian",
34
+ "el": "greek",
35
+ "ms": "malay",
36
+ "cs": "czech",
37
+ "ro": "romanian",
38
+ "da": "danish",
39
+ "hu": "hungarian",
40
+ "ta": "tamil",
41
+ "no": "norwegian",
42
+ "th": "thai",
43
+ "ur": "urdu",
44
+ "hr": "croatian",
45
+ "bg": "bulgarian",
46
+ "lt": "lithuanian",
47
+ "la": "latin",
48
+ "mi": "maori",
49
+ "ml": "malayalam",
50
+ "cy": "welsh",
51
+ "sk": "slovak",
52
+ "te": "telugu",
53
+ "fa": "persian",
54
+ "lv": "latvian",
55
+ "bn": "bengali",
56
+ "sr": "serbian",
57
+ "az": "azerbaijani",
58
+ "sl": "slovenian",
59
+ "kn": "kannada",
60
+ "et": "estonian",
61
+ "mk": "macedonian",
62
+ "br": "breton",
63
+ "eu": "basque",
64
+ "is": "icelandic",
65
+ "hy": "armenian",
66
+ "ne": "nepali",
67
+ "mn": "mongolian",
68
+ "bs": "bosnian",
69
+ "kk": "kazakh",
70
+ "sq": "albanian",
71
+ "sw": "swahili",
72
+ "gl": "galician",
73
+ "mr": "marathi",
74
+ "pa": "punjabi",
75
+ "si": "sinhala",
76
+ "km": "khmer",
77
+ "sn": "shona",
78
+ "yo": "yoruba",
79
+ "so": "somali",
80
+ "af": "afrikaans",
81
+ "oc": "occitan",
82
+ "ka": "georgian",
83
+ "be": "belarusian",
84
+ "tg": "tajik",
85
+ "sd": "sindhi",
86
+ "gu": "gujarati",
87
+ "am": "amharic",
88
+ "yi": "yiddish",
89
+ "lo": "lao",
90
+ "uz": "uzbek",
91
+ "fo": "faroese",
92
+ "ht": "haitian creole",
93
+ "ps": "pashto",
94
+ "tk": "turkmen",
95
+ "nn": "nynorsk",
96
+ "mt": "maltese",
97
+ "sa": "sanskrit",
98
+ "lb": "luxembourgish",
99
+ "my": "myanmar",
100
+ "bo": "tibetan",
101
+ "tl": "tagalog",
102
+ "mg": "malagasy",
103
+ "as": "assamese",
104
+ "tt": "tatar",
105
+ "haw": "hawaiian",
106
+ "ln": "lingala",
107
+ "ha": "hausa",
108
+ "ba": "bashkir",
109
+ "jw": "javanese",
110
+ "su": "sundanese",
111
+ "yue": "cantonese",
112
+ "minnan": "minnan",
113
+ "wuyu": "wuyu",
114
+ "dialect": "dialect",
115
+ "zh/en": "zh/en",
116
+ "en/zh": "en/zh",
117
+ }
118
+
119
+ # language code lookup by name, with a few language aliases
120
+ TO_LANGUAGE_CODE = {
121
+ **{language: code for code, language in LANGUAGES.items()},
122
+ "burmese": "my",
123
+ "valencian": "ca",
124
+ "flemish": "nl",
125
+ "haitian": "ht",
126
+ "letzeburgesch": "lb",
127
+ "pushto": "ps",
128
+ "panjabi": "pa",
129
+ "moldavian": "ro",
130
+ "moldovan": "ro",
131
+ "sinhalese": "si",
132
+ "castilian": "es",
133
+ "mandarin": "zh",
134
+ }
135
+
136
+ AUDIO_EVENT = {
137
+ "ASR": "ASR",
138
+ "AED": "AED",
139
+ "SER": "SER",
140
+ "Speech": "Speech",
141
+ "/Speech": "/Speech",
142
+ "BGM": "BGM",
143
+ "/BGM": "/BGM",
144
+ "Laughter": "Laughter",
145
+ "/Laughter": "/Laughter",
146
+ "Applause": "Applause",
147
+ "/Applause": "/Applause",
148
+ }
149
+
150
+ EMOTION = {
151
+ "HAPPY": "HAPPY",
152
+ "SAD": "SAD",
153
+ "ANGRY": "ANGRY",
154
+ "NEUTRAL": "NEUTRAL",
155
+ }
156
+
157
+ TTS_Vocal_Token = {
158
+ "TTS/B": "TTS/B",
159
+ "TTS/O": "TTS/O",
160
+ "TTS/Q": "TTS/Q",
161
+ "TTS/A": "TTS/A",
162
+ "TTS/CO": "TTS/CO",
163
+ "TTS/CL": "TTS/CL",
164
+ "TTS/H": "TTS/H",
165
+ **{f"TTS/SP{i:02d}": f"TTS/SP{i:02d}" for i in range(1, 14)}
166
+ }
167
+
168
+
169
+ @lru_cache(maxsize=None)
170
+ def get_encoding(name: str = "gpt2", num_languages: int = 99):
171
+ vocab_path = os.path.join(os.path.dirname(__file__), "assets", f"{name}.tiktoken")
172
+ ranks = {
173
+ base64.b64decode(token): int(rank)
174
+ for token, rank in (line.split() for line in open(vocab_path) if line)
175
+ }
176
+ n_vocab = len(ranks)
177
+ special_tokens = {}
178
+
179
+ specials = [
180
+ "<|endoftext|>",
181
+ "<|startoftranscript|>",
182
+ *[f"<|{lang}|>" for lang in list(LANGUAGES.keys())[:num_languages]],
183
+ *[f"<|{audio_event}|>" for audio_event in list(AUDIO_EVENT.keys())],
184
+ *[f"<|{emotion}|>" for emotion in list(EMOTION.keys())],
185
+ "<|translate|>",
186
+ "<|transcribe|>",
187
+ "<|startoflm|>",
188
+ "<|startofprev|>",
189
+ "<|nospeech|>",
190
+ "<|notimestamps|>",
191
+ *[f"<|SPECIAL_TOKEN_{i}|>" for i in range(1, 31)], # register special tokens for ASR
192
+ *[f"<|{tts}|>" for tts in list(TTS_Vocal_Token.keys())], # register special tokens for TTS
193
+ *[f"<|{i * 0.02:.2f}|>" for i in range(1501)],
194
+ ]
195
+
196
+ for token in specials:
197
+ special_tokens[token] = n_vocab
198
+ n_vocab += 1
199
+
200
+ return tiktoken.Encoding(
201
+ name=os.path.basename(vocab_path),
202
+ explicit_n_vocab=n_vocab,
203
+ pat_str=r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""",
204
+ mergeable_ranks=ranks,
205
+ special_tokens=special_tokens,
206
+ )
207
+
208
+
209
+ @lru_cache(maxsize=None)
210
+ def get_tokenizer(
211
+ multilingual: bool,
212
+ *,
213
+ num_languages: int = 99,
214
+ language: Optional[str] = None,
215
+ task: Optional[str] = None, # Literal["transcribe", "translate", None]
216
+ ) -> Tokenizer:
217
+ if language is not None:
218
+ language = language.lower()
219
+ if language not in LANGUAGES:
220
+ if language in TO_LANGUAGE_CODE:
221
+ language = TO_LANGUAGE_CODE[language]
222
+ else:
223
+ raise ValueError(f"Unsupported language: {language}")
224
+
225
+ if multilingual:
226
+ encoding_name = "multilingual_zh_ja_yue_char_del"
227
+ language = language or "en"
228
+ task = task or "transcribe"
229
+ else:
230
+ encoding_name = "gpt2"
231
+ language = None
232
+ task = None
233
+
234
+ encoding = get_encoding(name=encoding_name, num_languages=num_languages)
235
+
236
+ return Tokenizer(
237
+ encoding=encoding, num_languages=num_languages, language=language, task=task
238
+ )
239
+
240
+
241
+ class QwenTokenizer():
242
+ def __init__(self, token_path, skip_special_tokens=True):
243
+ super().__init__()
244
+ # NOTE: non-chat model, all these special tokens keep randomly initialized.
245
+ special_tokens = {
246
+ 'eos_token': '<|endoftext|>',
247
+ 'pad_token': '<|endoftext|>',
248
+ 'additional_special_tokens': [
249
+ '<|im_start|>', '<|im_end|>', '<|endofprompt|>',
250
+ '[breath]', '<strong>', '</strong>', '[noise]',
251
+ '[laughter]', '[cough]', '[clucking]', '[accent]',
252
+ '[quick_breath]',
253
+ "<laughter>", "</laughter>",
254
+ "[hissing]", "[sigh]", "[vocalized-noise]",
255
+ "[lipsmack]", "[mn]"
256
+ ]
257
+ }
258
+ self.special_tokens = special_tokens
259
+ self.tokenizer = AutoTokenizer.from_pretrained(token_path)
260
+ self.tokenizer.add_special_tokens(special_tokens)
261
+ self.skip_special_tokens = skip_special_tokens
262
+
263
+ def encode(self, text, **kwargs):
264
+ tokens = self.tokenizer([text], return_tensors="pt")
265
+ tokens = tokens["input_ids"][0].cpu().tolist()
266
+ return tokens
267
+
268
+ def decode(self, tokens):
269
+ tokens = torch.tensor(tokens, dtype=torch.int64)
270
+ text = self.tokenizer.batch_decode([tokens], skip_special_tokens=self.skip_special_tokens)[0]
271
+ return text
272
+
273
+
274
+ @lru_cache(maxsize=None)
275
+ def get_qwen_tokenizer(
276
+ token_path: str,
277
+ skip_special_tokens: bool
278
+ ) -> QwenTokenizer:
279
+ return QwenTokenizer(token_path=token_path, skip_special_tokens=skip_special_tokens)
cosyvoice/transformer/__init__.py ADDED
File without changes
cosyvoice/transformer/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (164 Bytes). View file
 
cosyvoice/transformer/__pycache__/activation.cpython-310.pyc ADDED
Binary file (2.49 kB). View file