esnya/japanese_speecht5_ttsを動かして音声合成を行う

初めに

前に英語版を動かしてみました。今回は日本語の追加学習モデルのesnya/japanese_speecht5_ttsが出ていたので,こちらを動かしていきます

ayousanz.hatenadiary.jp

以下にて記事の内容をリポジトリで公開しています。

github.com

開発環境

環境作成

まずは仮想環境を作成します

uv venv -p 3.11
source venv/bin/activate

注意 この記事では,OpenJTalkのPythonのラッパーライブラリとして pyopenjtalk-plusを使用しています。そのため,python は3.11以降でしか動きません

次に関連するライブラリを入れます cudaは入っていないので,cpu版を入れていきます

uv pip install transformers==4.29.2 sentencepiece torch soundfile accelerate pyopenjtalk-plus

注意

  1. transformersのversionは,モデルカードのサンプルカードで最新版では削除されているPRETRAINED_POSITIONAL_EMBEDDINGS_SIZESの定数を使っているため,4.29.2 をインストールします

  2. 上記に記載した通りOpenJTalkPythonのラッパーライブラリは,pyopenjtalk-plusを使用しています。

推論の準備

日本語の推論をするため,OpenJTalkのtokenizerのコードを公式モデルの付属しているものをダウンロードします

curl -O https://huggingface.co/esnya/japanese_speecht5_tts/resolve/main/speecht5_openjtalk_tokenizer.py

推論

macのM系チップでは,畳み込み層の出力チャネルが 65536 を超える場合に発生するバグがあるため CPUにて推論を行います

以下が該当のissueです

github.com

以下が推論コードです

import numpy as np
from transformers import (
    SpeechT5ForTextToSpeech,
    SpeechT5HifiGan,
    SpeechT5FeatureExtractor,
    SpeechT5Processor,
)
from speecht5_openjtalk_tokenizer import SpeechT5OpenjtalkTokenizer
import soundfile
import torch

# MPS が利用可能なら "mps"、なければ "cpu" を使用(今回はモデル本体は MPS/CPU で動作)
device = torch.device("mps" if torch.backends.mps.is_available() else "cpu")

# vocoder を CPU で実行するためのラッパークラス
class VocoderCPUWrapper(torch.nn.Module):
    def __init__(self, vocoder):
        super().__init__()
        self.vocoder = vocoder  # この vocoder は全パラメータが CPU 上にある必要があります
    def forward(self, x):
        # 入力を CPU に移動してから vocoder を実行
        x_cpu = x.cpu()
        return self.vocoder(x_cpu)

model_name = "esnya/japanese_speecht5_tts"
with torch.no_grad():
    # SpeechT5ForTextToSpeech を float32 でロードし、device に移動
    model = SpeechT5ForTextToSpeech.from_pretrained(
        model_name, torch_dtype=torch.float32
    )
    model.to(device)

    tokenizer = SpeechT5OpenjtalkTokenizer.from_pretrained(model_name)
    feature_extractor = SpeechT5FeatureExtractor.from_pretrained(model_name)
    processor = SpeechT5Processor(feature_extractor, tokenizer)
    
    # SpeechT5HifiGan (vocoder) は MPS の制限があるため、明示的に CPU にロード
    vocoder = SpeechT5HifiGan.from_pretrained(
        "microsoft/speecht5_hifigan", torch_dtype=torch.float32
    )
    vocoder.to("cpu")  # ここで CPU に移動
    vocoder_wrapper = VocoderCPUWrapper(vocoder)

    input_text = "吾輩は猫である。名前はまだ無い。どこで生れたかとんと見当がつかぬ。"
    # processor により input_ids を生成し、device に転送
    input_ids = processor(text=input_text, return_tensors="pt").input_ids.to(device)

    speaker_embeddings = np.random.uniform(-1, 1, (1, 16))
    speaker_embeddings = torch.FloatTensor(speaker_embeddings).to(device=device, dtype=model.dtype)

    # generate_speech 呼び出し時に vocoder_wrapper を指定
    waveform = model.generate_speech(
        input_ids,
        speaker_embeddings,
        vocoder=vocoder_wrapper,
    )

    waveform = waveform / waveform.abs().max()  # 正規化
    waveform = waveform.reshape(-1).cpu().float().numpy()

    soundfile.write(
        "output.wav",
        waveform,
        vocoder.config.sampling_rate,
    )