強調と重み付けの文法
()や[]の正確な倍率、A1111とComfyUIで異なる重みの計算式、プロンプトエディティング・BREAK・LoRA記法をソースコードベースで整理する。
タグに括弧を付けて効きを強めるのは誰もがやる操作だが、その倍率が正確にいくつで内部で何が起きているかは、フロントエンド(A1111 / Forge / ComfyUI などの生成UI)によって別物になる。以下の倍率と挙動は各リポジトリの実装コードおよび公式 Wiki が根拠で、裏付けのない経験則はその旨を明記して区別する。
フロントエンド別 対応表
| 構文 | A1111 | Forge | ComfyUI(本体) |
|---|---|---|---|
(tag) = ×1.1 | ○ | ○ | ○ |
(tag:1.2) | ○ | ○ | ○(ただし入れ子時は絶対値) |
[tag] = ×1/1.1 | ○ | ○ | ×(ただの文字。トークンを消費) |
[from:to:when] | ○ | ○ | × |
[a|b] 交互 | ○ | ○ | × |
BREAK | ○ | ○ | ×(小文字化されて break という単語になる) |
AND(Composable Diffusion) | ○ | ○ | ×(ConditioningCombine ノード) |
<lora:name:weight> | ○ | ○ | ×(LoraLoader ノード) |
| Textual Inversion | ファイル名を素で書く | 同左 | embedding:name |
\( \) エスケープ | ○ | ○ | ○ |
\[ \] エスケープ | ○ | ○ | ×(バックスラッシュが残る) |
{a|b|c} ランダム選択 | ×(拡張が必要) | ×(同左) | ○ |
Forge の modules/prompt_parser.py は A1111 とほぼ同一(差分は distilled_cfg_scale 周りのみで文法・スケジューリングは無変更)なので、文法面では同じものが動く。BREAK やチャンク分割などの実処理は backend/text_processing/ 側にあり、なお Forge の modules/sd_hijack_clip.py は全行コメントアウトされた死んだファイルなので参照しても意味がない。
A1111 の倍率:正確な数値
modules/prompt_parser.py の parse_prompt_attention() に定数がそのまま書かれている。
round_bracket_multiplier = 1.1
square_bracket_multiplier = 1 / 1.1 # = 0.9090909090909091
| 記法 | 倍率 |
|---|---|
(word) | 1.1 |
((word)) | 1.21 |
(((word))) | 1.331 |
[word] | 0.9090909090909091 |
[[word]] | 0.8264462809917354 |
(word:1.5) | 1.5 |
(word:0.25) | 0.25 |
数値指定が使えるのは () だけで [] では不可(A1111 Wiki が明記)。重み値の正規表現は :\s*([+-]?[.\d]+)\s*\) なので (word:.5) や (word: 1.2)、負値 (word:-1) も通る。
入れ子は乗算される
A1111 では内側と外側の括弧が掛け算になる。 公式 docstring の例がそのまま答えになっている。
a (((house:1.3)) [on] a (hill:0.5), sun, (((sky))).
house→ 1.3 × 1.1 × 1.1 = 1.573hill→ 0.5 × 1.1 = 0.55sky→ 1.4641(1.1 の 4 乗)
sky が 1.331 でなく 1.4641 になるのは、前方に閉じられていない ( が 1 個残っているため。未閉じの括弧はプロンプト末尾まで効き続ける。 parse_prompt_attention('(unbalanced') は [['unbalanced', 1.1]] を返す。長いプロンプトで括弧の数が合わないと、意図しない箇所が一律に強調されるので注意。
重みが「どう」適用されるか
modules/sd_emphasis.py の既定モード Original の実装:
original_mean = self.z.mean()
self.z = self.z * self.multipliers...expand(self.z.shape)
new_mean = self.z.mean()
self.z = self.z * (original_mean / new_mean)
つまり CLIP を通過した後の出力テンソルをトークンごとに倍し、そのあとチャンク全体の平均を元に戻す。この平均復元があるため、あるタグの重みを上げると、同じ 75 トークンチャンク内の他のタグは相対的に弱くなる。「1個のタグを強調したら無関係な部分が崩れた」の直接の原因はこれ。ソース内のコメント自身が “restoring original mean is likely not correct, but it seems to work well” と認めている。
設定 emphasis は 4 択で、None(記法を文字として扱う)/ Ignore(強調を無視)/ Original(既定)/ No norm(平均復元なし)。No norm の説明文には “seems to work better for SDXL” と書かれている。 Illustrious / NoobAI / Pony V6 はすべて SDXL なので、A1111・Forge 系では Settings の Emphasis を No norm に変えるのが一次資料に沿った設定になる(Pony V7 は AuraFlow ベースで SDXL ではないため対象外)。
ComfyUI は計算式が違う
comfy/sd1_clip.py の token_weights():
weight *= 1.1 # 素の () は ×1.1
if xx > 0:
weight = float(x[xx+1:]) # 数値指定は「代入」= 絶対値
素の括弧同士は乗算されるので ((word)) は 1.21 で A1111 と一致する。だが数値を書いた瞬間、外側の括弧が積み上げた重みが上書きされる。
| 記法 | A1111 | ComfyUI |
|---|---|---|
(word) | 1.1 | 1.1 |
((word)) | 1.21 | 1.21 |
(word:1.2) | 1.2 | 1.2 |
((word:1.2)) | 1.32 | 1.2 |
コロンの探索は rfind(":")(最後のコロン)なので、(a:b:1.2) は「テキスト a:b に重み 1.2」と解釈される。
適用式も別物で、空プロンプト(全パディング)を同じ長さで別途エンコードした埋め込み z_empty を原点とした線形の内挿/外挿になっている。
z[i][j] = (z[i][j] - z_empty[j]) * weight + z_empty[j]
帰結:
w = 0は「そのタグが存在しないのと等価」(ゼロベクトルではなく空埋め込みに一致)。A1111 のw = 0はゼロベクトル化してから全体平均を戻すので別挙動。- ComfyUI は平均正規化を一切行わない。重み変更の影響がそのタグに局所化され、他のタグを押し下げない。
w = 1.5は CLIP が実際に出力しうる範囲の外側へ 50% 外挿することを意味する。
したがって A1111 のプロンプトを ComfyUI へ数値ごと持ち込むのは誤り。 同じ数値でどちらが体感的に強いかという定量比較は一次情報での確認が取れていないが、計算式が別物であること自体はコード上確定している。A1111 互換にしたい場合は ComfyUI_ADV_CLIP_emb(weight_interpretation を A1111 などに切替)や ComfyUI_smZNodes を使う。
[] は ComfyUI では動かない
角括弧は特殊文字として扱われず、CLIP のトークンをそのまま消費する([ = id 314、] = id 316、各 1 トークン)。減衰は (word:0.8) と書くしかない。\[ と書くとバックスラッシュ(id 59)が 1 トークン残るので ComfyUI で \[ は禁物。丸括弧のエスケープ \( \) は両者で正しく処理されコストは 0 トークンなので、Danbooru の hatsune_miku_(vocaloid) は hatsune miku \(vocaloid\) と書く。
NAI 記法からの換算
NovelAI は乗数 1.05 で {} を使う。A1111 Wiki に換算表がある。
| NAI | A1111 相当 |
|---|---|
{word} | (word:1.05) |
{{word}} | (word:1.1025) |
[word] | (word:0.952) |
[[word]] | (word:0.907) |
NAI の [] は 1/1.05 = 0.952 で、A1111 の [](1/1.1 = 0.909)より減衰が浅い。NAI のプロンプトをそのまま A1111 に貼ると、[] は意図より強く減衰する。
プロンプトエディティング(A1111 / Forge)
[from:to:when] で途中からタグを差し替える。
| 記法 | 意味 |
|---|---|
[from:to:when] | when で from → to に切替 |
[to:when] | when 以降に to を追加 |
[from::when] | when 以降に from を削除 |
when の解釈が重要で、小数点付き(0.0〜1.0)は全ステップに対する割合、小数点なしの正整数は絶対ステップ番号。a [fantasy:cyberpunk:16] landscape は 16 ステップ目から cyberpunk。100 ステップでの [mountain:lake:0.25] は 25 ステップ目で切替。入れ子も可能。
1.6.0 以降は Hires fix にも対応し、2nd pass では整数の when から base_steps が引かれ、小数の when から 1.0 が引かれる。つまり 1.0〜2.0 の小数は 2nd pass を指す。旧挙動に戻すには use_old_scheduling オプション。
[a|b](Alternating Words)はステップごとに交互に切り替え、末尾まで行くと先頭へ戻る。空要素も書ける — [fe|]male は female / male を 1 ステップおきに、[fe|||]male は female, male, male, male, female… と 4 要素周期になる。
どちらも <lora:...> には効かない。 LoRA 記法はスケジューリング処理の前に正規表現で除去されてしまうため、[<lora:one:1.0>|<lora:two:1.0>] のような書き方は成立しない(A1111 Wiki が明記)。
ComfyUI 本体はこの構文を一切解釈しない。ConditioningSetTimestepRange(start/end を 0.0–1.0 で指定)+ ConditioningCombine が native の代替で、A1111 記法をそのまま通したい場合は comfyui-prompt-control を入れる。
BREAK
A1111 Wiki 原文は “Adding a BREAK keyword (must be uppercase) fills the current chunks with padding characters”。正規表現は \bBREAK\b で大文字のみマッチし、sd_hijack_clip.py が現在のチャンクを 75 トークンまで pad で埋めて新しいチャンクを開始する。BREAK 自身はトークンを消費しない。
用途は、前後を別々の CLIP コンテキストとして処理させ、色移りなどの混線を分離すること。ただしトークンを節約する機能ではない(むしろ現チャンクの残り枠を捨てる)。
ComfyUI では BREAK は機能しない。 master 全体を grep しても処理が存在せず、CLIP トークナイザが小文字化して break</w>(token id 2568)という普通の単語 1 トークンとして絵に影響する。ComfyUI では CLIPTextEncode を 2 個用意して ConditioningConcat でつなぐか、comfyui-clip-with-break などのカスタムノードを使う。
SDXL 固有の落とし穴として、pooled text embedding は A1111・ComfyUI ともに第1チャンクからしか取られない。したがって 75 トークンより手前で BREAK を入れると、それ以降のタグは pooled 条件付けに一切寄与しなくなる。早すぎる BREAK にはこの代償がある。
LoRA と Textual Inversion の記法
A1111 / Forge のパース正規表現は <(\w+):([^>]+)>。第1グループのネットワーク種別名が \w+ 制限で、登録済みなのは lora と hypernet の 2 つだけ(ファイル名側は [^>]+ なのでハイフンを含んでよい)。マッチした <...> はプロンプトから完全に除去されるのでトークンを 1 つも消費しない。
| 記法 | te 倍率 | unet 倍率 |
|---|---|---|
<lora:NAME> | 1.0 | 1.0 |
<lora:NAME:0.8> | 0.8 | 0.8(te と同値) |
<lora:NAME:0.8:1.2> | 0.8 | 1.2 |
<lora:NAME:0.8:1.2:256> | 0.8 | 1.2(第4引数は dyn_dim) |
<lora:NAME:te=0.8:unet=1.2:dyn=256> という名前付き形式も使える。制約として、LoRA はネガティブプロンプトには書けない、バッチで複数プロンプトを流すと最初のプロンプトの LoRA だけが使われる(いずれも A1111 Wiki 明記)。
Textual Inversion(埋め込み)は A1111 では拡張子を除いたファイル名を素の単語として書くだけで、接頭辞は無い。内部では埋め込み名のトークン id 列を最長一致させる方式なので多単語名でも動き、(EasyNegative:1.2) のように重み付けもできる(重みは埋め込みの全ベクトルに適用)。ただし embedding はベクトル数ぶんトークンを消費する。
ComfyUI は embedding:NAME 形式(拡張子省略可、配置先は models/embeddings)。分割正規表現が (?<=\s)embedding: なので、セグメント先頭でない位置に書くときは直前に空白が必要。
安全な重み範囲と壊れる閾値
数式から確実に言えることと、コミュニティの経験則を分けて示す。
式から確実に言えること
- 分布外への逸脱は
(w − 1)の大きさと強調したトークン数kの両方に比例する。多数のタグを一斉に 1.3 以上にしても相対的な強調にはならず、全体が分布外へ出るだけになる。 - A1111 の
Originalでは、あるタグを w に上げると他タグは概ね1 / (1 + (w−1)·k/N)倍に縮む(N は全トークン数)。強調はゼロサムに近い形で必ず他を犠牲にする。 original_meanが 0 近傍だとoriginal_mean / new_meanが不安定に発散しうる。- ComfyUI の
w = 0はタグを消すのと等価という明確な下限がある。
経験則 — 実務的な目安として広く共有されているのは 0.7〜1.4、1.5 超で彩度飽和や輪郭の焼き付き、1.8 超で構図崩壊とされる。ただし公開された対照実験は見当たらず、出典は二次的なブログ記事にとどまる。
重みを上げる前に疑うこと — マイナーキャラや細かい概念が出ないとき、重みを上げるのはたいてい誤った対処になる。学習サンプル数が足りないタグは重みを上げても線形には効かず、分布外へ出て絵が壊れるだけになる。NoobAI の公式マニュアルは、再現度が足りないキャラには重みではなく core_tags(そのキャラの外見特徴タグ)の併記を指示している。
補足:Composable Diffusion(AND)
A1111 / Forge には \bAND\b で複数プロンプトを独立に条件付けして合成する機能がある。a cat :1.2 AND a dog AND a penguin :2.2 のように重みも付けられる(既定 1)。Wiki には「0.1 未満の値はほとんど効かない — a cat AND a dog:0.03 は実質 a cat と同じ」とある。空間的な分離はしないので、色移りや複数キャラの属性混線の対策としては弱い。ComfyUI では ConditioningCombine ノードが相当する。