【ComfyUI】バッチ処理で画像生成を自動化する方法と「OutOfMemoryError」の解決策

問題の概要:バッチ処理の自動化とメモリエラー

ComfyUIは、ノードベースの直感的なインターフェースでStable Diffusionのワークフローを構築できる強力なツールです。しかし、単一の画像を生成するだけでなく、複数のプロンプトや設定で大量の画像を自動生成したい場合、手動での操作は非効率です。多くのユーザーが、バッチ処理による自動化を試みる中で、以下のような課題やエラーに直面します。

  • ワークフローを繰り返し実行するスクリプトの書き方がわからない。
  • バッチ処理中に「RuntimeError: CUDA out of memory.」が発生し、処理が中断する。
  • 生成された画像のファイル名が重複したり、管理が煩雑になる。
  • 異なるシード値やプロンプトを効率的に切り替える方法がわからない。

特に、OutOfMemoryErrorは、バッチ処理を妨げる最も一般的な障壁です。この記事では、ComfyUIのワークフローをPythonスクリプトから制御してバッチ処理を自動化する方法と、発生するメモリ関連のエラーを確実に解決する手順を詳しく解説します。

原因の解説:なぜメモリエラーが発生するのか?

ComfyUIのバッチ処理自動化でメモリ不足エラーが発生する主な原因は、GPUメモリの解放不足にあります。ComfyUIの内部では、画像生成のたびにモデルがVRAMにロードされ、テンソルや中間データが蓄積されます。単一の実行では問題なくても、ループ内で連続して実行すると、これらのデータが適切にガベージコレクションされず、VRAMが枯渇してしまいます。

技術的には、PyTorchのCUDAメモリ管理とComfyUIの実行コンテキストが密接に関係しています。各生成処理後に明示的にメモリをクリアしない限り、メモリ使用量は実行を重ねるごとに増加する傾向があります。また、使用するモデル(ベース、LoRA、ControlNetなど)の数やサイズ、画像の解像度もメモリ消費に直接影響します。

解決方法:Pythonスクリプトによる自動化とメモリ管理

ここでは、ComfyUIに同梱されている公式のサンプルスクリプトをベースに、安全なバッチ処理を実装する方法をステップバイステップで説明します。

ステップ1: 環境とスクリプトの準備

まず、ComfyUIのインストールディレクトリ内にある comfyUI フォルダ(メインのPythonモジュール)と、サンプルスクリプトの場所を確認します。バッチ処理用の基本的なスクリプトは comfyUI ディレクトリの外、例えば comfyui_root 直下に作成することをお勧めします。

ステップ2: ベースとなるPythonスクリプトの作成

以下のスクリプト(batch_generate.py)は、ComfyUIのAPIを通じてワークフローを実行する骨格です。キーとなるのは、各生成サイクル後のメモリ解放処理です。

import comfy.utils
import torch
import folder_paths
import json
import sys
import os

# ComfyUIのモジュールパスを追加(環境に合わせて変更)
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "comfy"))

def clear_memory():
    """GPUメモリを強制的に解放する"""
    if torch.cuda.is_available():
        torch.cuda.empty_cache()
        torch.cuda.ipc_collect()
        print("CUDAキャッシュをクリアしました。")

def load_workflow(json_path):
    """ワークフローのJSONファイルを読み込む"""
    with open(json_path, 'r', encoding='utf-8') as f:
        workflow_data = json.load(f)
    return workflow_data

def main():
    # 1. ワークフローの読み込み
    workflow_json = "your_workflow_api.json" # エクスポートしたAPI用ワークフローファイル
    prompt_data = load_workflow(workflow_json)

    # 2. バッチ処理するプロンプトのリストを定義
    batch_prompts = [
        {"positive": "1 girl, beautiful sunset, cinematic lighting",
         "negative": "blurry, bad anatomy",
         "seed": 42},
        {"positive": "cyberpunk cityscape, neon lights, rain",
         "negative": "daytime, sunny",
         "seed": 12345},
        # ... さらに多くのプロンプトを追加
    ]

    # 3. ComfyUIのモデルを事前ロード(オプション、最初の実行を安定させる)
    comfy.utils.load_models()

    for i, prompt_set in enumerate(batch_prompts):
        print(f"Processing batch {i+1}/{len(batch_prompts)}: {prompt_set['positive'][:50]}...")

        # 4. プロンプトデータを動的に更新
        # 注: あなたのワークフローに合わせてノードIDを修正する必要があります。
        # 例: "6" がCLIP Text Encode (Positive) ノードのIDの場合
        prompt_data["6"]["inputs"]["text"] = prompt_set["positive"]
        prompt_data["7"]["inputs"]["text"] = prompt_set["negative"] # Negativeノード
        prompt_data["3"]["inputs"]["seed"] = prompt_set["seed"]    # KSamplerノード

        try:
            # 5. ワークフローを実行
            # この部分はComfyUIの内部実行関数を呼び出す必要があります。
            # 実際には、`comfy.sample.sample` やカスタム実行関数を使用します。
            # 以下の行はプレースホルダーです。実際の実行方法は次のステップで説明します。
            # output_images = execute_workflow(prompt_data)
            print(f"  Seed {prompt_set['seed']} で生成を開始します。")

            # 6. 生成ごとにメモリをクリア(重要!)
            clear_memory()

        except torch.cuda.OutOfMemoryError as e:
            print(f"  ERROR: メモリ不足です。{e}")
            clear_memory()
            # 必要に応じて、解像度を下げるなどの処理をここに追加
            continue

        except Exception as e:
            print(f"  ERROR: その他のエラーが発生しました。{e}")
            clear_memory()
            continue

    print("バッチ処理が完了しました。")

if __name__ == "__main__":
    main()

ステップ3: ワークフローの実行エンジンとの連携

上記スクリプトの肝は、execute_workflow 関数の実装です。これには、ComfyUIが提供する comfy.sample.sample 関数を利用する方法や、より高レベルな ComfyPrompt クラスを利用する方法があります。以下は、より実用的な実行例です。

import comfy.sample
import comfy.samplers
import nodes
import importlib
# ComfyUIの内部モジュールをリロード(ホットリロード環境下で必要)
importlib.reload(comfy.sample)
importlib.reload(comfy.samplers)

def execute_workflow_with_api(prompt_data, output_dir="./batch_output"):
    """更新されたprompt_dataでワークフローを実行し、画像を保存する"""
    os.makedirs(output_dir, exist_ok=True)

    # workflow_dataから必要なノードの情報を取得して実行するロジック
    # この部分は、ComfyUIのソースコード `server.py` の execute_workflow 部分を参考に実装します。
    # 実際には、`nodes.NODE_CLASS_MAPPINGS` を使ってノードをインスタンス化し、
    # データフローを実行する複雑な処理が必要です。

    # 【代替現実的な方法】WebSocket APIを使用する
    # 多くのユーザーは、ComfyUIのWebSocketサーバーに接続してバッチ処理を実行します。
    # これは、内部APIを直接叩くよりも安定している場合があります。
    pass

現実的な推奨方法: 内部APIの複雑さを避けるため、多くの実践者はComfyUIが起動した状態でWebSocket API(ポート8188)に接続し、JSONペイロードを送信する方法を採用しています。これには websocket-client ライブラリを使用します。

ステップ4: WebSocket APIを用いた実用的なバッチスクリプト例

import websocket
import json
import uuid
import time

def send_prompt_via_websocket(ws, prompt_data, client_id):
    """WebSocketでプロンプトを送信"""
    payload = {
        "prompt": prompt_data,
        "client_id": client_id
    }
    ws.send(json.dumps(payload))
    print("プロンプトを送信しました。生成を待機中...")

    # 生成完了を待機(簡易的な実装)
    result = ws.recv()
    return result

def batch_process_with_websocket(workflow_template, batch_list, server_address="ws://127.0.0.1:8188/ws"):
    """WebSocket接続を用いたバッチ処理"""
    ws = websocket.WebSocket()
    ws.connect(server_address)
    client_id = str(uuid.uuid4())

    for idx, item in enumerate(batch_list):
        # テンプレートのプロンプトを置き換え
        current_prompt = json.loads(json.dumps(workflow_template)) # ディープコピー
        # ... ここで current_prompt 内の特定ノードのテキストやシードを item の値で更新 ...

        try:
            send_prompt_via_websocket(ws, current_prompt, client_id)
            # 各処理後に少し待機(サーバーの負荷軽減)
            time.sleep(1)
            clear_memory() # 同じメモリ解放関数を呼び出す
        except websocket.WebSocketException as e:
            print(f"WebSocketエラー: {e}")
            break
        except Exception as e:
            print(f"処理中にエラー: {e}")
            clear_memory()
            continue

    ws.close()
    print("WebSocketバッチ処理が終了しました。")

コード例・コマンド例:エラー発生時の具体的な対処

実際のエラーメッセージとその対処コマンドを以下に示します。

# エラーメッセージ例:
# RuntimeError: CUDA out of memory. Tried to allocate 2.00 GiB ...
# または
# torch.cuda.OutOfMemoryError: CUDA out of memory.

# 対処1: スクリプト内でのメモリ解放関数の呼び出しを確認
# 各ループの最後に必ず `clear_memory()` を呼び出しているか確認する。

# 対処2: バッチサイズを実質的に1にする
# KSamplerノードの "batch_size" を1に設定する。ワークフローJSON内で確認・修正。

# 対処3: コマンドラインからComfyUIを起動する際にVRAM使用量を最適化する
# Windows (コマンドプロンプトまたはPowerShell):
cd C:your_path_toComfyUI
python main.py --lowvram

# Linux/macOS:
cd /your_path_to/ComfyUI
python3 main.py --highvram # メモリ豊富な場合
# または
python3 main.py --normalvram

# 対処4: モデルをVRAMからアンロードするカスタムノードを利用する
# "ComfyUI-Manager" から "efficiency nodes" などをインストールし、
# ワークフロー中に "Load/Unload Model" ノードを配置してメモリを制御する。

まとめ・補足情報

ComfyUIでのバッチ処理自動化は、PythonスクリプトとWebSocket APIを組み合わせることで実現できます。成功の鍵は以下の点にあります。

  1. メモリ管理の徹底: 各画像生成イテレーションの後に、torch.cuda.empty_cache() を呼び出してGPUメモリを明示的に解放する。
  2. ワークフローのエクスポート: ComfyUIのUIで完成したワークフローは、「Save (API Format)」でJSONとして保存し、これをスクリプトのテンプレートとして使用する。
  3. エラーハンドリング: OutOfMemoryError を確実にキャッチし、処理をスキップしたり、パラメータを調整したりするロジックを組み込む。
  4. 実行方法の選択: 内部APIは強力ですが複雑です。初期段階では、起動済みのComfyUIサーバーに対してWebSocketでプロンプトを送信する方法が、トラブルが少なくおすすめです。

最後に、バッチ処理の進捗をログに残し、生成された画像にはプロンプトやシード値をファイル名に含めるなど、管理のしやすさを考慮した実装を心がけましょう。これにより、大規模な画像生成プロジェクトも効率的かつ安定的に実行できるようになります。

この記事は役に立ちましたか?