日本語TTS「Irodori-TTS」をRyzen AI Max+ 395の内蔵GPUで動かす──Windows ROCmで4倍速になるまでに越えた5つの壁と、画面が真っ暗になった話

テクノロジー

170万円の自宅AI環境のうち、統合メモリ128GBのGMKtec EVO-X2(Ryzen AI Max+ 395)は、これまでローカルLLM担当でした。ところが最近、別の仕事が増えました。音声合成(TTS)です。

きっかけは、動画や読み上げに使う「自分好みの声」をローカルで作りたくなったこと。クラウドの音声APIは便利ですが、生成した音声を今後の実験(他モデルの学習データ)に使いたいので、ライセンスが明快なローカルモデルで揃えたかったのです。

最初に触ったのはAlibabaの多言語TTS「Qwen3-TTS」。ボイスデザイン(文章で声を作る)とボイスクローン(数秒の参照音声から声を写す)を一通り試せるアプリを、Claude Codeと一緒にStreamlitで組みました。ここまでは半日。

問題はその先です。「せっかくGPUがあるんだから使いたい」と思ったところから、AMD統合GPU特有の沼が始まりました。

この記事は環境構築編です。日本語特化TTS「Irodori-TTS」をWindows + ROCmで動かし、CPUの4倍速で推論・7.7倍速で学習できるようになるまでの記録と、途中でデスクトップが2〜3秒おきに真っ暗になった事故の話。同じRyzen AI Max(Strix Halo)ユーザーには、たぶん有益です。 Qwen3-TTSとIrodori-TTSのどちらが良いかを115票のブラインド聴き比べで決めた比較編は別記事にしました。

先に結論

左: 推論のRTF(音声1秒を作るのにかかる秒数)。右: 話者埋め込み学習の速度。どちらもCPUに対してGPU bf16が最速で、推論は約4倍、学習は約7.7倍になりました
左: 推論のRTF(音声1秒を作るのにかかる秒数)。右: 話者埋め込み学習の速度。どちらもCPUに対してGPU bf16が最速で、推論は約4倍、学習は約7.7倍になりました
CPU (fp32)GPU (bf16)倍率
参照なし生成(RTF)2.19〜2.370.54約4倍
ボイスクローン(RTF)2.48〜2.570.84約3倍
話者埋め込みの学習(秒/サンプル)2.710.35約7.7倍

RTF 0.54というのは「10秒の音声を5.4秒で作る」速さ。CPUだと23秒かかっていたので、体感がまるで違います。学習は1000ステップ(8,000サンプル)が46分。CPUなら6時間。公式レシピ相当の48,000サンプルでも、CPUの36時間が4〜5時間になり、一晩で回せる範囲に入りました。

ただし、これはTransformer本体だけをGPUに載せ、音声コーデックはCPUに残すハイブリッド構成での数字です。なぜそうなったかは後半で。

Qwen3-TTSはGPUの方が遅かった

順番に話します。まずQwen3-TTS。AMDは2026年時点でWindows向けにROCm 7.2対応のPyTorch(torch 2.9.1)を公式配布しており、torch.cuda.is_available()True を返します(ROCmでも呼び方は cuda です。慣れるまで気持ち悪い)。統合メモリは99.7GBがGPUから見えました。

ここまでは順調。ところが実測すると、GPUの方がCPUより2倍遅い。

Qwen3-TTS 1.7BCPUGPU (ROCm 7.2.1)
1文ずつ生成(RTF)5.0910.34
4文バッチ(RTF)1.588.63

Qwen3-TTSは自己回帰型、つまり音声トークンを1個ずつ順に生成します。1トークンごとに小さなGPUカーネルを何十個も起動するので、内蔵GPUではカーネル起動のオーバーヘッドが支配的になる。加えてWindows ROCmではFlash Attention / Memory-Efficient Attentionがまだ experimental で、警告を出しながら素のattentionで動きます。そういうわけで、Qwen3-TTSはCPUで運用することにしました(ここで一度、諦めています)。

Irodori-TTSに出会う

日本語の読み間違いや抑揚に不満があって探していたところ、Aratakoさんが公開しているIrodori-TTSを見つけました。日本語特化、0.8Bパラメータ、48kHz出力、MITライセンス。しかもRectified Flow DiT、つまり非自己回帰です。音声全体を40ステップの反復で一気に生成する方式で、1ステップが大きな行列積の塊。これは内蔵GPUと相性が良いはず、と踏んで再挑戦しました。

結果は冒頭の表の通りですが、そこまでに5つの壁がありました。

壁1: torch 2.10が要る。公式Windows ROCm版は2.9.1

Irodori-TTSは torch>=2.10 を要求します。AMD公式のWindows向けPyTorchは2.9.1で、満たせません。

解決策は、AMDがTheRockプロジェクトで公開しているnightly indexです。https://rocm.nightlies.amd.com/v2/gfx1151/ に、Radeon 8060S(アーキテクチャ名 gfx1151)向けの torch 2.10.0+rocm7.13.0a がありました。

pip install --pre --index-url https://rocm.nightlies.amd.com/v2/gfx1151/ "torch==2.10.*" "torchaudio==2.10.*"

nightly なので明日動く保証はありませんが、2026年5月2日ビルドで問題なく動いています。

壁2〜4: 依存関係がWindows ROCmを想定していない

Irodori-TTSはLinux + CUDA前提で作られています(責める気は全くありません、自然なことです)。Windows ROCm nightly のtorchには含まれていないものがあり、そこで次々と落ちます。

症状対処
2音声コーデック dacvae がPyPIに無いGitHubの特定コミットから直接インストール
3torch.distributed が無く、依存ライブラリ(audiotools)がimport時に dist.ReduceOp を参照して落ちるsitecustomize.py でダミーの ReduceOp を注入(単一プロセスでは使われない属性)
4torchaudio.loadtorchcodec を要求するが、ROCm版には無い音声の読み書きを soundfile に差し替えるパッチ
おまけsentencepiece<0.2 にPython 3.12用wheelが無い / 依存パッケージがprotobufを3.xに下げてStreamlitが起動しなくなる0.2系を使用 / protobufを再アップグレード

一つ一つは小さいのですが、全部を自力で調べて潰すのは、正直かなりしんどい。エラーメッセージから「torch.distributedがnightlyビルドに入っていない」→「でも実際には使われない属性参照だけ」→「sitecustomize.pyで補えばIrodori側のコードを一切変えずに済む」という判断を、深夜に一人でやる気力は私にはありません。この話は最後にもう一度書きます。

壁5: 画面が2〜3秒おきに真っ暗になる

依存関係が通り、いざGPUで生成。デスクトップ全体がブラックアウトし、2〜3秒後に再描画され、また真っ暗になる、を繰り返し始めました。TTSアプリのタブではなく、OSの画面全体です。

内蔵GPUは表示と計算を同じシリコンでやっているので、GPU側の異常がそのままデスクトップに出ます。dGPU機なら「計算が落ちた」で済むところが、統合メモリ機では「画面が死ぬ」になる。これはStrix Halo特有の怖さです。

原因を追うと、音声コーデック(DACVAE)の畳み込み層がMIOpen(AMD版cuDNN)で失敗していました。fp32ではカーネル起動失敗、bf16ではJITコンパイル失敗。しかも失敗したプロセスがドライバ内でハングし、タスクマネージャーからも殺せない。イベントログにTDR(Event 4101)は記録されておらず、ドライバリセットではなく、ハングしたカーネルがコンポジタを巻き込んでいるように見えました。

対処は割り切りました。Transformer本体はGPU、コーデックはCPUというハイブリッド構成にし、ROCm環境ではコーデックをGPUに置く選択肢自体をアプリから消しました。

コーデックをCPUに固定した状態で、GPU推論50秒・GPU学習70秒を連続で回して、画面の異常は再現せず。それ以降、数時間のGPU学習を何度も回していますが、一度も起きていません。

完成したアプリ。左のサイドバーに「コーデック: cpu(固定)── ROCmではGPUコーデックが画面のブラックアウトを引き起こすため」と書いてあります。自分への戒めです
完成したアプリ。左のサイドバーに「コーデック: cpu(固定)── ROCmではGPUコーデックが画面のブラックアウトを引き起こすため」と書いてあります。自分への戒めです

コーデックがCPUに残る代償は、ボイスクローンで参照音声のエンコードがCPUで走ること(RTF 0.54 → 0.84)。これは参照音声の潜在表現を一度計算して保存しておく「事前計算」機能で解消できました。

その後: 話者埋め込みの学習もGPUで

Irodori-TTSにはSpeaker Inversionという機能があります。複数のクリップから16トークンの話者埋め込みを学習し、以後は参照音声なしでその声を再現するもの。CPUでは1サンプル2.7秒、200ステップで18分かかっていたのが、GPU bf16では0.35秒。バッチ8で1000ステップを45分44秒で回せました。

話者学習タブ。GPU学習中はログを直書きし、100ステップごとにチェックポイントを保存。途中で止めても「続きから学習」で再開できます。長時間ジョブの進捗が見えないのは精神衛生上よくないので、ここは最初に作り直しました
話者学習タブ。GPU学習中はログを直書きし、100ステップごとにチェックポイントを保存。途中で止めても「続きから学習」で再開できます。長時間ジョブの進捗が見えないのは精神衛生上よくないので、ここは最初に作り直しました

bf16とfp32で音質の差は、私の耳では分かりませんでした。

人間がやったこと、Claudeがやったこと

作業人間(私)Claude Code
「GPUで速くならないか」「Irodoriを試そう」の判断提案
nightly indexの発見、依存関係5件の切り分けと回避策
ブラックアウトの目撃・報告(「画面全体がチラつく」)
原因の絞り込み(MIOpen / コーデック)とハイブリッド構成の実装
「本当にもう出ないか」の確認試験立ち会い実行
音質の判断(bf16に劣化があるか、クローンが似ているか)
進捗が見えない・チェックポイントが無いことへの指摘実装
Windowsで os.kill(pid, 0) が生存確認ではなくプロセスを殺すバグ混入も修正も

最後の行は正直に書きます。Claude Codeは、Linuxの常識をWindowsに持ち込んで、学習ジョブの生存確認のつもりでプロセスを殺すコードを書きました。私が「学習が勝手に止まる」と気づき、Claudeが調べて直しました。万能ではない。

それでも、この環境構築を人間だけでやるのはかなり厳しい、というのが率直な感想です。Windows ROCm nightlyという少数派の環境で、TTSモデルの依存関係を5層掘って、MIOpenの失敗をハイブリッド構成で回避する。一つ一つの情報はどこかに散らばっていますが、それを深夜に一人で繋ぐ体力は、五十路にはありません(断言

コーディングエージェントがあると「試す→落ちる→原因を読む→回避策を書く→再試行」のループが数分で回ります。私は判断と、耳と、目(画面が真っ暗になったのを見るのは人間の仕事です)を提供し、手数の多い部分はClaudeに任せる。この種のツールは便利どころか、もう必須だと思っています。

同じ構成の人へのメモ

  • torch 2.10系の入手:Radeon 8060S / 8050S(gfx1151)なら、TheRockのnightly indexから入る。--pre --index-url https://rocm.nightlies.amd.com/v2/gfx1151/
  • 無いもの前提で:torch.distributedtorchcodec は無い。前者は属性スタブ、後者は soundfile で逃げる
  • 畳み込みは小さく試す:音声コーデックやVAEなど畳み込みを多用するモデルをGPUに載せる前に、まず短い入力で。MIOpenで失敗すると、内蔵GPU機では画面ごと巻き込まれる
  • ハイブリッドで十分:Transformer部分だけGPU、畳み込み部分はCPU、でも4倍速い
  • 自己回帰は伸びない:LLM系やQwen3-TTSのような自己回帰型は内蔵GPUで速くならないことがある。非自己回帰(DiT系)は伸びる
  • 長時間ジョブの監視:ログの直書きと定期チェックポイントを最初に作る。後から欲しくなる

まとめ

  • Irodori-TTSはWindows + ROCm nightlyで動き、Ryzen AI Max+ 395の内蔵GPUで推論4倍・学習7.7倍
  • 壁は5つ。torchのバージョン、無いパッケージ3つ、そしてMIOpenでの画面ブラックアウト
  • 内蔵GPUでは「計算が落ちる」が「画面が死ぬ」になる。コーデックはCPUに残す
  • この手の環境構築は、コーディングエージェントと組むと現実的な時間で終わる

次回の比較編では、このIrodori-TTSとQwen3-TTSを、漢字の読み・自然さ・感情表現の3観点で、自動指標と115票のブラインド聴き比べで比べます。結論だけ先に言うと、私の耳は自分の思い込みに負けていました。

真っ暗な画面を見つめる五十路の夜は、正直、ちょっと不安だった。

コメント

タイトルとURLをコピーしました