生成AI実験室(30) RTX 5060 Ti 2枚化の記録(7)——2枚で焼いたら絵がもやになった。原因はGPU同士の順番。直したら8〜20ステップが全部完走、8ステップで1枚の2.13倍

生成AI実験室 第30回 2枚で焼いたら絵がもやに。原因はGPU同士の順番だった

第25回で、Windows上のComfyUIで16GBのGPU2枚にMiniMax H3の仕事を分ける経路(H3VM)が動き、15秒が45分34秒で完走した話を書きました。その後、同じ経路で焼いた動画の絵が「もや」になりました。処理は成功扱い、ファイルも保存される、でも中身が崩れている。今回はその原因を2か所突き止めて直し、同じ入力・同じseedで8〜20ステップの7本を焼き直した記録です。

目次

症状: 成功しているのに、絵が崩れている

修正前の2GPU・8ステップ(もや状に崩れた絵)、修正後の2GPU・8ステップ、1枚(H3VMなし)・8ステップの5秒時点の比較
左: 修正前の2GPU・8ステップ(成功扱いだが、もや状)。中: 修正後。右: H3VMなし(1枚)。いずれも5秒時点

左が修正前の2GPU・8ステップ。人物も背景も、油膜のようなもやに溶けています。なお今回の動画は試し焼きで、カップやポールがゴルフとしては変な場所にありますが、見るのは「崩れているか、崩れていないか」だけなので、そこは気にしないでください。ComfyUI上のステータスは「成功」で、MP4も保存されていました。成功したかどうかは、ステータスではなく絵を見ないと分かりません。

続けて10ステップで焼くと、今度は最後の保存ノード(VHS_VideoCombine)の音声エンコードで止まりました。

Input contains (near) NaN/+-Inf
Error encoding a frame: Invalid argument
終了コード: -22

保存ノードのエラーですが、保存ノードの問題ではありません。上流から来た数値が壊れている(NaN)ので、エンコーダが受け取れない。右端の「1枚(H3VMなし)・8ステップ」は同じ条件でH3VMを切ったもので、こちらは普通に出ます。モデルでもプロンプトでもなく、2枚に分ける処理の中に原因がある、と切り分けました。

原因と修正: 2か所

1. 結合が終わる前に、読み始められる順序になっていた

対象は ComfyUI-H3-MultiGPU/h3vm/attention_parallel.py。2枚のGPUが出した結果を1つに結合してから次に渡すのですが、修正前は「結合完了を待つ」命令が、結果を読む reshape の後に置かれていました。

+        caller_stream.wait_event(merge_done)
         if not skip_output_reshape:
             output = output.transpose(1, 2).reshape(batch, -1, heads * dim_head)
-        caller_stream.wait_event(merge_done)
         output.record_stream(caller_stream)

transpose の後の reshape は見かけの並べ替えで済まず、メモリのコピーを起こすことがあります。そのコピーが、結合の完了より先に走ると、できかけのバッファを読んでしまう。「GPUが遅いから待ち時間を増やした」のではなく、「結合済みの結果を読むべきところで、完了待ちより先に読み始められる順序になっていた」のを直した、が正確です。全体を同期して並列を潰す直し方ではなく、必要な依存関係を1本足しただけです。

この順序を再現するテストでは、修正前は3回中2回で非有限値が出て、修正後は3回とも期待結果と一致。INT8のattention単体でも、系列長1,024と4,096を各3回、計6回とも1枚のGPUの結果と完全一致しました(この一致はattention単体の話で、最終動画の画素一致ではありません)。

2. ステップ数の「ヒント」で、途中のカウンターが0に戻っていた

対象は ComfyUI-H3-MultiGPU/h3vm/streaming_exact.py。H3VMには「何ステップ回すか」のヒント値があり、修正前はカウンターがヒントに達すると0に戻していました。サンプラーが10ステップで、ヒントが8のままだと、8で一度リセットされる。

-            if self.expected_steps and self._step >= self.expected_steps:
-                self._step = 0
+            # expected_steps is a hint, not a sampling boundary.
+            # sampling_end() resets the counter after the actual sampler finishes.

リセットを消して、実際のサンプリング終了時の処理に任せました。ヒントを8のまま8〜20ステップを回し、内部ログが1からステップ数まで連続することを確かめています。

2つの修正を同時に入れて最終動画を検証したので、10ステップのエラーにどちらがどれだけ効いたかは、個別には数値化していません。

結果: 8〜20ステップ、7本全部完走

修正後、同じプロンプト・同じ参照画像2枚・同じseedで、8/10/12/14/16/18/20ステップを1本ずつ。

条件値
GPURTX 5060 Ti 16GB × 2(CUDA 0 / 1)
ComfyUI / PyTorch0.36.0 / 2.13.0+cu130
本体minimax_h3_ref2va_pruned_int8_convrot
テキストエンコーダqwen3vl_32b_minimax_h3_nvfp4_awq
Turbo LoRAminimax_h3_ref2v_turbo_8step_v1.0_768p_comfyui_bf16、強度1.0(20ステップまで同じ)
H3VMON、参加率100%、dual VAE OFF
サンプラーeuler / simple、Shift 映像12 / 音声3
画面・尺0.6MP 縦3:4(672×928)、10秒設定、24fps
参照キャラクターシート1枚、ゴルフ場1枚。参照動画・音声なし
ステップ総時間うちサンプリング(概算)結果GPU0 / GPU1 使用率(採取最大)
88分38秒(517.968秒)約394秒完走・音声あり100% / 100%
1010分10秒(609.643秒)約491秒完走・音声あり100% / 100%
1211分44秒(703.555秒)約586秒完走・音声あり100% / 99%
1413分13秒(792.848秒)約676秒完走・音声あり100% / 100%
1615分20秒(920.058秒)約804秒完走・音声あり100% / 100%
1818分16秒(1096.258秒)約980秒完走・音声あり100% / 100%
2019分58秒(1198.082秒)約1082秒完走・音声あり100% / 100%

総時間は実行履歴の開始から成功までで、モデルのロードや保存を含みます。サンプリングはサンプラーの進捗表示から読んだ1秒単位の概算。使用率はnvidia-smiを約30秒間隔で採ったときの最大値で、平均ではありません。7本とも、FFmpegで先頭から末尾までデコードして終了値0を確認しています。

8ステップは、H3VMなし(1枚)の1,105.848秒(18分26秒)に対して517.968秒。2.13倍、53%の短縮です。同じPCで同じ条件を各1回ずつ比べた数字で、他の解像度や参照動画つきにそのまま当てはまる倍率ではありません。

修正後の2GPUで8・10・12・14・16・18・20ステップを焼いた5秒時点のフレーム7枚
修正後の2GPU。8〜20ステップの5秒時点

5秒時点のフレームをステップ順に並べたもの。どれも崩れていません。ステップを増やすほど良くなる、という採点はしていません(人物の形・ボールの位置・動作の精度は未評価)。

負荷を上げる: 0.8MPと15秒

修正を入れたまま、解像度と尺を上げてみました(10ステップ基準)。

解像度尺ステップ結果総時間
0.8MP(800×1056)10秒10完走・音声あり14分32秒(872.809秒)
0.6MP(672×928)15秒10完走・音声あり16分20秒(980.379秒)
0.8MP15秒10GPU0がメモリ不足(OOM)。3ステップ目で停止—
0.6MP15秒20完走・音声あり35分56秒(2,156.261秒)
0.6MP・15秒・20ステップを2GPUで完走した動画の1秒・5秒・9秒・14秒
0.6MP・15秒・20ステップ(2GPU・35分56秒)の1秒・5秒・9秒・14秒

0.6MP・15秒・20ステップの1秒・5秒・9秒・14秒。最終動画は361フレーム・24fps・実尺15.04秒・AAC音声つき。

0.8MP・15秒では、GPU0がINT8のMLP用の作業領域(872MiB)を確保できずに止まりました。そのときGPU0は空き0、GPU1には余力が残っていた。16GB×2枚は「32GBの連続した空き」ではなく、GPUごとの割り当てが上限になる、というのが今回の結論です。OOMのあとモデル解放処理から戻らず、ComfyUIの再起動で復旧しました。

条件GPU0 / GPU1 VRAM採取最大(MiB)
0.8MP・10秒・10ステップ14,005 / 15,082
0.6MP・15秒・10ステップ15,196 / 14,778
0.8MP・15秒・10ステップ(OOM)15,997 / 10,540
0.6MP・15秒・20ステップ15,955 / 12,920

各GPUの総量は16,311MiB。成功した条件でもGPU0は上限ぎりぎりで、同じ指定で常に余裕がある、とは言えません。

分かったこと

  1. 「成功」ステータスは画質の保証ではない。 修正前の8ステップは成功扱いでもやだった。抜き出したフレームを見るまで分からない。
  2. 保存ノードのエラーは、上流の数値異常のサインのことがある。 AACのNaN/Infは、エンコーダではなく2枚の結合順序が原因だった。
  3. 2GPUの同期は「待ち時間」ではなく「順序」。 依存関係を1本正しい位置に置くだけで直り、並列性は落ちていない。
  4. 16GB×2は32GBではない。 0.8MP・15秒はGPU0だけが先に溢れた。
  5. 第25回の「15秒が45分34秒」は参照動画つき。今回の0.6MP・15秒・10ステップは参照画像2枚で16分20秒、20ステップで35分56秒。条件が違うので倍率は出しません。

修正版のワークフロー(8〜20ステップ)は手元に保存し、デスクトップに20ステップ版のアイコンを置きました。H3VMを更新すると修正が上書きされる可能性があるので、再生成前に該当箇所の順序を確認する運用にしています。

まとめ

  • 2GPU(H3VM)で絵がもやになる原因は、結合完了を待つ前に読み始められる順序と、ステップ数ヒントによる途中リセットの2つ。
  • 2か所を直して、同じseedで8〜20ステップの7本が全部完走。8ステップは1枚の2.13倍。
  • 0.6MP・15秒・20ステップが35分56秒で完走。0.8MP・15秒はGPU0のOOM。
  • 16GB×2枚は、GPUごとの上限で決まる。

症状から対処を引く表はトラブル早見表に、機材ごとの所要時間は実測早見表にまとめています。


本記事の数値は2026年10月21日の当方環境(Windows・RTX 5060 Ti 16GB×2・ComfyUI 0.36.0・H3VM v0.21.1相当にローカル修正)の記録です。人物はAI生成です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次