텍스트-이미지 생성

텍스트-이미지 생성 (Text-to-image)

확산 모델(diffusion model) 하면 보통 텍스트-이미지 생성이 가장 먼저 떠오르곤 해요. 텍스트-이미지 생성은 텍스트 설명으로부터 이미지를 만들어 내는 작업인데, 그 텍스트 설명을 *프롬프트(prompt)*라고 불러요. 예를 들어 "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" 같은 문장이죠.

아주 높은 수준에서 보면, 확산 모델은 프롬프트와 무작위 초기 노이즈를 받아서 노이즈를 반복적으로 제거하며 이미지를 구성해요. 디노이징(denoising) 과정은 프롬프트에 의해 안내되고, 정해진 횟수의 타임 스텝이 지나 디노이징 과정이 끝나면 이미지 표현이 실제 이미지로 디코딩됩니다.

[!TIP] 잠재 확산 모델(latent diffusion model)이 어떻게 동작하는지 더 자세히 알고 싶다면 How does Stable Diffusion work? 블로그 포스트를 읽어 보세요.

🤗 Diffusers에서는 프롬프트로 이미지를 두 단계에 걸쳐 생성할 수 있어요:

  1. 체크포인트를 [AutoPipelineForText2Image] 클래스에 로드합니다. 이 클래스는 체크포인트에 따라 쓸 파이프라인 클래스를 자동으로 골라 줘요:
from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16, variant="fp16"
).to("cuda")  # or "mps", "xpu", "cpu"
  1. 파이프라인에 프롬프트를 넘겨 이미지를 생성합니다:
image = pipeline(
	"stained glass of darth vader, backlight, centered composition, masterpiece, photorealistic, 8k"
).images[0]
image

인기 있는 모델

가장 흔한 텍스트-이미지 모델로는 Stable Diffusion v1.5, Stable Diffusion XL (SDXL), Kandinsky 2.2가 있어요. 또 텍스트-이미지 모델과 함께 써서 생성 과정을 더 직접적으로 제어하게 해 주는 ControlNet 모델이나 어댑터도 있죠. 모델마다 아키텍처와 훈련 과정이 달라 결과가 조금씩 다르지만, 어느 모델을 고르든 사용법은 거의 동일해요. 각 모델에 같은 프롬프트를 넣고 결과를 비교해 볼게요.

Stable Diffusion v1.5

Stable Diffusion v1.5Stable Diffusion v1-4에서 초기화한 잠재 확산 모델로, LAION-Aesthetics V2 데이터셋의 512x512 이미지에서 595K 스텝으로 파인튜닝된 모델이에요. 이렇게 쓸 수 있죠:

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16, variant="fp16"
).to("cuda")  # or "mps", "xpu", "cpu"
generator = torch.Generator("cuda").manual_seed(31)
image = pipeline("Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", generator=generator).images[0]
image

Stable Diffusion XL

SDXL은 이전 Stable Diffusion 모델들보다 훨씬 큰 버전으로, 이미지에 더 많은 디테일을 더해 주는 2단계 모델 과정을 포함해요. 중앙을 중심으로 한 고품질 이미지를 생성하기 위한 추가적인 *마이크로 컨디셔닝(micro-conditionings)*도 함께 들어 있죠. 사용법을 더 자세히 보려면 더 포괄적인 SDXL 가이드를 확인해 보세요. 일반적으로 SDXL은 이렇게 씁니다:

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0", dtype=torch.float16, variant="fp16"
).to("cuda")  # or "mps", "xpu", "cpu"
generator = torch.Generator("cuda").manual_seed(31)
image = pipeline("Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", generator=generator).images[0]
image

Kandinsky 2.2

Kandinsky 모델은 텍스트와 이미지를 확산 모델에서 더 잘 정렬하게 해 주는 임베딩을 만들기 위해 이미지 프라이어(image prior) 모델도 사용한다는 점이 Stable Diffusion 모델과 조금 달라요.

Kandinsky 2.2를 쓰는 가장 쉬운 방법은 이렇습니다:

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"kandinsky-community/kandinsky-2-2-decoder", dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
generator = torch.Generator("cuda").manual_seed(31)
image = pipeline("Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", generator=generator).images[0]
image

ControlNet

ControlNet 모델은 Stable Diffusion v1.5 같은 텍스트-이미지 모델 위에 파인튜닝된 보조 모델 또는 어댑터예요. ControlNet 모델을 텍스트-이미지 모델과 조합하면 이미지를 생성하는 방법을 더 명시적으로 제어하는 다양한 옵션을 얻을 수 있어요. ControlNet은 모델에 추가적인 컨디셔닝 입력 이미지(conditioning input image)를 더합니다. 예를 들어 사람 포즈 이미지(보통 여러 키포인트를 이어 만든 뼈대 형태로 표현돼요)를 컨디셔닝 입력으로 주면, 모델이 그 이미지의 포즈를 따르는 이미지를 생성해요. 다른 컨디셔닝 입력과 사용법을 더 자세히 보려면 ControlNet 가이드를 확인해 보세요.

이 예시에서는 ControlNet을 사람 포즈 추정 이미지로 컨디셔닝해 볼게요. 사람 포즈 추정으로 사전 훈련된 ControlNet 모델을 로드합니다:

from diffusers import ControlNetModel, AutoPipelineForText2Image
from diffusers.utils import load_image
import torch

controlnet = ControlNetModel.from_pretrained(
	"lllyasviel/control_v11p_sd15_openpose", dtype=torch.float16, variant="fp16"
).to("cuda")  # or "mps", "xpu", "cpu"
pose_image = load_image("https://huggingface.co/lllyasviel/control_v11p_sd15_openpose/resolve/main/images/control.png")

controlnet을 [AutoPipelineForText2Image]에 넘기고, 프롬프트와 포즈 추정 이미지를 함께 제공합니다:

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", controlnet=controlnet, dtype=torch.float16, variant="fp16"
).to("cuda")  # or "mps", "xpu", "cpu"
generator = torch.Generator("cuda").manual_seed(31)
image = pipeline("Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", image=pose_image, generator=generator).images[0]
image
Stable Diffusion v1.5
Stable Diffusion XL
Kandinsky 2.2
ControlNet (pose conditioning)

파이프라인 파라미터 설정

파이프라인에는 이미지 생성에 영향을 주는 설정 가능한 파라미터가 여럿 있어요. 이미지의 출력 크기를 바꾸거나, 이미지 품질을 높이기 위해 네거티브 프롬프트를 지정하거나, 그 외에도 다양한 설정을 할 수 있죠. 이번 섹션에서는 이 파라미터들을 어떻게 쓰는지 더 깊게 살펴볼게요.

높이와 너비

heightwidth 파라미터는 생성 이미지의 높이와 너비(픽셀 단위)를 제어해요. 기본적으로 Stable Diffusion v1.5 모델은 512x512 이미지를 출력하지만, 8의 배수인 크기라면 얼마든지 바꿀 수 있어요. 예를 들어 직사각형 이미지를 만들려면:

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16, variant="fp16"
).to("cuda")  # or "mps", "xpu", "cpu"
image = pipeline(
	"Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", height=768, width=512
).images[0]
image

[!WARNING] 모델에 따라 훈련 데이터셋의 이미지 크기에 맞춰 기본 이미지 크기가 다를 수 있어요. 예를 들어 SDXL의 기본 이미지 크기는 1024x1024인데, heightwidth 값을 낮게 쓰면 품질이 낮은 이미지가 나올 수 있어요. 모델의 API 레퍼런스를 먼저 확인하는 걸 잊지 마세요!

가이던스 스케일

guidance_scale 파라미터는 프롬프트가 이미지 생성에 얼마나 영향을 미치는지 정해요. 값이 낮을수록 모델이 프롬프트와 느슨하게 관련된 이미지를 생성할 '창의력'을 얻게 되고, 값을 높일수록 모델이 프롬프트를 더 충실히 따르도록 밀어붙여요. 그런데 이 값이 너무 높아지면 생성 이미지에 아티팩트(artifacts)가 생길 수 있습니다.

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
image = pipeline(
	"Astronaut in a jungle, cold color palette, muted colors, detailed, 8k", guidance_scale=3.5
).images[0]
image
guidance_scale = 2.5
guidance_scale = 7.5
guidance_scale = 10.5

네거티브 프롬프트

프롬프트가 생성을 안내하는 것처럼, *네거티브 프롬프트(negative prompt)*는 모델이 생성하면 안 되는 것들에서 멀어지게 이끌어요. "low resolution"이나 "bad details" 같은 좋지 않은 이미지 특성을 없애 전반적인 이미지 품질을 높이는 데 흔히 쓰이죠. 네거티브 프롬프트로 이미지의 내용과 스타일을 제거하거나 수정하는 데도 쓸 수 있어요.

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
image = pipeline(
	prompt="Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
	negative_prompt="ugly, deformed, disfigured, poor details, bad anatomy",
).images[0]
image
negative_prompt = "ugly, deformed, disfigured, poor details, bad anatomy"
negative_prompt = "astronaut"

Generator

torch.Generator 객체는 수동 시드(manual seed)를 설정해 파이프라인에서 재현성을 보장해 줘요. Generator를 쓰면 이미지 배치를 생성해서 시드에서 만든 이미지를 Improve image quality with deterministic generation 가이드에서 설명하는 대로 반복적으로 개선할 수도 있습니다.

아래처럼 시드와 Generator를 설정할 수 있어요. Generator로 이미지를 만들면 매번 새로운 이미지를 무작위로 만들지 않고 같은 결과를 반환해야 합니다.

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
generator = torch.Generator(device="cuda").manual_seed(30)
image = pipeline(
	"Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
	generator=generator,
).images[0]
image

이미지 생성 제어하기

파이프라인의 파라미터를 설정하는 것 외에도, 프롬프트 가중치와 ControlNet 모델처럼 이미지 생성 방식을 더 제어하는 방법이 여러 가지 있어요.

프롬프트 가중치

프롬프트 가중치(prompt weighting)는 프롬프트 안 개념의 중요도를 높이거나 낮춰 이미지의 특정 특징을 강조하거나 최소화하는 기법이에요. 가중치가 적용된 프롬프트 임베딩을 만드는 데는 Compel 라이브러리를 추천해요.

[!TIP] 프롬프트 임베딩을 만드는 방법은 Prompt weighting 가이드에서 배울 수 있어요. 이 예시는 파이프라인에서 프롬프트 임베딩을 어떻게 쓰는지에 집중합니다.

임베딩을 만들고 나면 파이프라인의 prompt_embeds (네거티브 프롬프트를 쓴다면 negative_prompt_embeds) 파라미터로 넘겨 주면 돼요.

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained(
	"stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16
).to("cuda")  # or "mps", "xpu", "cpu"
image = pipeline(
	prompt_embeds=prompt_embeds, # generated from Compel
	negative_prompt_embeds=negative_prompt_embeds, # generated from Compel
).images[0]

ControlNet

ControlNet 섹션에서 봤듯이, 이 모델들은 추가적인 컨디셔닝 이미지 입력을 통합해 더 유연하고 정확한 방식으로 이미지를 생성해 줘요. 각 ControlNet 모델은 특정 종류의 컨디셔닝 이미지로 사전 훈련돼서, 그와 비슷한 새 이미지를 생성해요. 예를 들어 깊이 맵(depth map)으로 사전 훈련된 ControlNet 모델에 깊이 맵을 컨디셔닝 입력으로 주면, 그 안의 공간 정보를 보존하는 이미지를 생성해 줍니다. 프롬프트로 깊이 정보를 지정하는 것보다 훨씬 빠르고 쉬워요. Multi-ControlNet으로 여러 컨디셔닝 입력을 조합할 수도 있어요!

쓸 수 있는 컨디셔닝 입력 종류는 아주 많고, 🤗 Diffusers는 Stable Diffusion과 SDXL 모델용 ControlNet을 지원해요. 이 모델들을 어떻게 쓰는지는 더 포괄적인 ControlNet 가이드에서 배울 수 있습니다.

최적화

확산 모델은 규모가 크고, 이미지를 디노이징하는 반복적인 과정은 계산 비용과 부하가 상당히 커요. 하지만 그렇다고 강력한 GPU가 여럿 있어야만 쓸 수 있는 건 아니에요. 컨슈머급이나 무료 티어 리소스에서 확산 모델을 실행하는 최적화 기법도 여러 가지 있습니다. 예를 들어 모델 가중치를 절반 정밀도(half-precision)로 로드해 GPU 메모리를 아끼고 속도를 높이거나, 모델 전체를 GPU에 오프로드해 메모리를 더 아낄 수 있죠.

PyTorch 2.0은 더 메모리 효율적인 어텐션 메커니즘인 scaled dot product attention도 지원하는데, PyTorch 2.0을 쓰면 자동으로 활성화돼요. 여기에 torch.compile을 조합하면 코드를 더욱 빠르게 만들 수 있습니다:

from diffusers import AutoPipelineForText2Image
import torch

pipeline = AutoPipelineForText2Image.from_pretrained("stable-diffusion-v1-5/stable-diffusion-v1-5", dtype=torch.float16, variant="fp16").to("cuda")  # or "mps", "xpu", "cpu"
pipeline.unet = torch.compile(pipeline.unet, mode="reduce-overhead", fullgraph=True)

메모리를 아끼고 추론을 빠르게 할 수 있는 코드 최적화 팁을 더 보려면 Accelerate inferenceReduce memory usage 가이드를 읽어 보세요.

더 알아보기 (Learn more)