メインコンテンツへスキップ
見出し画像

WAN2.2 i2v 14B FP16 GGUF with lightning LoRA 4steps or lightx2v 6steps&Distorch Memory Manager

    2025年10月8日、WAN2.2用lightning LoRAを適用したVerⅡ 4stepsを更新しました。

    かなり良いです。上のコンセプトはWAN2.1ではなかなか難しい形でした。

    About WAN2.2

    i2v with WAN2.1 lightx2v LoRA 6steps

    i2vⅡ with WAN2.2 lightning LoRA 4steps

    いつものやつですね。FP16フルサイズのGGUFを使用しても尚、改造したMulitiGPUノードのDistorchによりVRAM消費量を12GB未満に抑える…動画生成のモデルが出る度に、私の定番になりつつあります。

    思ったより速いです。毎度お馴染みDistorchですが、シングルタスク単体は、WAN2.1より速いですね、意外な事に。

    また以下の様に、lightx2vによるステップ数削減が効くことも判っています。只、現状WAN2.1用LoRAを使用すると大量のエラーが吐き出される為、そこはスマートではないです。このエラーそのものは不可避ですが、表示自体を「何とかする」事は可能なので後述します。

    Distorch改造に関しては、以下を参照して下さい。

    480×480だと、処理中は以下の感じです。720×720、length121辺りまではVRAM消費が12GBが収まるようです。上jsonは640×640、length121設定にしています。

    画像

    fp16 GGUF

    量子化していないフルサイズのGGUFを、以下に公開しました。

    Ollama-Generate V2

    動作は重くなりますが、プロンプトの自動生成機能を付与する為、ollamaを使用します。vision系LLMにも対応しますが、正直動作精度が良くありません。通常のtext生成LLMの方が無難です。

    画像

    その代わり、プロンプト入力は日本語に対応します。Olama Generaye V2でで自動翻訳がかかる設計にしています。

    画像

    Fixed lora.py

    \ComfyUI\comfy\lora.py"

    動作には影響しませんが、本来はWAN2.1用のlightx2v LoRAをWAN2.2に適用すると、大量の

      lora key not loaded: diffusion_model.blocks.0.attn1.to_q.weight
      lora key not loaded: diffusion_model.blocks.0.attn1.to_k.weight

    的なエラーが出ます。気分は良くないので、「WAN2.2にWAN2.1用のLoRAを適用した場合」に限り、これを以下の様に変換する修正を施しましたので、上に公開します。

    Suppressed 290 WAN2.1 LoRA key warnings (WAN2.1 LoRA on WAN2.2 model - expected behavior)

    解説

    WAN2.2モデルにWAN2.1 LoRA適用時のみ未ロード警告を集約抑制する実装ノート(ComfyUI `lora.py`)

    目的

    • WAN2.2にWAN2.1 LoRAを適用したときだけ、大量に出る「想定内の未ロードキー警告」を集約ログにまとめる。

    • それ以外(WAN2.1モデルや他モデル)では、警告を抑制せず通常どおり出す。

    • 新規でエラー表示が出た全系列(`double_blocks`/`single_blocks` の img/text ヘッド群)も抑制対象に含める。


    対象ファイル

    • `ComfyUI/comfy/lora.py`


    変更前(初期状態)

    • 抑制ロジックはなく、未ロードキーはすべて警告として出力。

    if log_missing:
        for x in lora.keys():
            if x not in loaded_keys:
                logging.warning("lora key not loaded: {}".format(x))

    問題:

    • WAN2.1 LoRAをWAN2.2に適用すると、構造差由来の未ロードキーが大量に出続け、ログがノイズ化。


    変更後(完成形)

    • 変更位置: `def load_lora(...):` 内の `if log_missing:` ブロック

    • 実装の骨子:

      • LoRA側にWAN2.1特有の「キー断片」が1つでもあれば「WAN2.1 LoRA」と判定。

      • モデル側(`to_load.values()`)にWAN2.1特有断片が1つも無ければ「WAN2.2」と推定。

      • 両方が真(WAN2.1 LoRA × WAN2.2モデル)かつ、該当キーがインジケータに合致する場合のみ抑制し、件数を集約ログで出力。

      • それ以外は通常通り、個別の警告を出す。

    実コード(該当部のみ抜粋):

    if log_missing:
        # WAN2.1 on WAN2.2 warning suppression (expected missing keys)
        # Detect WAN2.1-style LoRA by presence of characteristic key fragments in the LoRA file
        wan21_lora_indicators = [
            # classic image-attn/text-attn specific
            "k_img.diff_b", "k_img.lora_down.weight", "k_img.lora_up.weight",
            "v_img.diff_b", "v_img.lora_down.weight", "v_img.lora_up.weight",
            "norm_k_img.diff",
            # img_emb variants (WAN2.1)
            "img_emb.proj.",
            # cross_attn variants (WAN2.1)
            ".cross_attn.k_img.", ".cross_attn.v_img.",
            # trainers: double_blocks/single_blocks heads (img/text)
            ".double_blocks.", ".single_blocks.",
            ".img_attn_proj.", ".img_attn_qkv.",
            ".img_mlp.fc1.", ".img_mlp.fc2.",
            ".img_mod.linear.",
            ".txt_attn_proj.", ".txt_attn_qkv.",
            ".txt_mlp.fc1.", ".txt_mlp.fc2.",
            ".txt_mod.linear.",
            # common single block linear heads
            ".linear1.lora_", ".linear2.lora_", ".modulation.linear."
        ]
    
        wan21_keys_found = [key for key in lora.keys() if any(indicator in key for indicator in wan21_lora_indicators)]
        is_wan21_lora = len(wan21_keys_found) > 0
    
        # Apply suppression ONLY when applying WAN2.1 LoRA onto WAN2.2 model.
        # Heuristic for model: if model (to_load values) contains WAN2.1-only keys, it's not WAN2.2.
        try:
            model_key_samples = [str(v) for v in to_load.values()]
        except Exception:
            model_key_samples = []
    
        model_has_wan21_like_keys = any(
            (".cross_attn.k_img." in mk) or (".cross_attn.v_img." in mk) or
            ("img_emb.proj" in mk) or ("norm_k_img" in mk)
            for mk in model_key_samples
        )
        is_wan22_model_target = not model_has_wan21_like_keys
    
        suppressed_count = 0
        actual_warnings = []
    
        for x in lora.keys():
            if x not in loaded_keys:
                # Suppress only expected WAN2.1-on-WAN2.2 missing keys
                if (is_wan21_lora and is_wan22_model_target and
                    any(indicator in x for indicator in wan21_lora_indicators)):
                    suppressed_count += 1
                    continue
                actual_warnings.append(x)
    
        for x in actual_warnings:
            logging.warning("lora key not loaded: {}".format(x))
    
        if suppressed_count > 0:
            logging.info(
                "Suppressed {} WAN2.1 LoRA key warnings (WAN2.1 LoRA on WAN2.2 model - expected behavior)".format(
                    suppressed_count
                )
            )

    何を抑制対象にしたか(部分一致インジケータ)

    • k_img/v_img 系:

      • "k_img.diff_b", "k_img.lora_down.weight", "k_img.lora_up.weight"

      • "v_img.diff_b", "v_img.lora_down.weight", "v_img.lora_up.weight"

      • "norm_k_img.diff"

    • img_emb 系:

      • "img_emb.proj."

    • cross_attn 系:

      • ".cross_attn.k_img.", ".cross_attn.v_img."

    • 新たに提示された系列も網羅:

      • ".double_blocks.", ".single_blocks."

      • ".img_attn_proj.", ".img_attn_qkv."

      • ".img_mlp.fc1.", ".img_mlp.fc2."

      • ".img_mod.linear."

      • ".txt_attn_proj.", ".txt_attn_qkv."

      • ".txt_mlp.fc1.", ".txt_mlp.fc2."

      • ".txt_mod.linear."

      • ".linear1.lora_", ".linear2.lora_", ".modulation.linear."

    注意:

    • 末尾の `.weight` までの完全一致にしない(トレーナー差により末端が揺れるため)。部分一致で十分かつ安全。


    再現手順

    • 対象ファイル: `ComfyUI/comfy/lora.py`

    • `load_lora` の `if log_missing:` ブロックを上記の完成形に差し替える

    • ComfyUIを再起動(または該当モジュール再ロード)

    • ログレベルを info 以上に設定(集約ログは info)


    動作検証の観点

    • WAN2.2 × WAN2.1 LoRA:

      • 個別の未ロード警告は抑制され、末尾に集約ログのみ表示

      • 例: `Suppressed 290 WAN2.1 LoRA key warnings (WAN2.1 LoRA on WAN2.2 model - expected behavior)`

    • WAN2.2 × WAN2.2 LoRA:

      • 抑制は発動しない(通常は未ロード警告自体が出ない想定)

    • WAN2.1 × WAN2.1 LoRA(または他モデル):

      • 抑制は発動しない(個別警告が出る)


    トラブルシューティング

    • 抑制されないキーがあった場合:

      • そのキー名の一部を `wan21_lora_indicators` に1行追加(部分一致でOK)

    • 抑制が効きすぎる場合(本来出すべき環境で消える):

      • モデル側判定(`model_has_wan21_like_keys`)の条件をより厳格化

    • ログが出ない:

      • ログレベルを info 以上に設定


    変更のインパクト

    • WAN2.2 × WAN2.1適用時のログノイズを大幅に削減

    • WAN2.2以外の組み合わせでは従来通りの警告表示を維持(安全側)


    このノートの内容で、第三者が後から読んでも同じ実装・同じ挙動を再現できます。必要になったら `wan21_lora_indicators` に断片を1行足すだけで拡張可能です。

    About WAN2.1 LoRA

    WAn2.1用LoRAの適用に関しては一概に総括できませんが、以下はHjgh側でstrength3.0、Low側で1.5で想定通りの動作をした例です。360度回転モーションLoRAを使用しています。

    WAN2.2はプロンプト再現能力が非常に高く、実はこの程度の事はプロンプトだけでやれてしまいますが。

    Fixed GGUF Loader for WAN2.2

    この先はともかく、少なくとも現時点で肝心のGGUFローダーがWAN2.2に対応していません。

    故に改造しています。以下、特に難しくありません。厳密に言えば、「GGUFローダーの記述を改造する事により、MultiGPUのGGUFローダーがWAN2.2を認識する」という仕組みになっています。

    ## 修正箇所

    **ファイル**: `ComfyUI/custom_nodes/ComfyUI-GGUF/loader.py`

    ### 修正前のコード

    IMG_ARCH_LIST = {"flux", "sd1", "sdxl", "sd3", "aura", "hidream", "cosmos", "ltxv", "hyvid", "wan"}

    ### 修正後のコード

    IMG_ARCH_LIST = {"flux", "sd1", "sdxl", "sd3", "aura", "hidream", "cosmos", "ltxv", "hyvid", "wan", "wan2.2"}

    ```

    Distorch Memory Manager

    About a node

    30GBクラスのベースモデル2個に対してdistorch処理をかける工程は、何か影響が出る予感がしていましたが、案の定結果RTX4070 12GBクラスではx4 UpscaleがOOMの為完走しないという問題を引き起こしました。

    但し、私は既に独自の改造により解決しています。まずは下図のように、Distorchによるメモリ使用をアンロードする為のオリジナルノードを作成しました。

    画像

    independently Distorch Memory Cleaner

    ノードは、以下記事で公開しています。ダウンロードして解凍し、custom_nodesフォルダに置くだけで動作します。

    custom_nodes\ComfyUI-MultiGPU_init_.py

    難易度が高くなるので推奨はしませんが、同機能を以下の様にMultiGPUに統合する事も可能です。但し、この方法はファイル構造やコードの内容を理解できるユーザーでないと、使い続ける事が難しいでしょう。理由は、上記事にて記述しています。 

    DisTorch Memory Managerの改造詳細をコード付きで解説します。

    ## 📋 DisTorch Memory Manager 改造総括

    ### 1. **DisTorchMemoryCleaner** - 基本的なメモリクリア

    class DisTorchMemoryCleaner:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {
                "image": ("IMAGE",),
            }}
        
        RETURN_TYPES = ("IMAGE",)
        FUNCTION = "clean_memory"
        CATEGORY = "DisTorch"
    
        def clean_memory(self, image):
            import torch
            import gc
            
            # GPUメモリのクリア
            if torch.cuda.is_available():
                torch.cuda.empty_cache()
                torch.cuda.synchronize()
            
            # Pythonのガベージコレクション
            gc.collect()
            
            # DisTorchの仮想メモリを解放
            try:
                import comfy.model_management
                # 仮想メモリの割り当てをリセット
                if hasattr(comfy.model_management, 'free_memory'):
                    comfy.model_management.free_memory(0, 'cuda:0')
                    comfy.model_management.free_memory(0, 'cpu')
            except:
                pass
            
            print("DisTorch memory cleaned")
            return (image,)

    **機能**:

    - GPUメモリのクリア(`torch.cuda.empty_cache()`)

    - Pythonガベージコレクション(`gc.collect()`)

    - DisTorch仮想メモリのリセット

    - シンプルで安全なメモリ管理

    ### 2. **DisTorchMemoryManager** - 包括的なメモリ管理(UI破損対策済み)

    class DisTorchMemoryManager:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {
                "image": ("IMAGE",),
                "clean_gpu": ("BOOLEAN", {"default": True}),
                "clean_cpu": ("BOOLEAN", {"default": False, "tooltip": "CPU memory cleanup (use with caution)"}),
                "force_gc": ("BOOLEAN", {"default": True}),
                "reset_virtual_memory": ("BOOLEAN", {"default": True}),
                "restore_original_functions": ("BOOLEAN", {"default": False, "tooltip": "Restore original model_management functions"}),
            }}
        
        RETURN_TYPES = ("IMAGE",)
        FUNCTION = "manage_memory"
        CATEGORY = "DisTorch"
    
        def manage_memory(self, image, clean_gpu, clean_cpu, force_gc, reset_virtual_memory, restore_original_functions):
            import torch
            import gc
            import psutil
            import pydantic
            
            print("=== DisTorch Memory Management ===")
            
            # メモリ使用量の表示(前)
            if torch.cuda.is_available():
                gpu_memory_before = torch.cuda.memory_allocated() / 1024**3
                print(f"GPU Memory before: {gpu_memory_before:.2f} GB")
            
            cpu_memory_before = psutil.virtual_memory().used / 1024**3
            print(f"CPU Memory before: {cpu_memory_before:.2f} GB")
            
            # GPUメモリのクリア
            if clean_gpu and torch.cuda.is_available():
                try:
                    torch.cuda.empty_cache()
                    torch.cuda.synchronize()
                    print("GPU cache cleared")
                except Exception as e:
                    print(f"GPU cache clear failed: {e}")
            
            # CPUメモリのクリア(UI破損対策済み)
            if clean_cpu:
                # 基本的なガベージコレクションのみ実行
                # オブジェクトの詳細検査は行わない(UIに影響する可能性があるため)
                try:
                    # 明示的にガベージコレクションを実行
                    collected = gc.collect()
                    print(f"CPU memory cleanup: {collected} objects collected")
                except Exception as e:
                    print(f"CPU memory cleanup failed: {e}")
            
            # ガベージコレクション
            if force_gc:
                try:
                    collected = gc.collect()
                    print(f"Garbage collected: {collected} objects")
                except Exception as e:
                    print(f"Garbage collection failed: {e}")
            
            # DisTorchの仮想メモリをリセット
            if reset_virtual_memory:
                try:
                    import comfy.model_management
                    if hasattr(comfy.model_management, 'free_memory'):
                        comfy.model_management.free_memory(0, 'cuda:0')
                        comfy.model_management.free_memory(0, 'cpu')
                    print("Virtual memory reset")
                except Exception as e:
                    print(f"Virtual memory reset failed: {e}")
            
            # メモリ使用量の表示(後)
            if torch.cuda.is_available():
                try:
                    gpu_memory_after = torch.cuda.memory_allocated() / 1024**3
                    gpu_freed = gpu_memory_before - gpu_memory_after
                    print(f"GPU Memory after: {gpu_memory_after:.2f} GB (freed: {gpu_freed:.2f} GB)")
                except Exception as e:
                    print(f"GPU memory measurement failed: {e}")
            
            try:
                cpu_memory_after = psutil.virtual_memory().used / 1024**3
                cpu_freed = cpu_memory_before - cpu_memory_after
                print(f"CPU Memory after: {cpu_memory_after:.2f} GB (freed: {cpu_freed:.2f} GB)")
            except Exception as e:
                print(f"CPU memory measurement failed: {e}")
            
            # 元の関数を復元(オプション)
            if restore_original_functions:
                try:
                    mm.get_torch_device = original_get_torch_device
                    mm.text_encoder_device = original_text_encoder_device
                    print("Original model_management functions restored")
                except Exception as e:
                    print(f"Failed to restore original functions: {e}")
            
            print("=== Memory Management Complete ===")
            
            return (image,)

    **主要な改善点**:

    1. **UI破損対策**:

       # CPUメモリクリアを安全に実行
       if clean_cpu:
           # オブジェクトの詳細検査は行わない
           try:
               collected = gc.collect()
               print(f"CPU memory cleanup: {collected} objects collected")
           except Exception as e:
               print(f"CPU memory cleanup failed: {e}")

      2. **詳細なメモリ監視**:

       # メモリ使用量の前後比較
       gpu_memory_before = torch.cuda.memory_allocated() / 1024**3
       cpu_memory_before = psutil.virtual_memory().used / 1024**3
       
       # 処理後
       gpu_freed = gpu_memory_before - gpu_memory_after
       cpu_freed = cpu_memory_before - cpu_memory_after

       3. **エラーハンドリング**:

       try:
           torch.cuda.empty_cache()
           torch.cuda.synchronize()
           print("GPU cache cleared")
       except Exception as e:
           print(f"GPU cache clear failed: {e}")

    ### 3. **DisTorchSafeMemoryManager** - 安全なメモリ管理(推奨)

    class DisTorchSafeMemoryManager:
        @classmethod
        def INPUT_TYPES(s):
            return {"required": {
                "image": ("IMAGE",),
                "clean_gpu": ("BOOLEAN", {"default": True}),
                "force_gc": ("BOOLEAN", {"default": True}),
                "reset_virtual_memory": ("BOOLEAN", {"default": True}),
            }}
        
        RETURN_TYPES = ("IMAGE",)
        FUNCTION = "safe_manage_memory"
        CATEGORY = "DisTorch"
    
        def safe_manage_memory(self, image, clean_gpu, force_gc, reset_virtual_memory):
            import torch
            import gc
            
            print("=== DisTorch Safe Memory Management ===")
            
            # メモリ使用量の表示
            if torch.cuda.is_available():
                gpu_memory_before = torch.cuda.memory_allocated() / 1024**3
                print(f"GPU Memory before: {gpu_memory_before:.2f} GB")
            
            # GPUメモリのクリア(UIに影響しない)
            if clean_gpu and torch.cuda.is_available():
                try:
                    torch.cuda.empty_cache()
                    torch.cuda.synchronize()
                    print("GPU cache cleared")
                except Exception as e:
                    print(f"GPU cache clear failed: {e}")
            
            # ガベージコレクション(UIに影響しない)
            if force_gc:
                try:
                    collected = gc.collect()
                    print(f"Garbage collected: {collected} objects")
                except Exception as e:
                    print(f"Garbage collection failed: {e}")
            
            # DisTorchの仮想メモリをリセット
            if reset_virtual_memory:
                try:
                    import comfy.model_management
                    if hasattr(comfy.model_management, 'free_memory'):
                        comfy.model_management.free_memory(0, 'cuda:0')
                        comfy.model_management.free_memory(0, 'cpu')
                    print("Virtual memory reset")
                except Exception as e:
                    print(f"Virtual memory reset failed: {e}")
            
            # メモリ使用量の表示(後)
            if torch.cuda.is_available():
                try:
                    gpu_memory_after = torch.cuda.memory_allocated() / 1024**3
                    gpu_freed = gpu_memory_before - gpu_memory_after
                    print(f"GPU Memory after: {gpu_memory_after:.2f} GB (freed: {gpu_freed:.2f} GB)")
                except Exception as e:
                    print(f"GPU memory measurement failed: {e}")
            
            print("=== Safe Memory Management Complete ===")
            
            return (image,)

    **特徴**:

    - **CPUメモリクリア機能を完全に除外**(UI破損を防ぐ)

    - **GPUメモリとガベージコレクションのみ**実行

    - **最も安全で安定した**メモリ管理

    ## �� **改造の核心ポイント**

    ### 1. **UI破損問題の解決**

    # 問題のあったコード(削除済み)
    # for obj in gc.get_objects():
    #     if isinstance(obj, pydantic.BaseModel):
    #         # この部分がUI破損の原因
    
    # 解決策
    if clean_cpu:
        # 基本的なガベージコレクションのみ実行
        collected = gc.collect()
        print(f"CPU memory cleanup: {collected} objects collected")

    ### 2. **メモリ使用量の可視化**

    # メモリ解放量の詳細表示
    gpu_freed = gpu_memory_before - gpu_memory_after
    cpu_freed = cpu_memory_before - cpu_memory_after
    print(f"GPU Memory freed: {gpu_freed:.2f} GB")
    print(f"CPU Memory freed: {cpu_freed:.2f} GB")

    ### 3. **エラーハンドリングの強化**

    try:
        torch.cuda.empty_cache()
        torch.cuda.synchronize()
        print("GPU cache cleared")
    except Exception as e:
        print(f"GPU cache clear failed: {e}")

    ## �� **使用方法**

    # DisTorchSafeMemoryManager(最も安全)
    clean_gpu = True
    force_gc = True
    reset_virtual_memory = True
    
    # DisTorchMemoryManager(上級者向け)
    clean_gpu = True
    clean_cpu = False  # UI破損を防ぐためFalse推奨
    force_gc = True
    reset_virtual_memory = True

    ### **効果**:

    - **GPUメモリ**: 最大数GBの解放

    - **CPUメモリ**: 数百MB〜数GBの解放

    - **安定性**: UI破損なし

    - **パフォーマンス**: アップスケール・ビデオ処理の安定化

    これらの改造により、ComfyUIでのメモリ管理が大幅に改善され、アップスケールやビデオ処理でのメモリエラーが大幅に減少します。

    Paging file

    このノードを使用しても尚、upscalingでOOM(この場合、VRAMではなくシステムメモリ不足で落ちる)エラーを起こす場合、ページングファイルのサイズを拡張して対応してください。

    画像

    高速化ノード組み合わせと、テンソル不一致エラーについて

    初期状態での組み合わせは、Skip Layer+Torch Compileですが、この組み合わせはどうも、720×720生成時にテンソル不一致エラーの原因になるようです。稀に640×640でも発生しますが、条件がどうも不明です。

    画像

    Skip Layerをバイパスして、代わりにApply First Block Cacheを使用すると解決する場合が多いです。何れにせよ、組み合わせを選択できるようにしておきました 。

    Torch.compileは無いと、特に640×640以上で相当生成速度が落ちるので、なるべく使用したい処です。

    今日のBGM

    鞘師里保 - Super Red (Music Video)


     
     

    GJL

     
     
    Road bike&Cycling Road Race, Respecting Mr.Greg LeMond, & Anti Doping, Football, Rock&Pops, History.

    あなたへのおすすめ