Spaces:
Running
on
Zero
Running
on
Zero
| # Copyright (c) 2025 SparkAudio | |
| # 2025 Xinsheng Wang (w.xinshawn@gmail.com) | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| import torch | |
| import torch.nn as nn | |
| from typing import List, Tuple | |
| from sparktts.modules.fsq.residual_fsq import ResidualFSQ | |
| from sparktts.modules.speaker.ecapa_tdnn import ECAPA_TDNN_GLOB_c512 | |
| from sparktts.modules.speaker.perceiver_encoder import PerceiverResampler | |
| """ | |
| x-vector + d-vector | |
| """ | |
| class SpeakerEncoder(nn.Module): | |
| """ | |
| Args: | |
| input_dim (int): acoustic feature dimension | |
| out_dim (int): output dimension of x-vector and d-vector | |
| latent_dim (int): latent dimension before quantization | |
| token_num (int): sequence length of speaker tokens | |
| fsq_levels (List[int]): number of levels for each quantizer | |
| fsq_num_quantizers (int): number of quantizers | |
| Return: | |
| speaker_embs: (B, T2, out_dim) | |
| """ | |
| def __init__( | |
| self, | |
| input_dim: int = 100, | |
| out_dim: int = 512, | |
| latent_dim: int = 128, | |
| token_num: int = 32, | |
| fsq_levels: List[int] = [4, 4, 4, 4, 4, 4], | |
| fsq_num_quantizers: int = 1, | |
| ): | |
| super(SpeakerEncoder, self).__init__() | |
| self.speaker_encoder = ECAPA_TDNN_GLOB_c512( | |
| feat_dim=input_dim, embed_dim=out_dim | |
| ) | |
| self.perceiver_sampler = PerceiverResampler( | |
| dim=latent_dim, dim_context=512 * 3, num_latents=token_num | |
| ) | |
| self.quantizer = ResidualFSQ( | |
| levels=fsq_levels, | |
| num_quantizers=fsq_num_quantizers, | |
| dim=latent_dim, | |
| is_channel_first=True, | |
| quantize_dropout=False, | |
| ) | |
| self.project = nn.Linear(latent_dim * token_num, out_dim) | |
| def get_codes_from_indices(self, indices: torch.Tensor) -> torch.Tensor: | |
| zq = self.quantizer.get_codes_from_indices(indices.transpose(1, 2)) | |
| return zq.transpose(1, 2) | |
| def get_indices(self, mels: torch.Tensor) -> torch.Tensor: | |
| mels = mels.transpose(1, 2) | |
| x = self.perceiver_sampler(mels).transpose(1, 2) | |
| zq, indices = self.quantizer(x) | |
| return indices | |
| def forward(self, mels: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """ | |
| Args: | |
| mels: (B, D_mel, T1) | |
| Return: | |
| x_vector: (B, out_dim) | |
| d_vector: (B, out_dim) | |
| """ | |
| # mels = mels.transpose(1,2) | |
| x_vector, features = self.speaker_encoder(mels, True) | |
| x = self.perceiver_sampler(features.transpose(1, 2)).transpose(1, 2) | |
| zq, indices = self.quantizer(x) # zq: (B, latent_dim, T2, latent_dim) | |
| x = zq.reshape(zq.shape[0], -1) | |
| d_vector = self.project(x) | |
| return x_vector, d_vector | |
| def tokenize(self, mels: torch.Tensor) -> torch.Tensor: | |
| """tokenize the input mel spectrogram""" | |
| _, features = self.speaker_encoder(mels, True) | |
| x = self.perceiver_sampler(features.transpose(1, 2)).transpose(1, 2) | |
| zq, indices = self.quantizer(x) | |
| return indices | |
| def detokenize(self, indices: torch.Tensor) -> torch.Tensor: | |
| """detokenize the input indices to d-vector""" | |
| zq = self.quantizer.get_output_from_indices(indices.transpose(1, 2)).transpose(1, 2) | |
| x = zq.reshape(zq.shape[0], -1) | |
| d_vector = self.project(x) | |
| return d_vector | |
| if __name__ == "__main__": | |
| model = SpeakerEncoder( | |
| input_dim=100, | |
| latent_dim=128, | |
| token_num=32, | |
| fsq_levels=[4, 4, 4, 4, 4, 4], | |
| fsq_num_quantizers=1, | |
| ) | |
| mel = torch.randn(8, 200, 100) | |
| x_vector, d_vector = model(mel) | |
| print("x-vector shape", x_vector.shape) | |
| print("d-vector shape", d_vector.shape) | |
| indices = model.tokenize(mel) | |
| print("indices shape", indices.shape) | |
| d_vector_post = model.detokenize(indices) | |
| print("d-vector shape", d_vector_post.shape) | |
| if d_vector_post.all() == d_vector.all(): | |
| print("d-vector post and d-vector are the same") | |
| else: | |
| print("d-vector post and d-vector are different") | |
| num_params = sum(param.numel() for param in model.parameters()) | |
| print("{} M".format(num_params / 1e6)) |