axinc-ai / ailia-models

The collection of pre-trained, state-of-the-art AI models for ailia SDK
2.04k stars 325 forks source link

ADD GPT-SoVITS #1404

Closed kyakuno closed 4 months ago

kyakuno commented 8 months ago

最新のText2Speechモデル。VALLEXのように0ショットに対応している。 https://github.com/RVC-Boss/GPT-SoVITS mit 日本語対応

kyakuno commented 8 months ago

モデルファイル https://huggingface.co/lj1995/GPT-SoVITS/tree/main

kyakuno commented 8 months ago

python3.9が必要、python3.11だとnumbaが入らない

kyakuno commented 8 months ago

macの場合

pip3.9 install -r requirements.txt
pip3.9 uninstall torch torchaudio
pip3.9 install --pre torch torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu
python3.9 webui.py
kyakuno commented 8 months ago

ONNXへの変換スクリプトがある https://github.com/RVC-Boss/GPT-SoVITS/blob/main/GPT_SoVITS/onnx_export.py

kyakuno commented 8 months ago

uvr5のweightを使用した場合にMDXNetDereverbが呼ばれてONNXが使用されるが、 GPT_SoVITSのONNXの推論コードは含まれていない気がする。

kyakuno commented 8 months ago

使用方法 https://taziku.co.jp/blog/gpt-sovits-training

kyakuno commented 8 months ago

UIを開く

スクリーンショット 2024-02-25 19 44 50

0ショットで推論

スクリーンショット 2024-02-25 19 48 51

参照音声。 RVCを使用したボイスチェンジャーを作るための元データとなる音声収録のテストを行なっています。

生成音声。 こんにちは。今日はAIについて解説します。

0ショットだと精度が低いのでFineTuningは必要そう。

kyakuno commented 8 months ago

FineTuningのためのデータセット作成

スクリーンショット 2024-02-25 19 56 54

音声ファイルを入力して分割後、ASRでテキストを生成できる。 音声ファイルはoutputs/slicer_optに、テキストファイルはoutputs/asr_optに格納される。

kyakuno commented 8 months ago

ASRをfaster whisperにするとjaも選択できる。

kyakuno commented 8 months ago

macOSだとfatster whisperが例外で落ちるので、ファイルの分割だけWebUIを使用し、手動でlstを作る。

vocal_path|speaker_name|language|text
kyakuno commented 8 months ago

作ったlst。

output/slicer_opt/kyakuno.wav_0000644800_0000964160.wav|slicer_opt|JA| RVCを使用したボイスチェンジャーを作るための元データとなる音声収録のテストを行なっています。30秒のデータを使用してどれくらい音声
output/slicer_opt/kyakuno.wav_0000406080_0000644800.wav|slicer_opt|JA| 変換が成功するのかどうかを試すために、とりあえず30秒の録音データを作成しています。
output/slicer_opt/kyakuno.wav_0000007360_0000406080.wav|slicer_opt|JA| RVCのバックエンドはPytorchで構築されており、Pytorchを使って学習をします。その際にF0を取得します。
kyakuno commented 8 months ago

データセット設定、トークン抽出 スクリーンショット 2024-02-25 20 21 22

学習 スクリーンショット 2024-02-25 20 21 28

kyakuno commented 8 months ago

macOSだと学習に非常に時間がかかるのでWindows + Conda + RTX3080に移行。

kyakuno commented 8 months ago

BLOG https://medium.com/axinc/gpt-sovits-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%B3%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%A7%E3%81%8D%E3%82%8B0%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%81%AE%E9%9F%B3%E5%A3%B0%E5%90%88%E6%88%90%E3%83%A2%E3%83%87%E3%83%AB-2212eeb5ad20

kyakuno commented 8 months ago

推論UIは下記のコマンドで単独で起動できる。

python3 GPT_SoVITS/inference_webui.py

inference_webui.pyを書き換えて、下記のようにすると、CUIからwavを推論できる。

inp_ref = "JSUT.wav"
prompt_text = "水をマレーシアから買わなくてはならない。"
prompt_language = i18n("日文")
text = "水は、いりませんか?"
text_language = i18n("日文")
how_to_cut = i18n("凑四句一切")
top_k = 5
top_p = 1
temperature = 1
ref_text_free = False
output = get_tts_wav(inp_ref, prompt_text, prompt_language, text, text_language, how_to_cut, top_k, top_p, temperature, ref_text_free)
# get_tts_wavのyieldをreturnに書き換える
print(output)
import soundfile as sf
sf.write("x.wav", output[1], output[0])
kyakuno commented 8 months ago

get_tts_wavは下記の3モデル構成。

ssl_model -> ssl_content
t2s_model -> pred_semantic
vq_model -> audio
kyakuno commented 8 months ago

GPT_SoVITS/onnx_export.pyにONNXへのエクスポートコードがある。 モデルパスを下記のように修正すると動く。

#cnhubert_base_path = "pretrained_models/chinese-hubert-base"

import os
cnhubert_base_path = os.environ.get(
    "cnhubert_base_path", "GPT_SoVITS/pretrained_models/chinese-hubert-base"
)

gpt_path = "GPT_SoVITS/pretrained_models/s1bert25hz-2kh-longer-epoch=68e-step=50232.ckpt"#"GPT_weights/nahida-e25.ckpt"
vits_path = "GPT_SoVITS/pretrained_models/s2G488k.pth"#"SoVITS_weights/nahida_e30_s3930.pth"
exp_path = "nahida"
kyakuno commented 8 months ago

デフォルトだとノイズをrefにするので、下記をコメントアウトする必要がある。

ref_audio = torch.tensor([load_audio("JSUT.wav", 48000)]).float()
kyakuno commented 8 months ago

エクスポート後にコード中のdebug = Trueにすると、onnxruntimeで推論できる。

kyakuno commented 8 months ago

phonesは下記のように与える。

    ref_seq = torch.LongTensor([cleaned_text_to_sequence(['m', 'i', 'z', 'u', 'o', 'm', 'a', 'r', 'e', 'e', 'sh', 'i', 'a', 'k', 'a', 'r', 'a', 'k', 'a', 'w', 'a', 'n', 'a', 'k', 'U', 't', 'e', 'w', 'a', 'n', 'a', 'r', 'a', 'n', 'a', 'i', '.'])])
    text_seq = torch.LongTensor([cleaned_text_to_sequence(['m', 'i', 'z', 'u', 'w', 'a', ',', 'i', 'r', 'i', 'm', 'a', 's', 'e', 'N', 'k', 'a', '?'])])
kyakuno commented 8 months ago

出力されるONNXファイル

-rw-r--r--  1 kyakuno  staff   11035654  3 18 14:30 nahida_t2s_encoder.onnx
-rw-r--r--  1 kyakuno  staff  307511198  3 18 14:30 nahida_t2s_fsdec.onnx
-rw-r--r--  1 kyakuno  staff  307574675  3 18 14:31 nahida_t2s_sdec.onnx
-rw-r--r--  1 kyakuno  staff  162706799  3 18 14:32 nahida_vits.onnx
kyakuno commented 8 months ago

推論コードを書けば動きそうな気もする。

kyakuno commented 8 months ago

debug引数はGptSoVitsにしか回ってきていないので、T2SModelには手動で実装が必要。

kyakuno commented 8 months ago

下記でONNX Runtimeで推論できた。

    def forward(self, ref_seq, text_seq, ref_bert, text_bert, ssl_content, debug=False):
        early_stop_num = self.t2s_model.early_stop_num

        if debug:
            import onnxruntime
            sess_encoder = onnxruntime.InferenceSession(f"onnx/nahida/nahida_t2s_encoder.onnx", providers=["CPU"])
            sess_fsdec = onnxruntime.InferenceSession(f"onnx/nahida/nahida_t2s_fsdec.onnx", providers=["CPU"])
            sess_sdec = onnxruntime.InferenceSession(f"onnx/nahida/nahida_t2s_sdec.onnx", providers=["CPU"])

        #[1,N] [1,N] [N, 1024] [N, 1024] [1, 768, N]
        if debug:
            x, prompts = sess_encoder.run(None, {"ref_seq":ref_seq.detach().numpy(), "text_seq":text_seq.detach().numpy(), "ref_bert":ref_bert.detach().numpy(), "text_bert":text_bert.detach().numpy(), "ssl_content":ssl_content.detach().numpy()})
            x = torch.from_numpy(x)
            prompts = torch.from_numpy(prompts)
        else:
            x, prompts = self.onnx_encoder(ref_seq, text_seq, ref_bert, text_bert, ssl_content)

        prefix_len = prompts.shape[1]

        #[1,N,512] [1,N]
        if debug:
            y, k, v, y_emb, x_example = sess_fsdec.run(None, {"x":x.detach().numpy(), "prompts":prompts.detach().numpy()})
            y = torch.from_numpy(y)
            k = torch.from_numpy(k)
            v = torch.from_numpy(v)
            y_emb = torch.from_numpy(y_emb)
            x_example = torch.from_numpy(x_example)
        else:
            y, k, v, y_emb, x_example = self.first_stage_decoder(x, prompts)

        stop = False
        for idx in range(1, 1500):
            #[1, N] [N_layer, N, 1, 512] [N_layer, N, 1, 512] [1, N, 512] [1] [1, N, 512] [1, N]
            if debug:
                y, k, v, y_emb, logits, samples = sess_sdec.run(None, {"iy":y.detach().numpy(), "ik":k.detach().numpy(), "iv":v.detach().numpy(), "iy_emb":y_emb.detach().numpy(), "ix_example":x_example.detach().numpy()})
                y = torch.from_numpy(y)
                k = torch.from_numpy(k)
                v = torch.from_numpy(v)
                y_emb = torch.from_numpy(y_emb)
                logits = torch.from_numpy(logits)
                samples = torch.from_numpy(samples)
            else:
                enco = self.stage_decoder(y, k, v, y_emb, x_example)
                y, k, v, y_emb, logits, samples = enco
            if early_stop_num != -1 and (y.shape[1] - prefix_len) > early_stop_num:
                stop = True
            if torch.argmax(logits, dim=-1)[0] == self.t2s_model.EOS or samples[0, 0] == self.t2s_model.EOS:
                stop = True
            if stop:
                break
        y[0, -1] = 0

        return y[:, -idx:].unsqueeze(0)
kyakuno commented 8 months ago

入力音声の特徴抽出は、cnhubert.pyではWav2Vec2FeatureExtractor -> Hubertを使用しているように見えるが、上層で、CNHubertのmodelのみを参照しているため、Wav2Vec2FeatureExtractorを使用していない。

kyakuno commented 8 months ago

hubertの入力:torch.Size([1, 51041]) hubertの出力:torch.Size([1, 768, 159])

kyakuno commented 8 months ago

エクスポートコードをコミット https://github.com/axinc-ai/GPT-SoVITS

kyakuno commented 8 months ago

all_jaだと、1文節をまとめてphonesに変換する。 jaだと、1文節のうちで、enとjaを切り替えながらphonesに変換する。

kyakuno commented 8 months ago

モデルファイルをアップロード https://storage.googleapis.com/ailia-models/gpt-sovits/nahida_t2s_encoder.onnx など

kyakuno commented 8 months ago

cnhubrtのモデルサイズ。 -rw-r--r--@ 1 kyakuno staff 377745020 3 18 18:47 nahida_cnhubert.onnx

kyakuno commented 8 months ago

language == "zh"の場合のみ、get_bert_featureを使用しており、そうでない場合はzerosを使用している。

kyakuno commented 8 months ago

samplingのtopKの設定はnahida_t2s_sdec.onnxに埋め込まれている。

スクリーンショット 2024-03-19 11 10 16

kyakuno commented 8 months ago

onnx : top_k=5, top_p=1.0, repetition_penalty=1.35 (t2s_model_onnx.py) (repetition_penalty sampling) webui : top_k=5, top_p=1.0 (ts2_model.py) (top_p sampling)

kyakuno commented 8 months ago

onnxの出力が不安定な気がしていて、下記を変更してみたが影響がなかった。

def sample(
    logits,
    previous_tokens,
    **sampling_kwargs,
):
    if True:
        top_k = 5
        top_p = 1.0
        temperature = 1.0
        if temperature != 1.0:
            logits = logits / temperature
        logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p)
        token = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1)
        return token, logits

    probs = logits_to_probs(
        logits=logits, previous_tokens=previous_tokens, **sampling_kwargs
    )
    idx_next = multinomial_sample_one_no_sync(probs)
    return idx_next, probs
kyakuno commented 8 months ago

webuiはhubertの入力の末尾に0.3秒の0をpaddingしているが、onnxはしていない。 ただ、そこまで差はない。

kyakuno commented 8 months ago

t2_model.pyのinfer_panelだとtopk_samplingではなくsampleを使っているので、実はonnxとロジックが一緒。

kyakuno commented 8 months ago

torchとonnxの出力不一致の要因を探っていく。onnxに変換する前の、onnx向けのtorchの段階で不一致があるので、torchの実装の差分を探る。

torch

------- 0
logits tensor([-6.2715, -5.9433, -1.8790,  ..., -6.6660, -6.5220, -5.5481])
samples tensor([[280]], dtype=torch.int32)
------- 1
logits tensor([-5.9058, -5.1973, -2.5371,  ..., -6.9879, -5.3853,  2.1045])
samples tensor([[875]], dtype=torch.int32)
------- 2
logits tensor([ -7.4869,  -5.0362,  -2.7423,  ..., -10.3586,  -7.4642,  -3.2918])
samples tensor([[457]], dtype=torch.int32)

onnx

------- 0
logits tensor([-6.2075, -6.0403, -1.7061,  ..., -6.3708, -5.6906,  2.0591],
samples tensor([[280]], dtype=torch.int32)
------- 1
logits tensor([-5.9783, -5.1318, -2.4765,  ..., -6.9966, -5.6614,  1.4082],
samples tensor([[53]], dtype=torch.int32)
------- 2
logits tensor([ -9.3376,  -5.3020,  -3.0583,  ..., -10.7543,  -6.6286,  -0.4839],
samples tensor([[29]], dtype=torch.int32)
kyakuno commented 8 months ago

topkとtoppを1にすると決定論的になるはず https://zenn.dev/hellorusk/articles/1c0bef15057b1d

kyakuno commented 8 months ago

topkとtoppを1にすると収束しない。

kyakuno commented 8 months ago

torchの方は初回推論時はEOSを無視するので、末尾の数値が一致しない。

kyakuno commented 8 months ago

入力音声をhubertで変換したssl_contentとprompt_semanticは完全一致している。

kyakuno commented 8 months ago

T2SModel (gptによるトークンと音声の潜在表現変換)のOnnxEncoderのbert_projまでは合っていて、ar_text_positionの出力が合わない。

kyakuno commented 8 months ago

SinePositionalEmbeddingのpe_tensorの値が合わない。

kyakuno commented 8 months ago

SinePositionalEmbeddingのpeはsin, cos, sin, cosであるべきで、torchだと  pe tensor([[[ 0.0000e+00, 1.0000e+00, 0.0000e+00, ..., 1.0000e+00, 0.0000e+00, 1.0000e+00], だが、onnxだと  pe tensor([[[ 8.4147e-01, 5.4030e-01, 8.2186e-01, ..., 1.0000e+00, 1.0366e-04, 1.0000e+00], になっており、sin, cos, sin, cosになっていなさそうな印象がある。

kyakuno commented 8 months ago

first stage decodeでtorchには存在する  logits = logits[:, :-1] ###刨除1024终止符号的概率 がonnxにはないので、topPとtopKの計算が変わり、結果の不一致が起きる。

torch

------- 0
logits.shape torch.Size([1024])
logits tensor([-6.2715, -5.9433, -1.8790,  ..., -6.6660, -6.5220, -5.5481])
samples tensor([[280]], dtype=torch.int32)
------- 1
logits.shape torch.Size([1025])
logits tensor([-5.9058, -5.1973, -2.5371,  ..., -6.9879, -5.3853,  2.1045])
samples tensor([[105]], dtype=torch.int32)
------- 2
logits.shape torch.Size([1025])
logits tensor([-7.3637, -4.6229, -2.0885,  ..., -8.7286, -6.8107,  2.7965])
samples tensor([[29]], dtype=torch.int32)

onnx

------- 0
logits.shape torch.Size([1025])
logits tensor([-6.2715, -5.9433, -1.8790,  ..., -6.5220, -5.5481,  2.5339],
       grad_fn=<SelectBackward0>)
samples tensor([[486]], dtype=torch.int32)
------- 1
logits.shape torch.Size([1025])
logits tensor([-6.2932, -5.9584, -1.9278,  ..., -6.5744, -5.5434,  2.6196],
       grad_fn=<SelectBackward0>)
samples tensor([[280]], dtype=torch.int32)
------- 2
logits.shape torch.Size([1025])
logits tensor([-5.9299, -5.2080, -2.5925,  ..., -7.0680, -5.4103,  2.1573],
       grad_fn=<SelectBackward0>)
samples tensor([[105]], dtype=torch.int32)

これを入れると、トークン生成までは完全一致するようになる。

onnx

------- 0
logits.shape torch.Size([1024])
logits tensor([-6.2715, -5.9433, -1.8790,  ..., -6.6660, -6.5220, -5.5481],
       grad_fn=<SelectBackward0>)
samples tensor([[280]], dtype=torch.int32)
------- 1
logits.shape torch.Size([1025])
logits tensor([-5.9058, -5.1973, -2.5371,  ..., -6.9879, -5.3853,  2.1045],
       grad_fn=<SelectBackward0>)
samples tensor([[105]], dtype=torch.int32)
------- 2
logits.shape torch.Size([1025])
logits tensor([-7.3637, -4.6229, -2.0885,  ..., -8.7286, -6.8107,  2.7965],
       grad_fn=<SelectBackward0>)
samples tensor([[271]], dtype=torch.int32)
kyakuno commented 8 months ago

sampleのmultinomial_sample_one_no_syncにも差分がある。

torch

q = torch.empty_like(probs_sort).exponential_(1)

onnx

q = torch.random_like(probs_sort)
kyakuno commented 8 months ago

pred_semanticの末尾にonnxは0を入れているが、torchには入っていない。

kyakuno commented 8 months ago

vq_decodeにnoise_scaleが入っていない。

kyakuno commented 8 months ago

torch版とonnx版でほぼ同じ精度が出るようになった。最終的な差分は下記の4つ。 ・sampleのmultinomial_sample_one_no_sync ・SinePositionalEmbeddingのpe ・vq_decodeのnoise_scale ・first_stage_decodeのEOSの除去

kyakuno commented 8 months ago

あとはコード整理したらtorch依存を除去できそう。