コンテンツにスキップ

ワーカーによるノードの分離 (Node Isolation with Workers)

このページは、ライブラリを分離(Isolated)して実行するための運用ガイドです。専用の Python サブプロセス内で実行することで、ライブラリが固定している依存関係(torch、transformers、diffusers など)が他のライブラリと衝突(コンフリクト)するのを防ぎます。ユーザー(アーティスト)はエディタ内の 共有 / 分離 (Shared / Isolated) ドロップダウン(ライブラリ を参照)でこれを選択します。内部的には、分離されたライブラリは ワーカー (Worker) サブプロセス上で動作します。このページでは、ライブラリ作者側の観点からこの仕組みを説明します。分離時の設計ミスを検知するルール一覧については、ストリクトモードリファレンス (Strict Mode Reference) を参照してください。

用語 (Vocabulary)

このページ全体で使用されるいくつかの用語:

  • オーケストレーター (Orchestrator) — メインの Griptape Nodes Python プロセス。フローグラフ、接続、パラメータレジストリ、設定、シークレット(認証情報)を管理・所有します。エディタはオーケストレーターと直接通信します。
  • ワーカーサブプロセス (Worker subprocess) — ライブラリのノードを実行する独立した Python プロセス。ワーカーモードを選択した各ライブラリに専用のプロセスが割り当てられます。ワーカーは WebSocket 接続(バス (Bus))を介してオーケストレーターと通信します。
  • process と aprocess — ノードの実行メソッド。従来どおり process(self) -> ... を実装すると、フレームワークがそれを async def aprocess(self) -> None でラップし、ワーカーのイベントループ上で実行できるようにします。ストリクトモードのルールでは「aprocess の内部から」という表現が使われますが、実際には「自身が実装した process メソッドの内部から」を意味します。
  • スキーマプローブ (Schema probe) — ライブラリ読み込み時にワーカーが登録された各ノードクラスを一度だけインスタンス化し、パラメータのレイアウトを検出する初期化パス。実行リクエストが届く前に __init__ が実行されます。

オプトインすべきか? (Should I opt in?)

次の場合にオプトイン(分離モードを選択)してください: ライブラリが特定の重量級 ML パッケージ(torch、transformers、diffusers、accelerate、peft、controlnet-aux、カスタム CUDA ホイールなど)の特定バージョンに固定(Pin)されており、異なるバージョンに依存する他のライブラリと共存させたい場合。ワーカーモードは、ライブラリ間での依存関係分離を実現するメカニズムです。

次の場合にはオプトイン不要(除外)です: ライブラリが軽量で幅広い互換性を持つパッケージ(標準ライブラリ、pydantic、griptape 自体、一般的な HTTP / YAML / JSON ツール群など)のみを使用している場合。オーケストレータープロセス内で実行することで、後述するプロセス間シリアライズのオーバーヘッドを回避できます。

判断に迷う場合の安全なデフォルトは「オプトイン」です — オーバーヘッドは発生しますが小さく、後からユーザーが同じ環境に別の重量級ライブラリをインストールした際のリスクを防ぐことができます。

オプトイン方法 (How to opt in)

ワーカーホスティングは、griptape-nodes-library.json 内の metadata.declarations に 2 つの宣言を記述することで設定します。これらは 2 つの異なる目的に対応するため、並行して設定します:

  • worker_mode_compatibility — ライブラリがワーカーホスティングと 互換性があるか を指定します。フィールドは compatibility の 1 つです:
    • COMPATIBLE: ライブラリはオーケストレータープロセスまたは専用ワーカーサブプロセスのどちらでも実行可能です。
    • INCOMPATIBLE: ライブラリはオーケストレータープロセスでのみ動作し、ワーカー上でホストしてはなりません。
  • suggested_worker_mode — 他のオーバーライドがない場合にライブラリがどこで 起動するか を指定します。フィールドは mode(ORCHESTRATOR または WORKER)の 1 つです。宣言を省略した場合はエンジンのデフォルト(現在はオーケストレーター)が適用されます。エディタにはライブラリごとの Shared / Isolated ドロップダウン(Shared = オーケストレーター、Isolated = ワーカー)があり、ユーザーが COMPATIBLE なライブラリのモードを切り替えられます。この宣言は、そのドロップダウンの初期推奨値となります。

両方の宣言を省略した場合、compatibility=COMPATIBLE を持つ worker_mode_compatibility を宣言し、suggested_worker_mode を指定しなかった場合と同等になります — ライブラリはワーカーモードに対応していますが、明示的な切り替え要求があるまではオーケストレーター内で起動します。

compatibility=INCOMPATIBLE と suggested_worker_mode の WORKER を同時に宣言することは矛盾しているため、ライブラリのメタデータ検証で拒否されます。

{
    "name": "My Library",
    "library_schema_version": "0.10.0",
    "metadata": {
        "author": "<Your Name>",
        "description": "<Description>",
        "library_version": "0.1.0",
        "engine_version": "0.85.0",
        "tags": ["AI", "Custom"],
        "declarations": [
            {
                "type": "worker_mode_compatibility",
                "compatibility": "COMPATIBLE"
            },
            {
                "type": "suggested_worker_mode",
                "mode": "WORKER"
            }
        ],
        "dependencies": {
            "pip_dependencies": [
                "torch==2.4.1",
                "transformers==4.45.2"
            ],
            "pip_install_flags": [
                "--extra-index-url",
                "https://download.pytorch.org/whl/cu121"
            ]
        }
    },
    "categories": [],
    "nodes": []
}

ワーカーモードによる厳格な環境分離は、特定のホイールを固定(Pin)した場合にのみ効果を発揮します。緩い torch>=2.0 のような指定では pip が見つけた任意のバージョンが解決され、開発者の環境とユーザーの環境でバージョンが乖離する原因になります。torch>=2.0 ではなく torch==2.4.1 のように固定してください。pip_install_flags は、インデックス URL やインストールに必要な追加引数を渡すためのエスケープハッチです。

スキーマの定義場所: library_declarations.py 内の WorkerModeCompatibility および SuggestedWorkerMode、および library_registry.py 内の Dependencies。

トレードオフ・制限事項 (What you give up)

プロセス間シリアライズのオーバーヘッドが発生します。ワーカー側のノードがノード実行中にオーケストレーターが保持する状態(フローグラフ、接続、パラメータレジストリ、設定、シークレット)をコールバックで取得しようとすると、そのリクエストは WebSocket バス経由で転送されます。各呼び出しはネットワーク往復を伴い、返されるデータは呼び出し時点でのスナップショット(stale-by-call)となるため、ワーカーが読み取る頃にはオーケストレーター側の状態が変化している可能性があります。

設計上の 2 つの重要な実践原則:

  • データはパラメータ経由でノードに渡し、実行中にフローの状態を直接フェッチしないこと。 process(または同じスコープで入力ハイドレーション中に実行される before_value_set / after_value_set)の内部から接続や隣接ノードの状態を読み取ることは可能ですが、読み取りごとにオーケストレーターへの往復通信が発生し、取得した情報は届いた瞬間から古くなっている可能性があります。
  • リクエストが公認のプロセス境界となります。 読み取り・書き込みともに、対応するリクエスト(SetParameterValueRequest、AddParameterToNodeRequest、RemoveParameterFromNodeRequest など)を発行すれば、エンジンが往復処理を正しく処理します。

ノード実行外(ライブラリ読み込み時やブートストラップ時)に発行されたリクエストは転送されません — その時点ではワーカーはまだオーケストレーターに接続されていないためです。__init__ からのバス呼び出しはワーカー自身のイベントループに再突入(リエントラント)してしまいデッドロックの原因となるため、__init__ には専用のストリクトモードルールが設けられています(次節参照)。

シリアライズできない値の受け渡し (Passing values that cannot be serialized)

Diffusers パイプライン、潜在テンソル(Latent Tensor)、アクティブなドライバーなどはデータ形式(JSON など)を持たないため、ワーカーとオーケストレーターの間をパラメータ値として直接移動させることはできません。生成側の出力パラメータに serializable=False を指定すると、エンジンはオブジェクトを生成したプロセス内にそのまま保持し、不透明なキーのみを転送します。消費側のノードは特別な宣言をせず、通常どおりオブジェクトを読み取ることができます。GPU メモリを保持するオブジェクトの解放フックや、コンテナ・複数ライブラリ間にまたがる接続の制限事項など、詳細については シリアライズできない値の受け渡し (Passing Values That Cannot Be Serialized) を参照してください。

把握しておくべきライフサイクルの変更 (Lifecycle changes you need to know)

__init__ はライブラリ読み込み時に実行される

ワーカーサブプロセスは起動時に、オーケストレーターに提供するパラメータスキーマを抽出するため、登録された各ノードクラスを一度だけインスタンス化します。このことによる 3 つの留意点:

  • __init__ 内での I/O 処理は厳禁です。 ネットワーク呼び出し、認証チェック、ディスク読み取り、データベース接続などはすべてライブラリの読み込みをブロックします。スキーマプローブにはタイムアウト制限があり、__init__ で例外が発生したりタイムアウトしたりしたクラスは、警告ルールを発行することなくエクスポート対象ライブラリから暗黙的に除外されます。I/O 処理は process やインスタンス構築後に実行されるライフサイクルフックに移動してください。
  • __init__ 内でのイベントバス呼び出しは厳禁です。 スキーマプローブ中にバスへ再突入すると、ワーカーがデッドロックします。reentrant-bus-in-init の正確性ルールによってそのクラスは検証エラーとなり、正確性違反であるためライブラリのスキーマからも除外されます。
  • パラメータの宣言は __init__ 内で行うのが標準パターンです。 ここでの self.add_parameter(...) は完全に問題ありません — スキーマプローブは、ノードがパラメータリストを定義するための唯一の公式な場所です。

ExecuteNodeRequest ごとに新しいノードが生成される

ワーカーはリクエストのメタデータから一時的なノードを生成し、process を実行した後にそれを破棄します。ノードは呼び出し間でメモリ上の内部状態を一切保持しません。

値を受け渡すためのサポートされているパターン:

  • 入力パラメータ は各実行の開始時に self.parameter_values に格納され、オーケストレーターの正幹データからハイドレーション(復元)されます。process の内部でこれらを読み取ります。前回の呼び出し時の値が残っていると仮定してはいけません。
  • 出力パラメータ は self.parameter_output_values に格納します。フレームワークは process が復帰した後にこれらをオーケストレーターへ送り返します。process 内で self.parameter_output_values["my_param"] = value を代入してください。
  • 実行間で永続化が必要な状態 はオーケストレーターに属します。正幹の値を更新するには、process 内から SetParameterValueRequest を発行してください。次回の実行時には、その新しい値が self.parameter_values にハイドレーションされます。実行の途中で self.parameter_values[k] = v を直接変更して状態を引き継ごうとしないでください — その変更はオーケストレーターへ伝播しません。

動作しないこと: self.foo = ... のようにインスタンス変数を設定し、次回の実行時にそれが残っていることを期待すること。次回の実行では完全に新しいノードインスタンスが生成されます。

実行中のパラメータリストの直接変更は伝播しない

process(または aprocess)の内部から self.add_parameter(...) や self.remove_parameter_element(...) を呼び出しても、それはワーカー側の一時的なノードインスタンスにのみ適用されます。オーケストレーターの正幹コピーには変更が一切反映されません。

実行中にパラメータを変更したい場合は、リクエストバスを経由してください:

  • パラメータを追加するには AddParameterToNodeRequest
  • パラメータを削除するには RemoveParameterFromNodeRequest

process 内から GriptapeNodes.handle_request(...) を通じてリクエストを発行します。ハンドラー側の処理により、変更がオーケストレーターに伝播されます。実行中に直接変更を行おうとすると parameter-mutation-during-aprocess ルールが発動し、どちらを使用すべきかが通知されます。

変更が反映されるのはオーケストレーター側のノードであり、現在実行中のノードインスタンスではありません。 これはシステム全体の基本原則(オーケストレーターが正幹ノードを保持する)と同じですが、明記しておくべき重要な帰結があります:パラメータを追加したその実行の内部では、追加したパラメータを読み戻すことはできません。リクエストが成功し、エディタ上ですでにパラメータが表示されていても、ワーカー内での self.get_parameter_by_name("new_param") は None を返します。

次回の実行時にも自動的には再出現しません。実行ごとにノードクラスから新しいワーカー側コピーが構築されるため、パラメータの構造は引き継がれず、値のみが引き継がれるためです。

これが設計契約(コントラクト)です:ノードのパラメータ構造は、そのパラメータ値の決定論的関数(Deterministic Function)でなければなりません。 パラメータは __init__ で作成するか、値が設定された際の値フック(Value Hook)から作成・削除してください — これは Diffusers VAE デコーダーが採用しているパターンであり、パイプラインの値が設定されるたびに出力パラメータを再構築します。このように記述された構造は同期処理を一切必要としません:エディタ上での編集時にもオーケストレーター側のノードで同じ導出処理が走り、新しいワーカーコピーで値がハイドレーションされる際にも再実行されるため、すべてのコピーが同じ形状に収束します。リクエストや前回のセッションの process など、過去の何らかの処理によって一度追加されただけの構造は「履歴」にすぎず「導出」ではないため、引き継がれません。

ハイドレーション側でもこのコントラクトが尊重されます。構造が安定するまでパスが繰り返し適用されるため、ある派生パラメータの値が、それを導出する値よりも先に届いた場合でも正しく機能します(派生パラメータのフックがさらに次のパラメータを生成するような連鎖も含みます)。どのフックからも導出されないパラメータの値(ユーザーがエディタで直接追加した場合など)は、そのパラメータ名を指定した警告ログを出力した上でその回の実行では適用されずにスキップされます。実行自体が失敗することはなく、オーケストレーター上の正幹値も保持されます。

値フック:ライブラリの種類による挙動の違い

実行時依存関係ライブラリ (Execution-dependency libraries)(pip_dependencies_exec を宣言するもの)は、オーケストレーター上に本物のノードクラスを保持するため、ユーザーが値を編集したときに before_value_set / after_value_set がオーケストレーター側で発火します(Shared ライブラリと全く同じ動作)。ドロップダウンの変更に応じてフィールドを表示/非表示にしたり、リストを拡張したりといったユーザー入力に応じたパラメータリスト調整フックは、編集時に正常に動作します。

これらのフックの実行時の動作について知っておくべき 2 つの点:

  • 入力が適用される際、ワーカー内でも実行されます。オーケストレーターは変更されていない値に対するフックをスキップしますが、ワーカーのノードは新しく構築されるため、すべての値が新規とみなされ、すべてのフックが実行されます。フックは軽量かつ冪等(Idempotent)に保ってください。
  • ワーカー内でノードオブジェクトに対して行った変更は、一時ノードの破棄とともに破棄されます。実行中のフックからパラメータリストを変更する場合は前述のルールに従ってください:永続化が必要な場合はリクエストバスを経由し、その同じ実行内ですぐに読み戻せることを期待しないでください。

レガシーワーカーモードライブラリ (Legacy worker-mode libraries)(worker_mode_override または suggested_worker_mode によって Isolated モードが選択されたもの)は挙動が異なります。オーケストレーターはノードクラスのスタブ(コードを含まないパラメータのみの定義)しか保持しないため、ユーザーが値を編集してもオーバーライドが呼び出されることはありません。process の直前に入力が適用される際にフックが実行されるため、入力値の変換自体は機能しますが、その中で行われたパラメータリストの変更は一時ノードとともに破棄されます(parameter-mutation-during-aprocess ルールをすり抜け、オーケストレーターにも伝播しません)。これらのライブラリでは、パラメータリスト全体を __init__ で静的に定義してください。値フックをオーバーライドすると、ライブラリ読み込み時に value-hooks-execute-only-on-worker 警告が発行されます。

分離モードでは接続フックが決して発火しない

after_incoming_connection、after_outgoing_connection、allow_* バリデータ、および *_removed 系のフックは、接続が変更された際にオーケストレーター上で呼び出されます — 接続はオーケストレーターが管理する状態であり、ワーカーへの問い合わせは行われません。ワーカーホストされたライブラリの場合、これらの呼び出しはスタブクラスに対して行われるため、記述したオーバーライドは何も実行されません(通知なしに無視されます)。接続フックをオーバーライドすると、ライブラリ読み込み時に connection-hooks-inert-on-worker 警告が発行されます。ノードが接続線の状態に動的に反応する必要がある場合(動的パラメータパターンなど)は、ライブラリを Shared モードで実行してください。

パラメータの converters、validators、traits はオーケストレーターに渡らない

スキーマプローブがライブラリをエクスポートする際、オーケストレーター側のスタブクラス用には Parameter のスカラーフィールド(名前、型、デフォルト値、ツールチップ、許可されたモード)のみがシリアライズされます。Parameter にアタッチしたカスタム converters、validators、traits は転送されません — これらはワーカーのプロセス内にのみ存在し、ワーカーがノードを実行する際にのみ機能します。

オーケストレーター側のスタブはこれらのパラメータに対するユーザー入力を受け付け、値をワーカーへ送信しますが、オーケストレーター側の UI は converters / validators / traits を再実行してエディタ上で事前に値を調整したり検証・拒否したりすることはできません。これはライブラリ読み込み時に parameter-behaviors-dropped-in-schema 警告として表示されます。

2 つの実用的な対処パターン:

  • バリデーションや変換処理を process 内に移動する。 ワーカーが実際の値に対して処理を再実行します。デメリットは、エディタ上でインラインの入力エラーをユーザーに表示できず、ノードを実行した時点で初めてエラーが表示される点です。
  • UI の装飾機能としてオーケストレーター側での欠落を許容する。 コンバーターが単なる表示上の整形(文字列の先頭大文字化など)である場合、オーケストレーター側で失われても実害はありません。

設定、シークレット、現在のプロジェクトは自動的に伝播する

設定やシークレットが変更されると、オーケストレーターは登録済みのすべてのワーカーに対して ReloadConfigRequest と RefreshSecretsRequest を自動的にブロードキャストします。各ワーカーは共有ディスク上のファイルを再読み込みし、インメモリのビューを更新します。特別な配線コードを書く必要はなく、すべて自動的に処理されます。

アクティブな プロジェクト (Project) も同様に伝播します。ワーカーはオーケストレーターと同様に起動し、共有ディスク上の同一設定から現在のプロジェクトを導出するため、新しく生成されたワーカーは最初からオーケストレーターと同じプロジェクトを参照します。起動後にオーケストレーター側でプロジェクトが切り替えられると、その新しいプロジェクトが稼働中のすべてのワーカーにプッシュされるため、環境変数、ディレクトリマクロ、シチュエーション/パスマクロが両プロセス間で常に同一のプロジェクトに対して解決されます。知っておくべき 2 つのケース:

  • ライブラリ設定の変更を伴う切り替えの場合、ワーカーは再起動され、起動時に新しいプロジェクトを再導出します。
  • 「浅い(Shallow)」切り替え(同一のワークスペースおよびライブラリ設定で、環境、ディレクトリ、シチュエーションのみが異なる場合)、ワーカーは再起動されません。オーケストレーターが切り替えをブロードキャストし、ワーカーはキューに入っている次のノード実行よりも前にその場で新しいプロジェクトを採用するため、古いプロジェクトに対して処理が実行されることはありません。

これらについて開発者が何かを実装する必要はありません。ワーカーがプロジェクトの選択を共有設定に書き戻すことはなく、オーケストレーターが常に単一の信頼できる情報源(Single Source of Truth)となります。

繊細な挙動として:OS レベルで設定された環境変数(コンテナによって注入された OPENAI_API_KEY など)は、更新ブロードキャスト や 明示的な削除 をまたいでも保持されます。ワーカーの更新処理は .env ファイルを再読み込みしますが、OS 側で設定された値を上書きすることはなく、ファイルからシークレットを削除しても衝突している OS 環境変数が消えることはありません。

set_secret(...) は、ユーザーの明示的な意図を表すため、OS レベルの値を上書きする唯一の経路です。この非対称性を明示するために、エンジンは WARNING ログを出力します。

「ライブラリの分離対応」チェックリスト

  • [ ] metadata.declarations で compatibility: COMPATIBLE を指定した worker_mode_compatibility が宣言されているか(または宣言を完全に省略しているか — 省略時は COMPATIBLE として扱われます)。また、mode: WORKER を指定した suggested_worker_mode が設定されているか(デフォルトでオーケストレーター内で起動させ、GUI 経由でユーザーにオプトインさせたい場合は省略)。
  • [ ] __init__ が I/O 処理を行わず、イベントバスへのリクエストも発行していないか。
  • [ ] process 内から add_parameter や remove_parameter_element を直接呼び出していないか。代わりに GriptapeNodes.handle_request(...) を介して AddParameterToNodeRequest / RemoveParameterFromNodeRequest を使用しているか。
  • [ ] ノード間やフローの状態は process 内からフェッチするのではなく、パラメータ経由で渡されているか。
  • [ ] 接続フック(after_incoming_connection およびその関連フック)をオーバーライドしていないか(ワーカーホストされたライブラリではこれらは決して発火しません)。
  • [ ] before_value_set / after_value_set が値の変換のみに使用されており、エディタ実行時のリアクティビティやパラメータリストの変更に使用されていないか。
  • [ ] カスタム converters / validators / traits を process 内で再実行しているか、あるいはオーケストレーター専用の UI 装飾として許容しているか。
  • [ ] pip_dependencies が特定のバージョンに厳密に固定(Pin)されているか。
  • [ ] カスタムインデックス URL や追加引数が必要な場合、pip_install_flags が設定されているか。

ストリクトモードは安全ネットです

通常のノード実行中にエンジンをローカルで実行し、コンソール出力に strict-mode のログが出現しないか確認してください。ワーカーの出力はエンジンを起動したのと同じターミナルに表示され、オーケストレーターの出力と区別できるようにプレフィックス Worker-<engine-id> が付与されます。WARNING と ERROR の両方のエントリに注意してください。

5 つのルールと実際の重要度:

ルール オーケストレーター ワーカー 備考
reentrant-bus-in-init ERROR ERROR 正確性(Correctness)ルール。該当クラスはライブラリのスキーマから除外されます。
parameter-behaviors-dropped-in-schema WARNING WARNING ライブラリ読み込み時、Parameter にワーカーのスキーマがシリアライズできない converters / validators / traits が含まれている場合に発火します。エスカレーションはされません。
connection-hooks-inert-on-worker WARNING WARNING ライブラリ読み込み時、ノードクラスが接続ライフサイクルフックをオーバーライドしている場合に発火します。それらのフックはオーケストレーター上でスタブに対して実行されるため、オーバーライドコードは決して走りません。エスカレーションはされません。
value-hooks-execute-only-on-worker WARNING WARNING ライブラリ読み込み時、ノードクラスが before_value_set / after_value_set をオーバーライドしている場合に発火します。値の変換は機能しますが、エディタ時のリアクティビティやパラメータリストの変更は動作しません。エスカレーションはされません。
parameter-mutation-during-aprocess WARNING ERROR ワーカー上でノードの実行結果を失敗(Failure)に昇格させます。

ストリクトモードのログが出力された場合、ルールの修復メッセージに違反したガイドラインの内容と修正方法が正確に提示されます。ストリクトモードの WARNING および ERROR エントリが一切ないワーカーログが、「分離対応完了(Isolation-ready)」の基準となります。