見出し画像

How to fix GetPrototype to GetMessageClass on TensorFlow

いやはや....難易度高かったわ

エンジニアでもないのに、これを何とかした自分を自画自賛したいですよ。

発端は以下記事です。

これはこれで、必要な措置なんですよ。間違っちゃいねえ。
(Protobufを古い4.xに戻せば発生しないエラーだったが、最新の6.31が出ている以上巻き戻して固定する自体が、本来良くない。そもそもエラーの性格自体からしてそういう種類のもの)

ただ、今度はTensorFlow側がついてこられねえ…て問題が発生した訳です。現時点で具体的に動作不良起こす訳じゃないのですが、とにかくエラー表示だけは出ていました。

AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'

最初、簡単かと思ったら、これが性質悪かったわ…

経過自体、Cursor先生に総括してもらいました。途中で、自分が何やってるかわかんなくなるくらい苦戦しましたからね。

ちなみに、以下の流れに入る前に、原因がtensorflowらしい…当たりは付けていましたが、その予測が出来た経緯もまた色々あるのですが、最早覚えきれていないので割愛します。

その凡その当たりがあってこそ、以下の流れに持っていくことが出来たのです。その当たりを付けられた…理由は、身も蓋もなく経験値、経験則的なものです。

承知しました。これまでの経緯と、なぜ一連の作業が必要だったのかを最初から要約します。


今回の目的

ComfyUI 起動中や特定の操作中に発生していた、以下のエラーを解消すること。

`AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'`

エラーの根本原因

  1. `GetPrototype` とは?

    • これは Google の Protocol Buffers(protobuf) というライブラリで使われていたメソッド名です。

    • `MessageFactory` クラスの一部として存在し、特定のメッセージタイプの「プロトタイプ」(ひな形となるインスタンス)を取得するために使われていました。

  2. なぜエラーになったのか?

  3. protobuf ライブラリはバージョンアップを重ねており、protobuf 4.x 以降(現在の 6.x も含む)で、`GetPrototype` メソッドは非推奨または削除されました。
    (但し、実際にはProtbuf5.29.4以前でこのエラーは出ない。6.31で初めて出る)

  4. 代わりに `GetMessageClass` などの新しいメソッドを使うように変更されました。

  5. 今回の環境には新しいバージョンの protobuf (6.31.0) がインストールされていましたが、ComfyUI 環境内のどこか(ComfyUI本体、カスタムノード、あるいは他の依存ライブラリ)が、古いバージョンの protobuf を前提としたコードを持っており、存在しない `GetPrototype` メソッドを呼び出そうとしてエラーが発生していました。


エラー発生箇所の特定を試みた経緯

  1. `GetPrototype` を使っているコードを探す

    • エラーメッセージは Python の `AttributeError` でしたが、Pythonコードのどこで `GetPrototype` が呼ばれているか特定する必要がありました。

    • まず、Pythonライブラリ(site-packages)や ComfyUI本体の Python ファイル(`.py`)を対象に `GetPrototype` を全文検索しました。

    • しかし、該当する Python コードは見つかりませんでした。

    • 次に、検索対象を C++ ヘッダファイル(`.h`)や他のテキストファイルに広げたところ、TensorFlow に含まれる protobuf の C++ ヘッダファイルに `GetPrototype` の記述が多数見つかりました。これは、protobuf の内部 C++ 実装で使われている名前であることを示していましたが、直接エラーの原因となる Python コードの場所は特定できませんでした。

  2. 結論

    • Python コード内で直接 `MessageFactory.GetPrototype` と書かれている場所は特定できませんでしたが、何らかのコード(おそらく古いカスタムノードやライブラリ)が、新しい protobuf バージョンと互換性のない方法で `MessageFactory` を使おうとしていることが原因と判断しました。


解決策の検討と採用

  1. protobuf のバージョンを下げる

    • エラーが出ない古いバージョン(例: 3.x系)に戻す方法です。これは最も簡単な解決策の一つですが、ユーザー様が「古い protobuf に下げるは出来ない」とのことでしたので、この方法は採用しませんでした。

  2. エラーを出すコードを修正する

    • もしエラーを出しているコードが特定できれば、その中で `GetPrototype` を `GetMessageClass` に書き換えるのが理想です。しかし、コードの場所特定が難しく、特にカスタムノードの場合は自分で修正するのが難しい場合もあります。

  3. Monkey Patch を使う(採用した方法)

    • エラーを出しているコードを修正することなく、新しい protobuf ライブラリに一時的に `GetPrototype` メソッドを追加してしまう方法です。

    • `GetPrototype` が呼ばれたら、内部的に新しい `GetMessageClass` を実行するようにします。

    • これにより、古いコードは `GetPrototype` を呼んでもエラーにならず、新しい API (`GetMessageClass`) の機能を使うことができます。


Monkey Patch の実装と ComfyUI への適用手順

  1. パッチコードの作成:

    • `monkey_patch_protobuf.py` という Python ファイルを作成しました。

    • このファイルには、`MessageFactory` クラスに `GetPrototype` メソッドを追加するコードが書かれています。

  2. ComfyUI 起動前にパッチを適用する方法の検討:

    • ComfyUI が起動し、エラーが出るコードが実行されるに、`monkey_patch_protobuf.py` を import する必要がありました。

    • 当初、ComfyUI の `main.py` の先頭に `import monkey_patch_protobuf` を追加することを考えましたが、ComfyUI の起動方法によっては難しいため、別の方法を検討しました。

    • Python の `runpy` モジュールを使って、パッチファイルを import した後に ComfyUI の `main.py` をモジュールとして実行する方法を採用しました。

  3. 起動スクリプトの作成:

    • `run_comfyui_with_patch.py` という Python ファイルを作成しました。

    • このファイルは、`monkey_patch_protobuf` を import した後、`runpy.run_module('main', run_name='main')` で ComfyUI 本体を起動します。

    • 当初 `'ComfyUI.main'` と指定して `ModuleNotFoundError` が発生しましたが、`sys.path` の調査から、ComfyUI の埋め込み Python 環境では `D:\USERFILES\ComfyUI\ComfyUI` がモジュール検索パスの先頭に来るため、`main.py` を `'main'` という名前で直接 import できることが分かりました。

  4. ファイルの配置:

    • Python が `run_comfyui_with_patch.py` と `monkey_patch_protobuf.py` を見つけられるように、これらを `D:\USERFILES\ComfyUI\ComfyUI` ディレクトリに配置しました。(このディレクトリが `sys.path` の先頭に来るため)

  5. バッチファイルの作成:

    • ComfyUI を起動するための `start_comfyui_with_patch.bat` を作成しました。

    • このバッチファイルでは、環境変数の設定、`PYTHONPATH` の設定(`D:\USERFILES\ComfyUI\ComfyUI` を指定)、そして絶対パスを使って `run_comfyui_with_patch.py` を Python で実行するコマンドを記述しました。

    • パスの指定ミス(相対パスの問題)や、エラーが確認できない問題(`pause` の追加)などがありましたが、最終的に絶対パスを使った信頼性の高いバッチファイルになりました。


結果

作成した `monkey_patch_protobuf.py` と `run_comfyui_with_patch.py` を `D:\USERFILES\ComfyUI\ComfyUI` ディレクトリに置き、`start_comfyui_with_patch.bat` を実行することで、monkey patch が適用された状態で ComfyUI が起動するようになりました。

これにより、Protocol Buffers の `AttributeError: 'MessageFactory' object has no attribute 'GetPrototype'` というエラーは発生しなくなる(または、エラーを出していた古いコードが正しく動作するようになる)ことが期待されます。


これが、一連の作業で行ったことと、それぞれのステップが必要だった理由の要約です。
何か不明な点があれば、遠慮なく質問してください。

上にある「検出」自体から、壮絶に苦戦しました。だって、見つかんねえんもんよ、該当する部分が。結局、以下のスクリプトを自作しています。

検出の為に使用したスクリプト

import os

search_dir = r"D:\USERFILES\ComfyUI"
keyword = "GetPrototype"
# 検索したい拡張子をリストで指定
target_exts = [
    ".py", ".txt", ".json", ".yaml", ".yml", ".md", ".ini", ".cfg", ".toml",
    ".js", ".ts", ".c", ".cpp", ".h", ".hpp"
]

for root, dirs, files in os.walk(search_dir):
    for file in files:
        if any(file.endswith(ext) for ext in target_exts):
            file_path = os.path.join(root, file)
            try:
                with open(file_path, encoding="utf-8", errors="ignore") as f:
                    for i, line in enumerate(f, 1):
                        if keyword in line:
                            print(f"{file_path}:{i}: {line.strip()}")
            except Exception as e:
                print(f"Error reading {file_path}: {e}")

結局Python部分にはGetPrototypeの定義がなかったんですよ。やっと見つかったのが以下です…いや、わかんねえだろうよ、これは。

D:\USERFILES\ComfyUI\search_getprototype.py:7: keyword = "GetPrototype"
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\dynamic_message.h:128: const Message* GetPrototype(const Descriptor* type) override;
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\dynamic_message.h:139: const Message* GetPrototypeNoLock(const Descriptor* type);
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\extension_set.h:811: const MessageLite* GetPrototypeForLazyMessage(const MessageLite* extendee,
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:641: // factory->GetPrototype(field->message_type()) MUST return an instance of
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:1286: // Some implementations do not support all types.  GetPrototype() will
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:1291: virtual const Message* GetPrototype(const Descriptor* type) = 0;
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:1295: //   MessageFactory::generated_factory()->GetPrototype(
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:1299: // pool, GetPrototype() will return nullptr.  (You can also check if a
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:1303: // This factory is 100% thread-safe; calling GetPrototype() does not modify
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\message.h:1310: // time, to be placed in generated_factory.  The first time GetPrototype()
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\reflection.h:208: reflection->GetMessageFactory()->GetPrototype(field->message_type());
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\reflection.h:269: reflection->GetMessageFactory()->GetPrototype(field->message_type());
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\compiler\cpp\service.h:103: void GenerateGetPrototype(RequestOrResponse which, io::Printer* printer);
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\compiler\java\service.h:108: void GenerateGetPrototype(RequestOrResponse which, io::Printer* printer);
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\io\coded_stream.h:477: // GetPrototype() MUST return non-NULL for any Descriptor which can be found
D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\tensorflow\include\google\protobuf\io\coded_stream.h:483: // GetPrototype() is given a Descriptor which resides in
(base) PS D:\USERFILES\ComfyUI>

で、これを踏まえて、上の流れの対策に向かっていく訳です。

久々に出ましたよ、monkey patch…以前reForgeの警告対策で作ったんですよね、それ以来だ。一応、下記に公開しておきます。

# monkey_patch_protobuf.py
from google.protobuf.message_factory import MessageFactory

def get_message_class(self, descriptor):
    return self.GetMessageClass(descriptor)

MessageFactory.GetPrototype = get_message_class
import monkey_patch_protobuf
import runpy
runpy.run_module('main', run_name='__main__') # ← この3行目が 'main' になっているか確認

改めて、こんな捻くれた対策を捻り出してくれたCursor先生、改めてすげえよ…エンジニアでもない私が、これを何とか出来てしまう訳ですから。

ただ、この方向にCursor先生を誘導したのは、私の「聞き方」…つまりプロンプトエンジニアリングの技術ではある訳ですよ。そこは自画自賛していい部分でしょうよ。

対策以前の方法で起動すると以下の様になる訳です。エラー自体がソースを特定してねえんだもん…これはわかんねえよ。

尚、対策後のバッチファイルは以下です。以下は、Flux1用ですね。前述しているように、私はtorch.compile最適化の為に、Flux1とHiDream、動画それぞれモデルごとに環境を切り替えています。
(SDXLではtorch.compileを使用しない為、どの環境でも共通に使える。但し、SD1.5はsage-atttentionが未対応(但し完走はする)の為、xformers専用環境を別途用意している)

set PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:32,garbage_collection_threshold:0.8
set TORCH_COMPILE_DEBUG=0
set TORCH_COMPILE_OPT_LEVEL=2
set TORCH_LOGS=recompiles,dynamo,inductor
set TORCH_INDUCTOR_VERBOSE=1
set TORCH_DYNAMO_CONFIG=force_parameter_static_shapes=false
set TRITON_CACHE_DIR=D:\USERFILES\Flux1\triton
set TORCH_HOME=D:\USERFILES\Flux1\torch
set TORCHINDUCTOR_CACHE_DIR=D:\USERFILES\Flux1\Inductor
set TEMP=D:\USERFILES\Flux1\Temp
set PYTHONPATH=D:\USERFILES\ComfyUI\ComfyUI

D:\USERFILES\ComfyUI\python_embeded\python.exe -m pip install -U protobuf

D:\USERFILES\ComfyUI\python_embeded\python.exe D:\USERFILES\ComfyUI\ComfyUI\run_comfyui_with_patch.py --use-sage-attention --windows-standalone-build --output-directory D:\USERFILES\A1111\outputs\ComfyUI

尚、TensorFlow側が完全にGetMessageClassロジックに切り替われば、この対策は不要になります。いつになるかわかりませんけどね。少なくとも最新の2.19.0では、まだ未対応という事です。

今日のBGMは…完全感覚Dreamerを止める止める言いつつ、結局優しいTAKAさんなので、最近まで最後にやり続けていましたが、遂に止めましたね、本当に。そして、代わりに勿論これです。

ONE OK ROCK: Wasted Nights

最早、代名詞と化してた名曲…だけど、前を向き、前に進み続けるTAKAさんの性格からしても、いつまでも「此処」で時間が止まったようにイメージ縛られているのは好きじゃないでしょう。

モーニング娘。だって、いつまでもLOVEマシーンとTHEぴーすだけで語られてたら、こっちとしても「お前いつまで、そこで時計止まってんだよ」、て思いますからね。

ONE OK ROCK - 完全感覚Dreamer [THIS IS MY BUDOKAN ?! 2010.11.28]

ただ、これが名曲である事には違いありません。この一曲だけでも彼らは音楽史に名前が残ります。

彼らと親交があるRADWIMPSの洋次郎さんも、彼ら中での好きな曲として「べただけど」と言いつつこれを上げています。理由が「リズム」という理由はよくわかるんですよ。ヒルクライムは嫌いですが、ヒルクライムでは、私はこれにリズムがとにかく合います。

勿論、既に購入済みですが、まだローラー台と合わせたトレーニングはしていません。もっと肺を戻さないと、こっちの心臓がついてこられない。

ヒルクライムで合わせる曲...は、あとは私はこれですね。

2000年代初頭にVロック界で一世を風靡した中島卓偉さんの名曲「イノヴェイター」。当時はTAKUI名義でした。2010年代には、ハロプロにも深く関わることになりました。

もう一つは、やっぱこれか…とにかくリズムが良いんですよ

モーニング娘。'14 『What is LOVE?』(Dance Shot Ver.)

つんく♂さんの曲にしては、リズムは結構素直です。あの人の転調効きまくった捻り効いてるリズムの曲も、それはそれで好きですが、ヒルクライムで心臓にも脚にも負荷かけてる時に、あんま捻られてもこっちが付いていけねえんですよ。ヒルクライムで大事なのはとにかくリズム…ロードバイク全般そうですけど、登りには登り也のリズムってものがあるんですよ。


いいなと思ったら応援しよう!