テキストプロンプトの仕様 (A1111 との違い)

Automatic1111版 web UI (以下 A1111) を使っていた人が ComfyUI で A1111 での画像を再現しようとして疑問に思うことは、『何かプロンプトの効きがおかしいような?』ではないでしょうか。 プロンプトその他が同じなのに絵が "飛んでしまう" のはなぜか、とか、A1111 でのプロンプトをどう変換すれば良いのか、という質問は公式 FAQ に載るくらい良く目にします。

そこでこのページでは、"強調" の方法、TI (textual inversion、embeddings) や LoRA など、ComfyUI におけるテキストプロンプトや関連する仕様について、A1111 との違いも含めて解説します。

まとめ
A1111 と ComfyUI との対応表。
注意: 必ずしも 1 対 1 対応ではないし、プロンプト全体で見たときの効果が同じわけでもない
Automatic1111版 web UI 拡張無しの ComfyUI
Textual Inversion - TI filename embedding:filename.ext
embedding:filename
(どちらでも可)
(通常の重み付けオペレータで強度を指定する)
LoRA 類 <lora:filename:multiplier> LoraLoader を使う (CLIP、U-Net それぞれに強度指定可)。
trigger word が指定されている場合は、プロンプトに含める
Hypernetwork <hypernet:filename:multiplier> HypernetworkLoader を使う (強度指定可)
trigger word が指定されている場合は、プロンプトに含める
コメント ×? /**/ (複数行も可)
// 〜 (行末まで)
重み付け (強調)
1.1 倍
(a b c d e) (a b c d e)
重み付け (強調)
1 / 1.1 倍
[a b c d e] (a b c d e:0.909)
重み付け (強調)
1.5 倍
(a b c d e:1.5) (a b c d e:1.5)
重み付け (強調)
-1 倍
(a b c d e:-1) (a b c d e:-1)
重み付けネスト
1.21 倍
((a b c d e)) ((a b c d e))
エスケープ \(a b c d e\) \(a b c d e\)
75トークン超え チャンクに分割して CLIP encode 後結合
(分割位置調整あり、だと思う)
バッチに分割して CLIP encode 後結合
(分割位置調整あり)
バッチ (チャンク) に分割 BREAK
余りは padding token で埋められる
複数の CLIPTextEncode に分けて、ConditioningConcat で結合する
複数のプロンプトを平行評価 AND 複数の CLIPTextEncode に分けて、ConditioningCombine で結合する
テキストのランダム置換 拡張で対応 {wild|card|test}
(ただし、置換はサーバに送られる前に UI 側でされる)
ワードの順次置換 Prompt S/R ×
ファイルから順次プロンプト送出 Prompts from file or textbox ×
敢えて言えば API 使用で
プリセットプロンプトの追加適用 スタイル ×
prompt editing [from:to:when]
a [fantasy:cyberpunk:16] landscape
KSamplerAdvanced を使う
ステップ毎にワードをサイクル [cow|cow|horse|man|siberian tiger|ox|man] in a field KSamplerAdvanced を使う (すごく面倒)
プロンプトの事前バリデーション 有り ×
CLIP Skip オプションで指定 CLIPSetLastLayer で指定
Alt-Diffusion
(別言語の CLIP 利用)
(よくわからない)
可能らしい ???

目次

機能

Textual Inversion (TI)、LoRA 類、Hypernetwork

Textual Inversion (TI) は、プロンプトの一部として CLIPTextEncode で指定します。 embedding:filename.extembedding:filename のような形式で指定します。 拡張子 (.ext の部分) は無くても構いません。

ファイルは、あらかじめサーバの /models/embeddings/ に置いておく必要があります。 サブディレクトリを作ってその中に入れても大丈夫です。 新規に置いたり名前を変更した場合は、ブラウザで UI をリロードしてください。

プロンプトの他の部分と同じように、重み付けオペレータが使用できます (下例)。 ただ、A1111 より重みが効きやすい傾向があるので、注意は必要です。

LoRA の類は LoraLoader で指定します。 モデルへのパッチ強度は、CLIP と U-Net それぞれ別々に指定できます。 ファイルは、あらかじめサーバの /models/loras/ に置いておく必要があります。 サブディレクトリを作ってその中に入れても大丈夫です。 新規に置いたり名前を変更した場合は、ブラウザで UI をリロードしてください。

いわゆる trigger word が指定されている場合は、LoraLoader を通した後の CLIP を使って CLIPTextEncode で指定します。 trigger word は通常のプロンプトなので、他の部分と同じように、重み付けオペレータが使用できます。 ただ、A1111 より重みが効きやすい傾向があるので、注意は必要です。

Hypernetworkは HypernetworkLoader で指定します。 モデルへのパッチ強度も指定できます。 ファイルは、あらかじめサーバの /models/hypernetworks/ に置いておく必要があります。 サブディレクトリを作ってその中に入れても大丈夫です。 新規に置いたり名前を変更した場合は、ブラウザで UI をリロードしてください。 いわゆる trigger word が指定されている場合は、CLIPTextEncode で指定します。 trigger word は通常のプロンプトなので、他の部分と同じように、重み付けオペレータが使用できます。 ただ、A1111 より重みが効きやすい傾向があるので、注意は必要です。

空白、改行、,

空白は単語区切りの意味があります。 一つでも複数連続していても同じ意味になります。 タブや改行も、空白です。 重み付けの () も単語区切り扱いです。 プロンプトの先頭や末尾の空白は削除されます。 というわけで、

  a b   (  c
d)
      e

というようなプロンプトは a b (c d) ea b(c d)e と同じです。

, などの記号は、記号 + 単語区切り、の意味を持つことがあります。 常に単語区切りが付くわけではなく、やや複雑な tokenize 処理の結果で決まるので完全な予測はしにくいです。 例えば a,ba, b は同じ意味になります。 一方で a,,ba, ,b は違う意味になります。

コメント

C/C++ スタイルのコメントが使用できます。 コメントアウトされた部分は、サーバに送られる前に web UI 側で取り除かれます。 /**/ は複数行のコメントも可能です。 // 〜 は、行末までのコメントになり、改行が残ります。

コメント有り等価な例備考
a /* c d */ b
a b
a/* c d */b
ab
a/*c
d*/b
ab
a // c d
b
a
b
改行が残る
a//c d
b
a
b
改行が残る

英文字アルファベット

英文字アルファベットについては、すべて小文字に変換されます。 また、A1111 の ANDBREAK のような英字の指示文字列は、ComfyUI にはありません。

重み付け (強調)

ComfyUI における重み付けの記法は、数値指定無しで 1.1 倍の重みになる (a b c d e) と、数値指定有りでその倍数になる (a b c d e:1.1) の二種類です。 数値指定有りの方を使えば抑制もできます (例: (a b c d e:0.8))。 負の重みも指定できます (例: (a b c d e:-1))。 ネストも可能です (例: ((a b c (d:0.8) e)))。

A1111 と ComfyUI とを比較すると、同じ重みでも ComfyUI の方がかなり直接的で強く働くように感じられると思います。 ComfyUI では通常は 1.1〜1.2 程度、最大でも 1.5 あたりで抑えることになるでしょう。 positive だけでなく negative についても同じように注意が必要です。 A1111 の感覚で (worst quality:2.0) などとすると、まず間違いなくひどい画像が生成されます。

部分強調であるということは同じで似た記法が使えますが、その仕様にはかなり違う部分もあります。 そのため、A1111 のプロンプトを ComfyUI に単純に変換することはできません。 詳細は 『トークンの重み適用の仕様』 で解説します。

A1111ComfyUI 例 1ComfyUI 例 2備考
a b c d e a b c d e (a b c d e:1.0)
(a b c d e) (a b c d e) (a b c d e:1.1)
(a b c d e:1.1) (a b c d e:1.1) ( a b c d e : 1.1 )
((a b c d e)) ((a b c d e)) (a b c d e:1.21)
[a b c d e] (a b c d e:0.909) ComfyUI には抑制専用の記法は無い
(a (b) c d e) (a (b) c d e) (a) (b:1.21) (c d e)
(a )(b:1.21)( c d e)
(a)(b:1.21)(c d e)
(a)((b))(c d e)
(a(b)c d e)
(どれも同じ結果)

エスケープ

プロンプト中で特殊な意味を持つ (){} は、\ (半角円記号またはバックスラッシュ) でエスケープすることができます。 pablo_picasso_\(style\) のように指定します。

他の文字はエスケープ対象になりません。 \| も対象外です。 例えば、a\\bc はそのまま a\\bc として CLIP に渡されます。

{} のエスケープについては、嬉しくない特徴があります。 指示通りに UI (フロントエンド側) でのランダム置換が止まるのは良いのですが、\ を残したままサーバに渡され、そのまま CLIP で処理されるのです。 実際に困ることは多分無いと思いますが、もし \ が付くのを回避したければ、\ 無しのプロンプトを API で渡す、というような方法しかありません。

トークン列への変換

CLIP/OpenCLIP による処理は大きく分けて二段階になります。 最初に行うのは、テキストからトークン列への変換 (tokenize) です。

tokenize は、入力されたプロンプトテキストを、CLIP が知るトークン (字句) の列に分割/変換する処理です。 tokenize 処理は、前から順に対応するトークンに置き換えていくだけの単純なものではなく、結果のトークン数が少なくなるように BPE merge と呼ばれる手法を用います。

必ずしも英単語一つがトークン一つに対応するわけではありません。 例をあげると、latentdiffusion はそれぞれ 2 トークンになります。 blackandwhitephotographyartificialintelligence は 1 トークンです。 数字 09 は、必ず一文字が 1 トークンになります。 2048 だと 4 トークンです。 文字列とトークンの関係は comfy/sd1_tokenizer/vocab.json で定義されています。 見てみると面白いです。

バッチ分割

CLIP/OpenCLIP による処理は大きく分けて二段階になります。 二段階目は、トークン列から潜在表現への変換 (encode) です。

encode は、トークンの列を、より情報密度の高い潜在表現 (embeddings) に変換する処理です。 変換を行うのは CLIP text encoder と呼ばれるトランスフォーマーで、その入力は必ず 77 個のトークンからなるトークン列です。 入力の先頭と末尾のトークンは固定値なので、プロンプトで使えるのは 75 トークンです。 75 トークンを超える長いプロンプトが使用された場合は、encode を複数回に分けて行います。 そのために、トークン列をバッチ (A1111 ではチャンク) へ分割します。

バッチ分割では、まず、同じ重みを持つ連続したトークンをまとめた『グループ』を作ります。 そして先頭のグループから順に、バッチに追加していきます。 バッチが 75 トークンを超える時は、グループの長さによって処理が変わります。

  • グループの長さが 7 トークン以下の時は、現在のバッチが 75 トークンとなるように末尾を padding token で埋めます。 グループのトークンは新たなバッチを作ってそこに追加します。 .../sd1_clip.py#L345
  • グループの長さが 8 トークン以上の時は、現在のバッチの残りに入るだけグループのトークンを入れます。 残りは新たなバッチを作ってそこに追加します。 .../sd1_clip.py#L340

全てのグループのトークンをバッチに追加したら、最後のバッチも 75 トークンになるように padding token で末尾を埋めます。

エンコードは、バッチ毎に行います。 そのため、バッチ間ではコンテキストが共有されません。 7 トークン以下のグループを残そうとするのは、意味的に関係が深いであろう部分をなるべく分割したくない、ということなのだと思います。

A1111 ではプロンプト中に BREAK を入れることで分割位置を明示的に指定できます。 ComfyUI では、複数の CLIPTextEncode に分けて、ConditioningConcat で結合します。

複数プロンプトの平行評価

A1111 では AND で指定する複数プロンプトの平行評価は、ComfyUI の得意とする処理です。 複数の CLIPTextEncode に分けて、ConditioningCombine で結合することで実現できます。 また ConditioningSetAreaConditioningSetMask と組み合わせることで、場所により異なる conditioning を適用したり重みを変更することもできます。

プリプロセス

プロンプトテキストのランダム置換は {wild|card|test} のように指定できます。 ただし、置換はサーバに送られる前に UI 側でされます。 EmptyLatentImage でバッチ生成を指定した場合でも、すべての画像で同じプロンプトを使用することになります。

A1111 では "Prompts from file or textbox" を使って、プロンプトを切り替えながら連続して画像を生成する機能がありますが、ComfyUI にはそのような機能はありません。 設計的には、そのような場合には API を使う想定になっているように思います。 ComfyUI のバックエンドを利用した CushyStudio が得意とするところでしょう (拡張機能、カスタムノードについて)。

A1111 では定型プロンプトを追加適用する "スタイル" と呼ばれる機能がありますが、ComfyUI にはそのような機能はありません。 文字列を結合するノードがあれば似たようなことは可能ですがそれも無いので、カスタムノードに頼ることになります。

ランタイムでの置換等

A1111 で "prompt editing" と呼ばれている機能は、生成の途中でプロンプトを切り替えるものです。 これは、ComfyUI が得意です。 KSamplerAdvanced を使います。 プロンプトだけでなく、使用するモデル、ノイズ除去のステップ数など、様々なものが変更可能です。 切り替え時に同時に latent 画像を加工したりすることも可能です。

A1111 で [cow|cow|horse|man|siberian tiger|ox|man] in a field などと指定すると、 1 ステップ毎にプロンプトを切り替えることができます。 ComfyUI でも KSamplerAdvanced を使えば可能ではありますが、1 ステップ毎に一つの KSamplerAdvanced を用意しなければならず、配線も設定も大変面倒です。 また、KSamplerAdvanced 開始時のオーバーヘッドがあるため、処理に時間がかかります。 CushyStudio なら KSamplerAdvanced を使った構成も簡単ですが、実行が遅いことは変わりません。 ConditioningCombine を使った 複数プロンプトの平行評価 でも似たような結果が得られることはあるので、そちらの方が良いでしょう。

その他

UI 上でプロンプトの文法やトークン数のチェックを行う自動バリデーション機能は、A1111 にはありますが、ComfyUI にはありません。 これは、ComfyUI の自由度との引き換えになっている感じがします。 とは言え A1111 相当の範囲であれば実現可能に思えるので、実装されるかもしれません。

CLIP Skip は、CLIPSetLastLayer で指定します。

別言語の CLIP を利用できるらしい Alt-Diffusion は、A1111 では可能らしいです。 これについては、よくわかりません。 後で追記します。

トークンの重み適用の仕様

重みの決定

重みの決定からエンコードまでは、A1111 と ComfyUI とで大きな差はありません。

プロンプトテキストのどの部位がどの重みになるのかは、tokenize の前に決定されます。 その後、同じ重みで連続した文字列ごとに tokenize を行い、token と重みのペアを記録していきます。

tokenize の後は encode を行います。 encoder には、token のみを渡します。 ここでは重みは使いません。

重み適用と『正規化』

token と一緒に保存されていた重みを、encode 結果の embeddings の対応する位置それぞれに適用します。

重みは、その位置の embedding にのみ作用します。 トークンが他の場所の embeddings に及ぼした影響は変えられません。 そのため、たとえ重みを 0 にしても、トークンが無かったことにはなりません。 同様に、重みを負にしても、positive prompt が negative prompt になったりはしません。

ComfyUI では、空のプロンプトの embeddings を基準とした重み付けを行います (.../sd1_clip.py#L9)。 CFG と同じような考え方です (CFG Scale とは何か)。 基準が CFG の uncond と同じなので、重み付けの方法として自然です。

A1111 での重み適用は、ComfyUI と 2 つの点で大きな違いがあります。

  1. まず、A1111 では latent space 原点を基準とした重み付けを行います。 つまり、重みを embeddings にそのまま乗算します。

  2. その上で、embeddings の全要素の平均が重み適用前と同じになるように、再スケーリングします。 事前に重みを正規化するのとは結果が異なりますが、これもある種の正規化と言えるのかもしれません。 これに関してはコード内のコメントに、正しくはないと思うけどアーティファクトを防げるからこうする、的なことが書かれています (.../sd_hijack_clip.py#L256)。

ComfyUI 公式 FAQ では、『正規化』について言及しています。 基準点については 公式 FAQ には記述がありませんが、正規化よりもこちらの違いの方が影響が大きいという意見もあります (.../discussions/473#discussioncomment-5604052)。

原点の違いについては、かなり根本的な違いなので、テクニックで揃えるのは無理です。 『正規化』の方は、強調したい所以外の重みを相対的に小さくする、というような方法で、似たような効果を得ることはできます。 ただしこれだと、事前に重みを正規化するのと同等の効果になります。 A1111 で行っているような、事後の embeddings の再スケーリングとは、やはり結果は異なります。

結論として、A1111 のプロンプトの効果をそのまま ComfyUI で再現することは、重み付けを使用している場合はほぼ不可能、と言って良いと思います。 どうしても、という場合は、第三者によるカスタムノード (拡張機能、カスタムノードについて の Advanced CLIP Text Encode) を使うという手はあります。