生成 AI のサービスには画像や効果音、音楽を作る物がありますが、同じことが手元の GPU でもできるかを、余っていた Radeon RX 7900 XT で技術検証しました。その中で、生成の最初の1回だけが数分止まったように見えることがあり、私たちも一度この遅さをハングと誤診しました。生成のジョブに時間の上限を置くときも、どの値を基準にするかを誤ると、正常なジョブを止めてしまいます。
結論
- 最初の1回が遅いのは、hipBLASLt と MIOpen がカーネルを自動調整しているためです。GPU が動いていればハングではありません。
- 調整の結果は利用者ごとにキャッシュされ、解像度を変えるとやり直しになります。使う解像度は固定します。
- MOSS-SoundEffect v2.0 は、キャッシュがあってもプロセスごとに約 90 秒かかります。一組の効果音は1つのプロセスでまとめて作ります。
- ジョブの時間の上限は、冷たいキャッシュ(初めての利用者・初めての解像度)の値から作ります。温かい値から作った 300 秒の上限に、初回の 283.1 秒が迫りました。
試した環境
- GPU: AMD Radeon RX 7900 XT(gfx1100、VRAM 20 GiB)
- OS と ROCm: Ubuntu、ROCm 7.2.4
- ライブラリ: torch 2.13.0+rocm7.2
- モデル: FLUX.2-klein-4B(画像)、MOSS-SoundEffect v2.0(効果音)、ACE-Step 1.5 turbo(音楽)、Qwen-Image-Edit-2511(画像編集)、Qwen3-VL-8B(画像を見る)
- 生成は、検証の中で組んだ、ジョブを並べて GPU で1本ずつ回す仕組みから、サービス用の利用者で走らせました
初回と2回目以降はこれだけ違う
FLUX.2-klein-4B で画像を作ったときの所要です。「新しい解像度の初回」のほかは 1024² です。
キャッシュが空の最初の1回は 274.7 秒、2回目以降は 10.6〜16.7 秒でした。いちど調整が済めば、後で別のプロセスを起こしても、同じ解像度なら初回は 29.3 秒です。ところが解像度を変えると、その解像度で調整がやり直しになり、220.5 秒かかりました。
ほかのモデルでも、初回だけが長くなります。
| モデル | 初回 | 2回目以降 |
|---|---|---|
| MOSS-SoundEffect v2.0 | 125.7 秒(プロセスの中の初回) | 35.2 秒 |
| ACE-Step 1.5 turbo | 123.9〜143.9 秒 | 45 秒の曲を 37.9 秒 |
| Qwen-Image-Edit-2511 | 1枚目 197 秒(8 手)/133 秒(4 手) | Lightning 8 手で 140〜145 秒/枚 |
GPU が動いていれば、ハングではありません。ただ GPU の使用率だけでは見分けにくいので、後の節の3つの指標を合わせて見ます。1 step に何十秒もかかり、GPU の使用率が 0% のまま時間だけ経つなら、自動調整ではなく別の原因を疑います(MOSS-SoundEffect では torch.compile が重く、1 step に 70 秒かかりました)。

キャッシュは利用者ごとに溜まる
私たちの環境で見た、キャッシュの置き場です。どれも利用者の HOME の下にあります。
| 中身 | 置き場 |
|---|---|
| 自動調整の結果(find-db) | ~/.config/miopen |
| コンパイル済みのカーネル | ~/.cache/miopen |
| comgr のキャッシュ | ~/.cache/comgr |
そのため、生成を走らせる利用者を新しく作ると、また最初から払います。次のグラフは、サービス用の利用者を新しく作ったあと、各モデルを1回ずつ流したときの値です(ジョブの開始から終了まで)。
画像 1024² の 283.1 秒のうち、描画が 274.9 秒でした。別の利用者で初めて回したときも、FLUX.2-klein-4B の 512² が 52.5 秒、MOSS-SoundEffect の 2 秒・100 手が 316.7 秒かかっています。
サービス用の利用者の HOME はモデル用のディスクに作り、キャッシュがそこに溜まるようにしました。利用者のあいだでキャッシュを共有する案(MIOPEN_USER_DB_PATH・MIOPEN_CUSTOM_CACHE_DIR・AMD_COMGR_CACHE_DIR を共有の置き場へ向ける)も考えましたが、やめました。得をするのは人が手で生成を打つときだけで、acl を入れて既定 ACL の置き場を作る手間に見合わないと判断しました。
一組はまとめて作る
MOSS-SoundEffect は、2回目以降が 35.2 秒でも、プロセスを起こすたびに約 90 秒(MIOpen の自動調整)を払います。キャッシュがあっても消えないので、一組の効果音は1つのプロセスでまとめて作ります。
画像でも、1枚ずつプロセスを起こすのは損でした。画像のモデルを1枚ずつ呼んだ別の計測では、1枚 13〜24 秒のうち描画は 3〜4 秒で、残りは重みの積み直しでした。一組の画像は、重みを1回だけ読んでまとめて作ります。
時間の上限は冷たい値から作る
検証で組んだジョブの仕組みには、ハングしたジョブを止めるための時間の上限を持たせました。最初は、この上限を温かい値から作っていました。同じ解像度で後のプロセスの初回にかかる 29.3 秒をもとにした、300 秒です。
新しいサービス用の利用者で最初に画像 1024² を流すと 283.1 秒かかり、上限まで 17 秒しか残りませんでした。あと少し遅ければ、正常なジョブを止めるところでした。そこで初回の費用を、冷たいキャッシュ・新しい解像度で測った値に置き直し、下限も付けました。
上限 = 初回の費用 + 1件の目安 × 件数 × 3(ただし 15 分より短くしない)
初回の費用 = 冷たいキャッシュ・新しい解像度で測った値
上限を超えたら、取り消しと同じ手順(SIGTERM を送り、15 秒待って SIGKILL)で止めます。上限はハングに備えた安全弁なので、広めに取ります。一方、完了までの目安(ETA)は、温かい値のままにしました。
止まったかどうかは3つの指標で見る
初回は数分止まって見えるので、1つの指標だけでは見分けにくくなります。そこで3つを合わせて見ます。
GPU の使用率は amdgpu の gpu_busy_percent、CPU 時間は /proc/<pid>/stat の utime と stime の和で取り、3つがそろった状態が 5 分続いたら警告を出すようにしました。警告だけにして、止めはしませんでした。重みを読み込んでいるあいだは、窓を初回の費用の 1.5 倍に広げました。
進捗を標準出力から拾うときに、1つ引っかかりました。TQDM_DISABLE=1 を渡しても diffusers の進捗バーは止まらず、\r で同じ行を書き換え続けます。ログを読む側は、\r も行の区切りとして扱う必要があります。
確かめていないこと
- キャッシュを利用者のあいだで共有したら、初回がどれだけ縮むか。hipBLASLt の自動調整は上の環境変数の対象外で、共有しても残る費用は切り分けていません。
- MOSS-SoundEffect が、キャッシュがあってもプロセスごとに約 90 秒払う理由。
- 新しいサービス用の利用者で、温まった後に2回目を測った値。温かい値は、以前に測った物を使っています。
おわりに
ROCm で最初の1回が止まって見えたら、GPU が動いているかを見てから待ってください。時間の上限を置くなら、初めての利用者・初めての解像度で一度測った値を基準にすると、正常なジョブを止めずに済みます。
※ ROCm・MIOpen・diffusers の振る舞いは執筆時点の版(ROCm 7.2.4、torch 2.13.0+rocm7.2)で見た物で、いまは変わっている可能性があります。


