Prompt48 commited on
Commit
298e6e1
·
verified ·
1 Parent(s): c7e10ba

Upload edit\Qwen3-TTS-test\finetuning\dataset.py with huggingface_hub

Browse files
edit//Qwen3-TTS-test//finetuning//dataset.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2026 The Alibaba Qwen team.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ from typing import Any, List, Tuple, Union
17
+
18
+ import librosa
19
+ import numpy as np
20
+ import torch
21
+ from qwen_tts.core.models.configuration_qwen3_tts import Qwen3TTSConfig
22
+ from qwen_tts.core.models.modeling_qwen3_tts import mel_spectrogram
23
+ from torch.utils.data import Dataset
24
+
25
+ AudioLike = Union[
26
+ str, # wav path, URL, base64
27
+ np.ndarray, # waveform (requires sr)
28
+ Tuple[np.ndarray, int], # (waveform, sr)
29
+ ]
30
+
31
+ MaybeList = Union[Any, List[Any]]
32
+
33
+ class TTSDataset(Dataset):
34
+ def __init__(self, data_list, processor, config:Qwen3TTSConfig, lag_num = -1):
35
+ self.data_list = data_list
36
+ self.processor = processor
37
+ self.lag_num = lag_num
38
+ self.config = config
39
+
40
+ def __len__(self):
41
+ return len(self.data_list)
42
+
43
+ def _load_audio_to_np(self, x: str) -> Tuple[np.ndarray, int]:
44
+
45
+ audio, sr = librosa.load(x, sr=None, mono=True)
46
+
47
+ if audio.ndim > 1:
48
+ audio = np.mean(audio, axis=-1)
49
+
50
+ return audio.astype(np.float32), int(sr)
51
+
52
+ def _normalize_audio_inputs(self, audios: Union[AudioLike, List[AudioLike]]) -> List[Tuple[np.ndarray, int]]:
53
+ """
54
+ Normalize audio inputs into a list of (waveform, sr).
55
+
56
+ Supported forms:
57
+ - str: wav path / URL / base64 audio string
58
+ - np.ndarray: waveform (NOT allowed alone here because sr is unknown)
59
+ - (np.ndarray, sr): waveform + sampling rate
60
+ - list of the above
61
+
62
+ Args:
63
+ audios:
64
+ Audio input(s).
65
+
66
+ Returns:
67
+ List[Tuple[np.ndarray, int]]:
68
+ List of (float32 waveform, original sr).
69
+
70
+ Raises:
71
+ ValueError: If a numpy waveform is provided without sr.
72
+ """
73
+ if isinstance(audios, list):
74
+ items = audios
75
+ else:
76
+ items = [audios]
77
+
78
+ out: List[Tuple[np.ndarray, int]] = []
79
+ for a in items:
80
+ if isinstance(a, str):
81
+ out.append(self._load_audio_to_np(a))
82
+ elif isinstance(a, tuple) and len(a) == 2 and isinstance(a[0], np.ndarray):
83
+ out.append((a[0].astype(np.float32), int(a[1])))
84
+ elif isinstance(a, np.ndarray):
85
+ raise ValueError("For numpy waveform input, pass a tuple (audio, sr).")
86
+ else:
87
+ raise TypeError(f"Unsupported audio input type: {type(a)}")
88
+ return out
89
+
90
+
91
+ def _build_assistant_text(self, text: str) -> str:
92
+ return f"<|im_start|>assistant\n{text}<|im_end|>\n<|im_start|>assistant\n"
93
+
94
+ def _ensure_list(self, x: MaybeList) -> List[Any]:
95
+ return x if isinstance(x, list) else [x]
96
+
97
+ def _tokenize_texts(self, text) -> List[torch.Tensor]:
98
+ input = self.processor(text=text, return_tensors="pt", padding=True)
99
+ input_id = input["input_ids"]
100
+ input_id = input_id.unsqueeze(0) if input_id.dim() == 1 else input_id
101
+ return input_id
102
+
103
+ @torch.inference_mode()
104
+ def extract_mels(self, audio, sr):
105
+ assert sr == 24000, "Only support 24kHz audio"
106
+ mels = mel_spectrogram(
107
+ torch.from_numpy(audio).unsqueeze(0),
108
+ n_fft=1024,
109
+ num_mels=128,
110
+ sampling_rate=24000,
111
+ hop_size=256,
112
+ win_size=1024,
113
+ fmin=0,
114
+ fmax=12000
115
+ ).transpose(1, 2)
116
+ return mels
117
+
118
+
119
+
120
+ def __getitem__(self, idx):
121
+ item = self.data_list[idx]
122
+
123
+ audio_path = item["audio"]
124
+ text = item["text"]
125
+ audio_codes = item["audio_codes"]
126
+ language = item.get('language','Auto')
127
+ ref_audio_path = item['ref_audio']
128
+
129
+ text = self._build_assistant_text(text)
130
+ text_ids = self._tokenize_texts(text)
131
+
132
+ audio_codes = torch.tensor(audio_codes, dtype=torch.long)
133
+
134
+ ref_audio_list = self._ensure_list(ref_audio_path)
135
+ normalized = self._normalize_audio_inputs(ref_audio_list)
136
+ wav,sr = normalized[0]
137
+
138
+ ref_mel = self.extract_mels(audio=wav, sr=sr)
139
+
140
+ return {
141
+ "text_ids": text_ids[:,:-5], # 1 , t
142
+ "audio_codes":audio_codes, # t, 16
143
+ "ref_mel":ref_mel
144
+ }
145
+
146
+ def collate_fn(self, batch):
147
+ assert self.lag_num == -1
148
+
149
+ item_length = [b['text_ids'].shape[1] + b['audio_codes'].shape[0] for b in batch]
150
+ max_length = max(item_length) + 8
151
+ b,t = len(batch),max_length
152
+
153
+ input_ids = torch.zeros((b,t,2),dtype=torch.long)
154
+ codec_ids = torch.zeros((b,t,16),dtype=torch.long)
155
+ text_embedding_mask = torch.zeros((b,t),dtype=torch.bool)
156
+ codec_embedding_mask = torch.zeros((b,t),dtype=torch.bool)
157
+ codec_mask = torch.zeros((b,t),dtype=torch.bool)
158
+ attention_mask = torch.zeros((b,t),dtype=torch.long)
159
+ codec_0_labels = torch.full((b, t), -100, dtype=torch.long)
160
+
161
+ for i,data in enumerate(batch):
162
+ text_ids = data['text_ids']
163
+ audio_codec_0 = data['audio_codes'][:,0]
164
+ audio_codecs = data['audio_codes']
165
+
166
+ text_ids_len = text_ids.shape[1]
167
+ codec_ids_len = audio_codec_0.shape[0]
168
+
169
+ # text channel
170
+ input_ids[i, :3, 0] = text_ids[0,:3]
171
+ input_ids[i, 3:7, 0] = self.config.tts_pad_token_id
172
+ input_ids[i, 7, 0] = self.config.tts_bos_token_id
173
+ input_ids[i, 8:8+text_ids_len-3, 0] = text_ids[0,3:]
174
+ input_ids[i, 8+text_ids_len-3, 0] = self.config.tts_eos_token_id
175
+ input_ids[i, 8+text_ids_len-2:8+text_ids_len+codec_ids_len , 0] = self.config.tts_pad_token_id
176
+ text_embedding_mask[i, :8+text_ids_len+codec_ids_len] = True
177
+
178
+ # codec channel
179
+ # input_ids[i, :3, 1] = 0
180
+ input_ids[i, 3:8 ,1] = torch.tensor(
181
+ [
182
+ self.config.talker_config.codec_nothink_id,
183
+ self.config.talker_config.codec_think_bos_id,
184
+ self.config.talker_config.codec_think_eos_id,
185
+ 0, # for speaker embedding
186
+ self.config.talker_config.codec_pad_id
187
+ ]
188
+ )
189
+ input_ids[i, 8:8+text_ids_len-3 ,1] = self.config.talker_config.codec_pad_id
190
+ input_ids[i, 8+text_ids_len-3 ,1] = self.config.talker_config.codec_pad_id
191
+ input_ids[i, 8+text_ids_len-2 ,1] = self.config.talker_config.codec_bos_id
192
+ input_ids[i, 8+text_ids_len-1:8+text_ids_len-1+codec_ids_len, 1] = audio_codec_0
193
+ input_ids[i, 8+text_ids_len-1+codec_ids_len, 1] = self.config.talker_config.codec_eos_token_id
194
+
195
+ codec_0_labels[i, 8+text_ids_len-1:8+text_ids_len-1+codec_ids_len] = audio_codec_0
196
+ codec_0_labels[i, 8+text_ids_len-1+codec_ids_len] = self.config.talker_config.codec_eos_token_id
197
+
198
+ codec_ids[i, 8+text_ids_len-1:8+text_ids_len-1+codec_ids_len,:] = audio_codecs
199
+
200
+ codec_embedding_mask[i, 3:8+text_ids_len+codec_ids_len] = True
201
+ codec_embedding_mask[i, 6] = False # for speaker embedding
202
+
203
+ codec_mask[i, 8+text_ids_len-1:8+text_ids_len-1+codec_ids_len] = True
204
+ attention_mask[i, :8+text_ids_len+codec_ids_len] = True
205
+
206
+ ref_mels = [data['ref_mel'] for data in batch]
207
+ ref_mels = torch.cat(ref_mels,dim=0)
208
+
209
+ return {
210
+ 'input_ids':input_ids,
211
+ 'ref_mels':ref_mels,
212
+ 'attention_mask':attention_mask,
213
+ 'text_embedding_mask':text_embedding_mask.unsqueeze(-1),
214
+ 'codec_embedding_mask':codec_embedding_mask.unsqueeze(-1),
215
+ 'codec_0_labels':codec_0_labels,
216
+ 'codec_ids': codec_ids,
217
+ 'codec_mask':codec_mask
218
+ }