初めに
LFM2.5-Audio-1.5B-JP は、音声入力に対してテキストと音声を interleaved に生成できる日本語 speech-to-speech(s2s)会話モデルです。
前回の記事では、このモデルに LoRA で ずんだもん口調を追加学習しました。ただし前回の口調のみ版は assistant の教師を text-only にしていたため、声色はほぼベースモデルのままでした。
今回はその続きとして、VOICEVOX ずんだもん音声を教師にして、口調だけでなく声もずんだもん寄りにするところまで進めます。
デモ
開発環境
| 項目 | バージョン / 構成 |
|---|---|
| OS | Windows 11 |
| GPU | RTX 4070 Ti SUPER 16GB |
| パッケージ管理 | uv |
| Python | 3.12(>=3.12,<3.13 固定) |
| torch / torchaudio | CUDA 12.8 wheel |
| liquid-audio | 1.3.0 |
| peft | 0.19.1 |
| ruff | dev dependency として導入 |
| VOICEVOX ENGINE | ローカル API(http://127.0.0.1:50021) |
Windows では Triton の公式ビルドがないため、Mimi decode 周りで torch.compile を使うと落ちます。前回の口調のみ版と同じく、liquid_audio import より前に以下を設定して eager 実行に落とします。
import os os.environ.setdefault("NO_TORCH_COMPILE", "1") os.environ.setdefault("NO_CUDA_GRAPH", "1") os.environ.setdefault("PYTHONUTF8", "1") os.environ.setdefault("PYTHONIOENCODING", "utf-8")
口調のみ版から声込み版で何を変えたか
口調のみ版と声込み版の差分は、assistant の教師データです。
| 項目 | 口調のみ版 | 声込み版 |
|---|---|---|
| 目的 | ずんだもん口調だけを学習 | ずんだもん口調 + ずんだもん寄りの声を学習 |
| assistant 教師 | TextSegment(text=ztext) |
InterleavedSegment(text=ztext, audio=VOICEVOX音声) |
| audio_out 損失 | 0 | 有効 |
| 教師音声 | 不要 | VOICEVOX ずんだもん speaker=3 |
| LoRA target | backbone 基線 | backbone 基線 + 音声側 target |
| 主な評価 | 口調率・応答妥当性 | 口調率・崩壊有無・話者類似度・音声品質 |
口調のみ版では、以下のように assistant を text-only にしていました。
ChatMessage(role="assistant", content=[TextSegment(text=ztext)])
この場合、audio_out は空なので、モデルは音声 codebook を教師として見ません。つまり、返答テキストはずんだもん口調になりますが、声はベースモデルの声のままです。
声込み版ではここを次の形に変えます。
ChatMessage(
role="assistant",
content=[InterleavedSegment(text=ztext, audio=zundamon_voicevox_wav)],
)
InterleavedSegment に同一テキストの VOICEVOX 音声を入れると、mapper が Mimi で audio_out を 8 codebook にエンコードし、assistant 側の text/audio 両方に supervision がかかります。これで、ずんだもん口調とずんだもん寄りの声を同時に学習できます。
重要なのは、InterleavedSegment.text と VOICEVOX 合成元テキストを必ず一致させることです。テキストと音声が違うと、同じ assistant 区間で「この文字列」と「別内容の音声 token」を同時に当てに行くことになり、学習が壊れます。
データ作成
声込み版のデータ作成は、前回の QA/ずんだ口調データに VOICEVOX 教師音声を追加する形です。
1) VOICEVOX ずんだもん音声の合成
data/synth_zundamon.py で、各行の zundamon_text を VOICEVOX ずんだもん speaker=3 で合成します。出力は Mimi と合わせて 24kHz mono WAV にします。
| 項目 | 内容 |
|---|---|
| VOICEVOX 音声キャッシュ | data/zundamon_audio |
| 音声規模 | 2095 wav / 約 4.814 時間 / 平均 8.27 秒 |
| 音声教師データ | 1982 件 |
| mapper | LFM2AudioChatMapper(codebooks=8, interleaved_text_tokens=6, interleaved_audio_tokens=9) |
| context | 512 |
声込み版の 1 サンプルは以下の 3 メッセージ構成です。
samples.append([
ChatMessage(role="system", content=[TextSegment(text=SYSTEM_ZUNDA)]),
ChatMessage(role="user", content=[AudioSegment(audio=in_bytes)]),
ChatMessage(role="assistant", content=[
InterleavedSegment(text=ztext, audio=z_bytes)
]),
])
汎用会話リプレイ行は前回と同じく text-only にして、声を汚さず会話能力の忘却を抑える方針にしています。
assistant_seg = TextSegment(text=replay)
学習
学習スクリプトは口調のみ版と同じ train/train_zundamon_lora.py を使います。口調のみ版では text_only=True で audio_loss_multiplier=0 にしていましたが、声込み版では --allow-audio-loss を付けて audio_out 損失を有効化します。
最終採用モデルの構成は以下です。
| 項目 | 値 |
|---|---|
| 採用モデル | checkpoints/zundamon_m2_v4/best |
| データ | data/dataset_m2/arrow_train_v4 / arrow_val_v4 |
| LoRA rank / alpha | r=96 / alpha=192 |
| target | q_proj,k_proj,v_proj,out_proj,w1,w2,w3,depth_linear,to_logits |
| dropout | 0.05 |
| semantic codebook factor | 1 |
| best val_loss | 2.3147 @ step4000 |
| adapter size | 約 287MB |
実行イメージは以下です。
$env:PYTHONUTF8="1"
$env:PYTHONIOENCODING="utf-8"
$env:NO_TORCH_COMPILE="1"
$env:NO_CUDA_GRAPH="1"
uv run --no-sync python -u train/train_zundamon_lora.py `
--train-data data/dataset_m2/arrow_train_v4 `
--val-data data/dataset_m2/arrow_val_v4 `
--output checkpoints/zundamon_m2_v4 `
--allow-audio-loss `
--semantic-codebook-factor 1 `
--lora-r 96 `
--lora-alpha 192 `
--lora-targets q_proj,k_proj,v_proj,out_proj,w1,w2,w3,depth_linear,to_logits `
--max-steps 4000
TTS 評価は以下です。
| tag | WavLM cos to ref | delta vs base | gap closed | log-mel cos to ref |
|---|---|---|---|---|
| base | 0.5798 ± 0.0985 | +0.0000 | +0.0% | 0.9652 |
| 音声追加学習モデル | 0.9054 ± 0.0189 | +0.3256 | +75.8% | 0.9888 |
実行すると前回と同じく以下のような画面が起動します 話しかけることでずんだもんの口調およびボイスで返信が返ってきます
