notes
Tools

Stable Diffusion のプロンプト 77 トークン制限

CLIP テキストエンコーダの 77 トークン上限と、WebUI 系が長文を扱える仕組み (分割エンコード)、diffusers での対処

要点: Stable Diffusion のプロンプトは CLIP の入力上限 77 トークン (BOS/EOS 込み、実質 75) を超えた分が無言で切り捨てられる。WebUI (A1111/Forge) は 75 トークンごとに分割エンコードして連結するため事実上無制限。素の diffusers は切り捨てるだけ。

数字#

  • 上限: 77 トークン (先頭 BOS + 末尾 EOS を含む。ユーザが使えるのは 75)
  • 1 タグ ≒ 1〜3 トークン。カンマも 1 トークン消費
  • 日本語や造語はサブワード分割で膨らみやすい (例: tsukema → 3 トークン)
  • SDXL はエンコーダが 2 本 (CLIP-L + OpenCLIP-G) だが上限はどちらも 77

トークン数の確認#

from transformers import CLIPTokenizer
t = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14")
print(len(t("masterpiece, 1girl, ...").input_ids))  # 77 以下なら安全

WebUI が長文を扱える仕組み#

75 トークンごとにチャンク分割 → 各チャンクを個別にエンコード → 埋め込みを系列方向に連結して UNet に渡す。チャンク境界をまたぐ概念は弱くなるため、関連タグは近くに置く。

diffusers での対処#

  • 素の pipe(prompt=...) は 77 超を警告なしで切り捨て (Token indices sequence length is longer... は tokenizer の警告で、生成は止まらない)
  • compel で WebUI 同様の分割エンコードができる:
from compel import Compel, ReturnedEmbeddingsType
compel = Compel(tokenizer=[pipe.tokenizer, pipe.tokenizer_2],
                text_encoder=[pipe.text_encoder, pipe.text_encoder_2],
                returned_embeddings_type=ReturnedEmbeddingsType.PENULTIMATE_HIDDEN_STATES_NON_NORMALIZED,
                requires_pooled=[False, True], truncate_long_prompts=False)
emb, pooled = compel(prompt)
nemb, npooled = compel(negative)
# 注意: pad_conditioning_tensors_to_same_length は multi-encoder 構成で
# AttributeError になる (compel 2.x)。空プロンプトのチャンクを cat して自前で長さを揃える
img = pipe(prompt_embeds=emb, pooled_prompt_embeds=pooled,
           negative_prompt_embeds=nemb, negative_pooled_prompt_embeds=npooled).images[0]
  • ネガティブプロンプト側も同じ 77 制限。ポジと長さ (チャンク数) を揃える必要がある

切り捨て時の症状#

エラーは出ない。末尾のタグ (スタイル指定を後置していると特にそれ) が効かなくなるだけなので気づきにくい。プロンプトを伸ばしたのに反映されない時はまずトークン数を数える。

最終更新:

On this page