Stable Diffusion with Python 파이썬으로 배우는 확산 모델
Chapter 07 목차
제2·3부 — 10·12·17장

프롬프트, 그 미묘한 마법

CLIP의 77 토큰 벽을 청크로 허물고, Compel로 개념을 블렌딩하며, 스케줄된 프롬프트로 단계를 가른다 — 그리고 좋은 프롬프트를 쓰는 원칙을 익힌다.

77 Token Limit Prompt Weighting Compel Scheduled Prompt LLM Prompting

Section 0177 토큰 제한

Stable Diffusion v1.5의 텍스트 인코더는 OpenAI의 CLIP 인코더를 쓴다. CLIP 텍스트 인코더에는 77 토큰 제한이 있고, 이 제한이 다운스트림 Stable Diffusion으로 그대로 전파된다.

긴 프롬프트를 인코딩하려 하면 단호한 오류가 떨어진다 — RuntimeError: tensor a (181) must match tensor b (77). CLIP 텍스트 인코더는 한 번에 단 77개의 토큰만 받는다.

77 토큰 제한 재현
prompt = "a photo of a cat and a dog driving an aircraft " * 20

tokenizer   = pipe.tokenizer
text_encoder = pipe.text_encoder

tokens = tokenizer(prompt, truncation=False,
                   return_tensors='pt')["input_ids"]
print(len(tokens[0]))           # 181

embeddings = pipe.text_encoder(tokens.to("cuda"))[0]
# RuntimeError — 181 != 77
Key — 시작·끝 토큰 토큰화된 결과의 첫 ID 49406(<|startoftext|>)과 마지막 ID 49407(<|endoftext|>)은 토크나이저가 자동으로 붙인다. 토큰을 청크로 잘라 다시 이을 때 이 두 토큰을 반드시 제거해야 한다. 그렇지 않으면 시작·끝 토큰이 중간에 끼어든다.

Section 02제한 극복하기

다행히 Stable Diffusion UNet은 77 토큰 제한을 강제하지 않는다. 제한은 CLIP 인코더에만 있다. 임베딩을 배치로 가져와 청크된 임베딩을 하나의 tensor로 연결해 UNet에 넘기면 제한을 우회할 수 있다.

긴 프롬프트를 77 토큰 청크로 분할·인코딩·연결 STEP 01 / 5
space 재생 · → 다음 · R 리셋
Concept제한 극복의 다섯 단계

1. 파이프라인에서 텍스트 토크나이저와 텍스트 인코더를 추출한다.

2. truncation=False로 어떤 크기의 프롬프트든 토큰화한다.

3. 자동으로 붙은 시작·끝 토큰을 제거한다.

4. 77개씩 청크로 꺼내 각각 임베딩으로 인코딩한다.

5. 임베딩들을 [1, x, 768] 크기의 tensor로 스택해 UNet에 공급한다.

Intuition — 프롬프트 가중치까지 같은 메커니즘 위에 프롬프트 가중치를 얹을 수 있다. 개별 단어나 토큰에 다른 중요도 점수를 부여해 모델의 주의를 미세 조정하면, 더 정확한 결과가 나온다. 토큰을 임베딩으로 바꾼 뒤 가중치를 곱하는 것이 핵심 발상이다.

Section 03Compel 블렌딩

Compel은 Damian Stewart가 만든 오픈 소스 프롬프트 가중치·블렌딩 라이브러리다. "반은 고양이, 반은 개인 사진"을 원할 때, "A photo with half cat and half dog"이라 쓰면 "half"가 동물이 아니라 사진 자체에 적용되어 버린다.

Compel은 두 개념의 텍스트 임베딩을 직접 블렌딩해 이 문제를 푼다.

Compel — 두 개념을 블렌딩
from compel import Compel

compel = Compel(
    tokenizer = pipeline.tokenizer,
    text_encoder = pipeline.text_encoder)

# 고양이 70% + 개 30% 블렌딩
prompt = '("A photo of cat", "A photo of dog").blend(0.7, 0.3)'
prompt_embeds = compel(prompt)

image = pipeline(
    prompt_embeds = prompt_embeds,
    generator = torch.Generator("cuda:0").manual_seed(1)
).images[0]

Compel이 Diffusers와 매끄럽게 맞물리는 이유는, 모델 파일의 동일한 CLIPTokenizerCLIPTextModel로 텍스트 임베딩을 만들기 때문이다. .blend()의 비율을 바꾸면 더 고양이 같은, 혹은 더 개 같은 사진을 얻는다.

Section 04스케줄된 프롬프트

Diffusers 기본 코드에는 한계가 있다. "처음 5단계는 고양이를, 다음 5단계는 개를 생성하라"는 프롬프트는 쓸 수 없다. 두 개념을 번갈아 노이즈 제거해 혼합하는 것도 불가능하다. 스케줄된 프롬프트(scheduled prompt)는 이 빈틈을 메운다.

스케줄된 프롬프트 — 단계별로 다른 임베딩 STEP 01 / 4
space 재생 · → 다음 · R 리셋
Key — 커스텀 파서 스케줄된 프롬프트를 지원하려면 [cat:dog:0.5] 같은 구문을 해석하는 커스텀 파서가 필요하다. 책에서는 lark 패키지로 파서를 만들고, 누락된 단계를 채운 뒤, 각 노이즈 제거 단계에서 해당 단계의 텍스트 임베딩을 UNet에 공급하는 커스텀 파이프라인을 구축한다. 단계가 진행될수록 프롬프트가 바뀐다.

Section 05좋은 프롬프트의 원칙

SD v1.5의 CLIP 인코더는 인터넷의 캡션 이미지로 학습됐는데, 그중 다수가 구조화된 문장이 아닌 태그다. 그래서 v1.5는 "마법 같은" 키워드를 외워 조합해야 한다. SDXL의 듀얼 인코더는 훨씬 지능적이지만, 그래도 원칙은 있다.

Table 17.1 — 좋은 프롬프트의 원칙
원칙나쁜 예좋은 예
명확하고 구체적으로"A painting of cool sci-fi""...futuristic cityscape with towering skyscrapers, neon lights, flying vehicles"
설명적으로"A beautiful woman""...long flowing brown hair, piercing emerald eyes, gentle smile"
일관된 용어"blue suit, he wears colorful cloth" (모순)"tailored blue suit adorned with a colorful scarf"
Pitfall — 모순되는 용어 "파란 정장을 입은 남자"이면서 "화려한 옷"이라 쓰면, 모델은 파란 정장과 화려한 정장 사이에서 혼란에 빠진다. 결과는 예측 불가능 — 때로는 파란 정장, 때로는 화려한 정장이다. 의도가 "파란 정장 + 화려한 스카프"라면 그렇게 명확히 써야 한다.

이 밖에도 아트워크와 스타일을 참조하고, 네거티브 프롬프트를 통합하며, 반복과 개선을 거듭하라. 그리고 가장 강력한 도구 — LLM을 프롬프트 생성기로 활용하면, 간단한 아이디어를 풍부하고 구조화된 Stable Diffusion 프롬프트로 자동 확장할 수 있다.