初めに
LFM2.5-Audio-1.5B-JP は、音声入力に対して「新しい返答内容」を音声で返す日本語の speech-to-speech(s2s)会話モデルです。このモデルに LoRA で「ずんだもん口調」を追加学習しました。今回はデータ作成から学習・評価・音声対話デモまでを記録します。
今回学習したのは 口調(テキスト)だけで、返答の声はほぼベースモデルのままです
開発環境
| 項目 | バージョン / 構成 |
|---|---|
| OS | Windows 11 |
| GPU | RTX 4070 Ti SUPER 16GB |
| パッケージ管理 | uv 0.11.8 |
| Python | 3.12(>=3.12,<3.13 固定) |
| torch / torchaudio | 2.11.0+cu128 |
| liquid-audio | 1.3.0(JP 対応) |
| transformers | 5.10.2 |
| peft | 0.19.1 |
| accelerate | 1.13.0 |
環境構築
[project] requires-python = ">=3.12,<3.13" dependencies = [ "liquid-audio[demo]>=1.0.0", "torch>=2.8.0", "torchaudio>=2.8.0", "soundfile>=0.12.1", ] # torch / torchaudio は CUDA 12.8 ビルドを PyTorch 公式インデックスから取得する [tool.uv.sources] torch = [{ index = "pytorch-cu128" }] torchaudio = [{ index = "pytorch-cu128" }] [[tool.uv.index]] name = "pytorch-cu128" url = "https://download.pytorch.org/whl/cu128" explicit = true
以下で実行します
# 依存を一括取得(初回は CUDA 版 torch 約 2.6GB を取りに行く) uv sync
peft の入れ方
LoRA に使う peft を uv プロジェクトに足すとき、uv run --with peft は禁止です。overlay が別バージョンの torch を引き込み、プロジェクトの torchaudio(libtorchaudio.pyd)と ABI が合わなくなって OSError: Could not load this library で import に失敗します。
正解は、既存の torch を再利用して 1 パッケージだけ入れることです。
# NG: uv run --with peft ... ← torch 差し替えで torchaudio が ABI 破壊 uv pip install peft # 既存 torch を再利用、peft==0.19.1 uv run --no-sync python ... # --no-sync が無いと lockfile 同期で peft が消える
uv pip install は --no-sync を受け付けません(--no-sync は uv run 専用フラグ)。この非対称さに最初ハマりました。
Windows の Triton 回避
音声生成を実行すると torch._inductor.exc.TritonMissing で落ちます。原因は、Mimi(音声トークナイザ)のデコード経路が既定で torch.compile + CUDA グラフを使い、内部で Triton を要求するのに、Triton には Windows 公式ビルドが無いことです。
対処は、liquid_audio を import する前に環境変数を立てて eager 実行に落とすことです。これらはデコレータ適用時(import 時)に読まれるため、import より前に設定しないと効きません。あわせて、端末の cp932 文字化け対策とログのバッファ対策(python -u 相当)も定型で付けておきます。
import os # Windows には公式 Triton ビルドが無いため、Mimi decode を eager にする os.environ.setdefault("NO_TORCH_COMPILE", "1") os.environ.setdefault("NO_CUDA_GRAPH", "1") os.environ.setdefault("PYTHONUTF8", "1") # 端末 cp932 の文字化け対策 os.environ.setdefault("PYTHONIOENCODING", "utf-8") os.environ.setdefault("PYTHONUNBUFFERED", "1") # stdout バッファでハング誤認を防ぐ # ↑ この後で import する from liquid_audio import LFM2AudioModel, LFM2AudioProcessor
以降、実行コマンドはこれらの環境変数プレフィックスを付けて回します。
データ作成
今回学習するのは「質問音声 → ずんだもん口調の回答テキスト」のペアです。HuggingFace に既存の口調化済みデータ(takaaki-inada/databricks-dolly-15k-ja-zundamon, CC BY-SA 3.0, 15,015 件)があるので、これを採用します。
学習
公式の liquid_audio.trainer.Trainer はフルFT 専用で LoRA コードがありません。そこで Trainer をサブクラス化し、__init__ だけ上書きして「from_pretrained の後・AdamW の前」に get_peft_model を注入します。train() / log() は親から継承し、grad-accum を入れる train_step() だけ上書きする方針です。
LORA_TARGETS = ["q_proj", "k_proj", "v_proj", "out_proj", "w1", "w2", "w3"] lora_cfg = LoraConfig( r=16, lora_alpha=32, lora_dropout=0.05, bias="none", target_modules=LORA_TARGETS, # task_type は渡さない ) model = get_peft_model(model, lora_cfg) # AdamW の前に注入
本走は以下のコマンドで回します。
$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/out/arrow_train --val-data data/out/arrow_val `
--output checkpoints/zundamon_m1b
評価
評価方法は、質問を edge-tts で音声化(学習と同経路)→ generate_sequential でテキストのみ生成(assistant に音声を持たせていないため)です。デコードは再現性重視で greedy、system は学習と同一のずんだもんペルソナ、同一質問を adapter ON(best) / OFF(base) で対比します(OOD のみ base も生成)。口調率は data/quality_gate.tone_eval を再利用、内容・安全性は LLM-judge(Claude Opus 4.8)で判定しました(数値は eval/results/report.md のまま)。
$env:PYTHONUTF8="1"; $env:NO_TORCH_COMPILE="1"; $env:NO_CUDA_GRAPH="1" uv run --no-sync python eval/eval_zundamon_s2s.py
口調率(自動指標)
| セット | tone_ok | 率 |
|---|---|---|
| in-domain | 26/30 | 86.7% |
| OOD-知識 | 27/30 | 90.0% |
| OOD-指示 | 14/20 | 70.0% |
| OOD-安全 | 7/8 | 87.5% |
| 全体 | 74/88 | 84.1% |
口調は学習分布外(OOD 知識)にも強く般化しました。今回の目標(口調の付与)は達成です。OOD 指示が低いのは、箇条書き/リスト形式で各項目が「のだ」で終わらないこと、一部の指示 echo・ループが原因です。
音声対話デモ
uv run --no-sync python demos/demo_zundamon_realtime.py # リアルタイム連続対話
起動するとコンソールに http://127.0.0.1:7860(使用中なら 7861 を自動選択)が出るので、ブラウザで開きます。停止は Ctrl+C です。
