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

Archive of custom code like Distorch+α

    2025年3月8日現在、以下の改造は、MultiGPUノードの設定により、互換性の問題を起こすことなく同等の効果を生み出せることがわかりました。よって、以下の記述はほぼ無意味になりましたが、試行錯誤のプロセスを保存する意味で残しておきます。

    Like Distorch

    Hunyuan VideoとSkyReelsに於いては、GGUFを使用し、かつ64GB以上のRAMとRTX4070 12GBクラスのGPUを使用する前提であるならば、大きな高速化に寄与するMultiGPUノードに拠るDistorch機能ですが、

    Flux1に対してはほとんど効果がない…点に関しては以下で解説しています。また以下記事では言及していませんが、その後Flux1.dev_fp16.GGUFにおいてもGGUFに切り替えてDistorchをテストした処、高速化するどころか大量の共有VRAMを使いに行ってしまい、寧ろ劇遅化する事がわかりました。
    (この方法を思いついた時は、これでFlux1もHunyuan Videoのように常時専用VRAM12GB未満かと期待したんですがね)

    その後、KJNodes製のtorch.compileをmax-autotuneモードで使用する事により、かなり良い結果が得られたので、その点に関しては以下記事で解説しています。

    Customized code

    今回、ComfyUIの基幹部分に対してコード改造を行い、Distorchと同様の、或いは、少なくとも似たようなRAMに対するモデルロード機能を普遍的に持たせることが出来るか、という試みを行いました。

    現時点、Flux1の特に2回目以降の生成に関しては、従前環境よりも更に高速化が得られたので、現時点で一度暫定版として公開します。

    ベンチマーク方法は、以下記事と同じです。VRAM消費量が12GBを超えてくるMulti ControlNet付のFlux1.devでテストしています。2回目3回目で70秒前後という従前対比で大幅な高速化を実現しています。
    (以下記事のWaveSpeed版では最適化しても100秒以上はかかった。勿論今回のテストもWaveSpeedは使用している)

    画像

    問題は、Flux1以外の環境で互換性を維持できるかですが、現時点では問題は起きていません。前回記事で記述している問題は、この改造を行う以前に既に生じていました。

    例によって、Cursor先生にコードを作ってもらった訳ですが、発想のベースとなっているIssueは、Githubの以下の2つです。ベースはそもそも、A1111で採用されているコードから来ているらしいですが。

    ※ComfyUIフォルダ直下に設置

    ※ComfyUI\comfyフォルダの同名ファイルに置き換え

    utils.pyファイル修正後のdef load_torch_file関数は以下のようになります。

    def load_torch_file(ckpt, safe_load=False, device=None):
        if device is None:
            device = torch.device("cpu")
        if ckpt.lower().endswith(".safetensors") or ckpt.lower().endswith(".sft"):
            try:
                load_options = {'device': device.type}
                try:
                    import yaml
                    import os
                    config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.yaml')
                    if os.path.exists(config_path):
                        with open(config_path, 'r') as f:
                            config = yaml.safe_load(f)
                            if config and isinstance(config, dict):
                                if 'safetensors_load_options' in config:
                                    load_options.update(config['safetensors_load_options'])
                                elif 'mmap' in config:
                                    load_options['mmap'] = config['mmap']
                except Exception as e:
                    logging.warning(f"Failed to load config.yaml: {e}")
    
                try:
                    if 'mmap' in load_options and not load_options['mmap']:
                        # mmapを無効化する場合はload()を使用
                        sd = safetensors.torch.load(open(ckpt, 'rb').read(), device=device.type)
                    else:
                        # デフォルトまたはmmap:trueの場合はload_file()を使用
                        sd = safetensors.torch.load_file(ckpt, **load_options)
                except TypeError as e:
                    if 'mmap' in load_options:
                        del load_options['mmap']
                    sd = safetensors.torch.load_file(ckpt, **load_options)
                except Exception as e:
                    if len(e.args) > 0:
                        message = e.args[0]
                        if "HeaderTooLarge" in message:
                            raise ValueError("{}\n\nFile path: {}\n\nThe safetensors file is corrupt or invalid. Make sure this is actually a safetensors file and not a ckpt or pt or other filetype.".format(message, ckpt))
                        if "MetadataIncompleteBuffer" in message:
                            raise ValueError("{}\n\nFile path: {}\n\nThe safetensors file is corrupt/incomplete. Check the file size and make sure you have copied/downloaded it correctly.".format(message, ckpt))
                    raise e
            except Exception as e:
                raise e
        else:
            if safe_load or ALWAYS_SAFE_LOAD:
                pl_sd = torch.load(ckpt, map_location=device, weights_only=True)
            else:
                pl_sd = torch.load(ckpt, map_location=device, pickle_module=comfy.checkpoint_pickle)
            if "global_step" in pl_sd:
                logging.debug(f"Global Step: {pl_sd['global_step']}")
            if "state_dict" in pl_sd:
                sd = pl_sd["state_dict"]
            else:
                if len(pl_sd) == 1:
                    key = list(pl_sd.keys())[0]
                    sd = pl_sd[key]
                    if not isinstance(sd, dict):
                        sd = pl_sd
                else:
                    sd = pl_sd
        return sd

    尚。修正前の初期状態では以下のようになっています。

    def load_torch_file(ckpt, safe_load=False, device=None):
        if device is None:
            device = torch.device("cpu")
        if ckpt.lower().endswith(".safetensors") or ckpt.lower().endswith(".sft"):
            try:
                sd = safetensors.torch.load_file(ckpt, device=device.type)
            except Exception as e:
                if len(e.args) > 0:
                    message = e.args[0]
                    if "HeaderTooLarge" in message:
                        raise ValueError("{}\n\nFile path: {}\n\nThe safetensors file is corrupt or invalid. Make sure this is actually a safetensors file and not a ckpt or pt or other filetype.".format(message, ckpt))
                    if "MetadataIncompleteBuffer" in message:
                        raise ValueError("{}\n\nFile path: {}\n\nThe safetensors file is corrupt/incomplete. Check the file size and make sure you have copied/downloaded it correctly.".format(message, ckpt))
                raise e
        else:
            if safe_load or ALWAYS_SAFE_LOAD:
                pl_sd = torch.load(ckpt, map_location=device, weights_only=True)
            else:
                pl_sd = torch.load(ckpt, map_location=device, pickle_module=comfy.checkpoint_pickle)
            if "global_step" in pl_sd:
                logging.debug(f"Global Step: {pl_sd['global_step']}")
            if "state_dict" in pl_sd:
                sd = pl_sd["state_dict"]
            else:
                if len(pl_sd) == 1:
                    key = list(pl_sd.keys())[0]
                    sd = pl_sd[key]
                    if not isinstance(sd, dict):
                        sd = pl_sd
                else:
                    sd = pl_sd
        return sd

    まだ、全ての環境通してのテストの最中なので完成版とは言い難いのですが、Flux1に関しては想像以上の効果がありました。

    一方、既に同様の思想で高速化しているFunyuan VideoとSkyReelsに関しては、ほとんど効果がありませんでした。これは当たり前で、Distorchで既に同じような理屈の事をやってる訳ですから。

    For Hunyuan Video&Skyreels

    さて、Hunyuan VideoとSkyreelsに関しては効果がない処か、やはりDistorchの機能的に被るだけに懸念されていた反作用が生じる事がわかりました。

    つまり、私がこれまで組んできたDistorchを使用するjsonと本記事で解説しているLike Distorchを併用すると、寧ろVRAM消費量が増大します。

    よって、Hunyuan VideoとSkyreelsを使用する場合は、Like Distorchを無効化すべきで、この場合、起動バッチファイルの冒頭に以下二行を追加します。

    SET SKIP_CONFIG_YAML=1
    SET SAFETENSORS_MMAP=true

    しかし、この後、SD1.5においては、この環境変数を以てしても、モデルロードの際にエラーを起こすことがわかりました。

    MultiGPUノードに拠る同等の効果

    2025年3月8日、以下きまま / Easygoingさんの記事を読み、急転直下、ここまで試行錯誤してきたアプローチとほぼ同等の効果をMultiGPUの設定で得られることがわかりました。
    (新しいClipファイルについても重要な記載があります)

    結論、冒頭描いているように本記事の内容は完全になくても良い訳ですが、試行錯誤のプロセス保存の意味で残しておきます。

    具体的な設定方法については、以下記事で解説していますが、これまで見落としていたclip周りのノードの設定変更によって、上記、コード改造によって実現しようとしていた事とほぼ同じことが、互換性の問題を起こさずにできる事がわかりました。

    きまま / Easygoingさんが記事を起こしてくださらねば、気づかぬままだったかもしれません…。

    一言で言えば、「Distorchに気を取られ過ぎた」…のが反省点です。Distorch機能を伴わない各種ローダーの存在は勿論気づいていましたが、その使い方をもっと試行錯誤すれば良かったのですがね。

    Topaz Photo AI

    …

    一方、私が多用する有料の動画高画質化AI(300$はたけえ…)Topaz VideoAIの高速化に関して、以下の動画を見つけました。

    ぶっちゃけ大して変わんないですね…コンマいくつかは速くなった感じですが.…Ryzen9+X870 AM5+DDR5-6400環境によって、X570 AM4環境からほぼ倍速化しているので、そこから更に上乗せはソフト弄った位では難しいようです。

    画像

    ともあれ、X870 AM5化でこれが一番高速化しましたね。

    従前、2時間動画のFHD画質向上&60fps化で、一晩で終わらなかったのでね…大体10時間から、下手すると15時間かかってましたからね。

    これが、大体5時間で終わるので、寝てる間に2時間動画なら2本いけますね。これは作業効率から大きいですよ。

     
     

    GJL

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

    あなたへのおすすめ