생성형 이미지 실전: Midjourney·FLUX·SDXL
세 모델의 구조적 차이를 이해하고 프로덕션 파이프라인에 통합하는 개발자용 완전 가이드
이미지 생성 모델은 더 이상 "예쁜 그림 뽑는 장난감"이 아니다. 제품 썸네일 자동화, 마케팅 에셋 배치 생성, 게임 컨셉 아트, UI 목업, 데이터 증강(augmentation)까지 실제 파이프라인에 들어와 있다. 문제는 모델마다 접근 방식과 통제 가능성이 완전히 다르다는 점이다. Midjourney는 API가 (공식적으로) 없고 Discord/웹 중심이라 자동화가 까다롭지만 미적 완성도가 압도적이다. FLUX는 프롬프트 충실도(prompt adherence)와 텍스트 렌더링이 강하면서 로컬·API 양쪽 모두 가능하다. SDXL은 오픈 웨이트라 완전한 통제(LoRA, ControlNet, 인페인팅, 파인튜닝)가 가능하지만 손이 가장 많이 간다.
이 가이드는 "어떤 모델이 최고냐"는 무의미한 질문 대신, 각 모델의 작동 구조를 이해하고 → 상황에 맞게 선택하고 → 프로덕션 코드로 통합하는 것을 목표로 한다. 디퓨전 모델이 노이즈에서 어떻게 이미지를 복원하는지, CFG/스텝/스케줄러 같은 파라미터가 실제로 무엇을 바꾸는지, 같은 프롬프트가 모델별로 왜 다르게 해석되는지를 다룬다.
전체 흐름은 다음과 같다.
| 단계 | 내용 | 다루는 섹션 |
|---|---|---|
| 기초 | 디퓨전 동작 원리, 핵심 파라미터 | 1, 2 |
| 모델별 | Midjourney / FLUX / SDXL 실전 | 3, 4, 5 |
| 프롬프트 | 모델 공통·차별 프롬프트 전략 | 6 |
| 통제 | ControlNet·인페인팅·LoRA | 7, 8 |
| 프로덕션 | API 통합, 큐, 비용, 함정 | 9, 10, 11 |
각 섹션은 개념 → 실제 설정값 → 동작하는 코드 → 흔한 함정 → 베스트 프랙티스 순서로 구성했다. 코드는 Python(diffusers, replicate) 중심이며 그대로 복붙해서 돌아가는 수준으로 작성했다.
1. 디퓨전 모델은 실제로 무엇을 하는가 — 파라미터를 이해하려면 원리부터
파라미터를 외우기 전에, 디퓨전 모델이 노이즈에서 이미지를 만드는 과정을 알아야 한다. 그래야 steps, cfg, scheduler 값을 만질 때 무슨 일이 일어나는지 예측할 수 있다.
핵심 직관: 학습 시 모델은 "깨끗한 이미지에 점점 노이즈를 더하는" 과정을 보고, 그 반대(노이즈 → 이미지)를 예측하도록 훈련된다. 추론(inference) 시에는 순수 가우시안 노이즈에서 출발해, 매 스텝마다 "이 노이즈에서 노이즈를 얼마나 빼야 하는가"를 예측하며 점진적으로 이미지를 복원한다.
SDXL·FLUX는 픽셀 공간이 아니라 **잠재 공간(latent space)**에서 동작한다. VAE(Variational Autoencoder)가 512×512 같은 이미지를 64×64×4 정도의 잠재 텐서로 압축하고, 디퓨전은 이 작은 텐서에서 돌아간 뒤 VAE 디코더가 다시 픽셀로 펼친다. 이게 연산량을 극적으로 줄여준다.
[텍스트 프롬프트]
│ 텍스트 인코더 (CLIP / T5)
▼
[조건 임베딩] ──┐
▼
[랜덤 노이즈] → UNet/Transformer (N 스텝 반복) → [잠재 이미지]
│ VAE 디코더
▼
[최종 픽셀 이미지]
구조적 차이를 알면 모델 선택이 쉬워진다.
| 요소 | SDXL | FLUX | 의미 |
|---|---|---|---|
| 텍스트 인코더 | CLIP-L + CLIP-G (이중) | CLIP-L + T5-XXL | T5는 긴 자연어 문장 이해가 강함 |
| 백본 | UNet | Rectified Flow Transformer (DiT 계열) | FLUX가 텍스트·구도 충실도 우위 |
| 잠재 처리 | 잠재 디퓨전 | 잠재 + flow matching | FLUX는 적은 스텝으로 수렴 |
흔한 함정: 스텝을 무작정 올리면 좋아진다고 생각하는 것. 일정 지점(보통 SDXL 30~40, FLUX-dev 20~28)을 넘으면 이미지가 더 좋아지지 않고 GPU 시간만 낭비된다. 스케줄러가 이미 수렴했기 때문이다.
베스트 프랙티스: 새 모델을 처음 쓸 땐 한 시드를 고정하고 steps를 10·20·30·40으로 바꿔가며 그리드를 뽑아라. 그 모델의 "수렴 지점"을 눈으로 확인한 뒤 기본값을 정하는 것이 추측보다 훨씬 빠르다.
2. 핵심 파라미터 완전 정복 — steps, CFG, sampler, seed, resolution
모든 디퓨전 기반 모델(SDXL, FLUX, 그 파생)이 공유하는 파라미터들이다. Midjourney는 이걸 추상화해 숨겼지만, 내부 원리는 같다.
1) Steps (샘플링 스텝)
- 노이즈 제거 반복 횟수. 많을수록 디테일이 안정되지만 수확체감.
- SDXL: 25~40 / FLUX-dev: 20~28 / FLUX-schnell: 4 (distilled 모델이라 4스텝에 수렴하도록 학습됨)
- schnell에 30스텝을 주면 오히려 깨지거나 시간만 낭비된다 — distilled 모델은 학습된 스텝 수를 지켜야 한다.
2) CFG Scale (Classifier-Free Guidance, 가이던스)
- 프롬프트를 얼마나 "강하게" 따를지. 높으면 프롬프트 충실도↑이지만 과포화·인공적 질감·번 아웃(burn) 발생.
- SDXL: 5~8 권장 / FLUX-dev: 3.5 전후 (FLUX는 guidance distillation이 들어가 낮은 값을 씀) / FLUX-schnell: guidance 미사용(0).
- 함정: SDXL 감각으로 FLUX에 CFG 7을 주면 이미지가 타버린다. 모델마다 적정 범위가 완전히 다르다.
3) Sampler / Scheduler
- 노이즈 제거 경로를 푸는 수치 해법(ODE/SDE solver).
- 실전 추천:
DPM++ 2M Karras(품질·속도 균형),Euler a(다양성),DPM++ SDE Karras(고디테일). FLUX는 보통Euler+ flow 스케줄. Karras변형은 노이즈 스케줄을 비선형으로 배치해 적은 스텝에서 더 깨끗하다.
4) Seed
- 초기 노이즈를 결정하는 정수. 같은 seed + 같은 파라미터 + 같은 모델 = 동일 결과(재현성). 프로덕션에서 seed를 반드시 로깅하라. 좋은 결과를 다시 못 만든다.
5) Resolution
- SDXL은 총 픽셀 ~1024×1024 근처로 학습됨. 1024×1024, 1152×896, 896×1152, 1216×832 등이 안정적. 512×512를 주면 SD1.5보다 못한 결과가 나온다.
- FLUX는 1024 기준에 더 유연하지만 역시 학습 해상도 근처가 안전.
# diffusers 기준 — 파라미터가 코드에서 어떻게 매핑되는지
image = pipe(
prompt=prompt,
num_inference_steps=30, # steps
guidance_scale=6.0, # CFG
width=1024, height=1024, # resolution (학습 해상도 근처)
generator=torch.Generator("cuda").manual_seed(12345), # seed 고정
).images[0]
베스트 프랙티스: 파라미터를 하나씩만 바꿔 A/B 그리드를 만들어라. seed·prompt를 고정하고 CFG만 4·6·8로 비교하면 그 모델의 CFG 민감도를 정확히 알 수 있다. 여러 파라미터를 동시에 바꾸면 무엇이 효과를 냈는지 영원히 모른다.
3. Midjourney 실전 — 미적 완성도와 파라미터, 그리고 자동화의 현실
Midjourney는 "가장 적은 노력으로 가장 예쁜 결과"가 강점이다. 미적 편향이 강하게 들어가 있어 대충 써도 작품처럼 나온다. 대신 공식 API가 없고(2026 기준 일반 공개 프로그래매틱 API 부재), Discord 봇 또는 웹 인터페이스 중심이라 자동화는 제약이 크다.
프롬프트 구조와 파라미터 (-- 플래그):
선명한 묘사, 스타일, 조명, 카메라 --ar 16:9 --stylize 250 --chaos 15 --v 6
| 파라미터 | 역할 | 실전 팁 |
|---|---|---|
--ar W:H | 종횡비 | --ar 3:2(사진), --ar 9:16(모바일) |
--stylize (--s) | MJ 고유 미적 편향 강도 (0~1000) | 사실적 제품샷은 낮게(50~100), 아트는 높게(500+) |
--chaos (--c) | 4개 그리드의 다양성 (0~100) | 탐색 단계는 높게, 확정 단계는 0 |
--seed | 재현성 | 일관성 필요 시 고정 |
--no | 네거티브 (제외 요소) | --no text, watermark |
--style raw | 미적 편향 줄이고 프롬프트 충실도↑ | 정확한 통제가 필요할 때 |
--cref / --sref | 캐릭터/스타일 레퍼런스 (이미지 URL) | 시리즈 일관성의 핵심 |
일관성 워크플로 (캐릭터·브랜드 시리즈):
- 마스터 이미지 1장을 확정한다.
- 그 URL을
--cref <url>(캐릭터)·--sref <url>(스타일)로 후속 프롬프트에 물린다. --cw(character weight, 0~100)로 충실도를 조절한다. 얼굴만 유지하려면 낮게.
함정 1 — 자동화 우회의 ToS 리스크: 비공식 서드파티 "Midjourney API"(Discord 봇을 셀프 자동화하는 래퍼)는 계정 정지 위험이 있다. 상업 파이프라인에 넣기 전에 약관과 리스크를 반드시 검토하라. 자동화·대량 생성이 핵심 요구사항이면 처음부터 FLUX/SDXL을 고려하는 게 합리적이다.
함정 2 — 프롬프트를 길게 쓰면 더 좋다는 오해: MJ는 단어 가중을 자체적으로 한다. 형용사를 20개 욱여넣으면 서로 희석되어 오히려 평범해진다. 핵심 명사 + 스타일 + 조명 + 매체, 5~15 단어가 스위트 스팟.
함정 3 — --stylize를 사실성과 혼동: stylize는 사실성이 아니라 "MJ다운 미적 보정" 강도다. 사실적 제품 사진이 필요하면 stylize를 낮추고 --style raw를 쓰는 게 정답이다.
베스트 프랙티스: Midjourney는 사람이 골라야 하는 고품질 무드보드·컨셉·키비주얼에 쓰고, 무인 대량 생성·정밀 통제는 다른 모델에 맡기는 하이브리드 전략이 현실적이다. 좋은 결과의 seed·전체 프롬프트·sref URL을 반드시 기록해 둬야 시리즈를 이어갈 수 있다.
4. FLUX 실전 — 프롬프트 충실도와 텍스트 렌더링의 강자, 로컬과 API
FLUX(Black Forest Labs)는 현재 오픈/세미오픈 진영에서 프롬프트 충실도와 텍스트(글자) 렌더링이 가장 강한 축이다. T5-XXL 텍스트 인코더 덕에 긴 자연어 지시와 공간 관계("왼쪽에 빨간 컵, 오른쪽에 파란 책")를 비교적 잘 지킨다.
변종(variant) 구분이 가장 중요하다:
| 변종 | 라이선스/성격 | 스텝 | 가이던스 | 용도 |
|---|---|---|---|---|
| FLUX.1 [schnell] | Apache-2.0, 상업 자유 | ~4 | 미사용(0) | 빠른 초안·대량 생성 |
| FLUX.1 [dev] | 비상업 라이선스(연구·개인) | 20~28 | 3.5 전후 | 고품질, 상업은 라이선스 확인 필수 |
| FLUX.1 [pro] | API 전용(클로즈드) | — | — | 최고 품질, BFL/파트너 API |
함정 0 — 라이선스: [dev]를 상업 제품에 그냥 쓰면 라이선스 위반 소지가 있다. 상업이면 [schnell](Apache-2.0) 또는 [pro] API를 검토하라. 코드 돌리기 전에 이걸 먼저 확인해야 한다.
로컬 실행 (diffusers, schnell 예시):
import torch
from diffusers import FluxPipeline
pipe = FluxPipeline.from_pretrained(
"black-forest-labs/FLUX.1-schnell",
torch_dtype=torch.bfloat16,
)
# VRAM 절약: 모델 일부를 GPU로 그때그때 올림 (24GB 미만에서 유용)
pipe.enable_model_cpu_offload()
image = pipe(
prompt="a cozy bookstore cafe interior, warm afternoon light, "
"a wooden sign on the wall reading 'OPEN', photorealistic",
num_inference_steps=4, # schnell은 4
guidance_scale=0.0, # schnell은 guidance 미사용
height=1024, width=1024,
generator=torch.Generator("cpu").manual_seed(42),
).images[0]
image.save("out.png")
API 실행 (Replicate — 인프라 없이):
import replicate
output = replicate.run(
"black-forest-labs/flux-schnell",
input={
"prompt": "a wooden sign reading 'AINORM', clean studio lighting",
"num_outputs": 1,
"aspect_ratio": "1:1",
"output_format": "png",
},
)
print(output) # 생성 이미지 URL 리스트
VRAM 가이드: FLUX 전체 모델은 무겁다(12B 파라미터급). 24GB 미만이면 enable_model_cpu_offload(), fp8/양자화 빌드, 또는 API를 써라. 무리하게 풀 모델을 올리면 OOM으로 죽는다.
함정 1 — schnell에 긴 디테일 프롬프트: schnell은 빠른 초안용이라 dev/pro보다 디테일 표현력이 낮다. 최종 품질은 dev/pro로 가야 한다. schnell로 구도·구성을 탐색하고 좋은 seed·프롬프트를 dev로 승격하는 워크플로가 효율적이다.
함정 2 — SDXL식 네거티브 프롬프트 습관: FLUX는 SDXL처럼 별도 네거티브 프롬프트 슬롯을 핵심 기능으로 쓰지 않는다. 제외하고 싶은 건 긍정 프롬프트에서 명시적으로 다른 걸 지정하는 식으로 푸는 게 자연스럽다.
베스트 프랙티스: FLUX는 자연어 문장형 프롬프트에 강하다. SDXL식 콤마 나열 태그보다 "무엇이 어디에 어떤 조명으로 있는지"를 문장으로 서술하면 충실도가 올라간다. 글자가 들어가는 디자인(간판, 포스터 텍스트)은 FLUX의 킬러 유스케이스다.
5. SDXL 실전 — 완전한 통제권, base+refiner, 그리고 diffusers 파이프라인
SDXL(Stable Diffusion XL)은 오픈 웨이트라 가장 통제력이 크다. LoRA, ControlNet, 인페인팅, IP-Adapter, 파인튜닝 등 생태계가 가장 두껍다. 대신 "날것"이라 기본값으로는 FLUX/MJ보다 거칠 수 있고, 손이 많이 간다.
구조 — base + refiner: SDXL은 base 모델(전체 구도)과 선택적 refiner(마지막 단계 디테일 보강)로 나뉜다. refiner는 필수가 아니며, 많은 커뮤니티 파인튜닝 모델은 base만으로도 충분하다.
import torch
from diffusers import StableDiffusionXLPipeline
pipe = StableDiffusionXLPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True,
).to("cuda")
image = pipe(
prompt="product photo of a ceramic coffee mug on marble, "
"soft studio lighting, sharp focus, high detail",
negative_prompt="blurry, low quality, distorted, watermark, text, extra fingers",
num_inference_steps=30,
guidance_scale=6.5,
width=1024, height=1024,
generator=torch.Generator("cuda").manual_seed(7),
).images[0]
image.save("mug.png")
SDXL의 핵심 무기 — Negative Prompt: SDXL은 네거티브 프롬프트가 실제로 강하게 작동한다. 품질 결함을 여기서 적극 배제한다.
negative: blurry, lowres, jpeg artifacts, deformed, bad anatomy,
extra limbs, watermark, signature, text, oversaturated
해상도 — SDXL의 함정 1: SDXL은 ~1024² 근처로 학습됐다. 512×512를 주면 망가진다. 안전한 해상도 버킷:
1024×1024 (1:1)
1152×896 / 896×1152 (4:3)
1216×832 / 832×1216 (3:2)
1344×768 / 768×1344 (16:9)
함정 2 — VAE 색 깨짐: 일부 SDXL 체크포인트는 VAE가 fp16에서 NaN·검은 패치를 낸다. 별도 fp16-fix VAE를 명시 로드하거나, 문제가 있으면 fp32 VAE를 쓴다.
from diffusers import AutoencoderKL
vae = AutoencoderKL.from_pretrained("madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16)
pipe.vae = vae
함정 3 — 커뮤니티 체크포인트 ≠ 베이스: Civitai 등의 파인튜닝 모델은 권장 CFG·sampler·해상도가 베이스와 다르다. 모델 카드의 권장값을 반드시 읽고 시작하라. 베이스 감각으로 CFG를 주면 과포화된다.
메모리 최적화:
pipe.enable_vae_tiling() # 큰 이미지 VAE 디코드 시 OOM 방지
pipe.enable_xformers_memory_efficient_attention() # 설치돼 있으면
# 또는 pipe.enable_model_cpu_offload() # VRAM이 빠듯할 때
베스트 프랙티스: SDXL은 "기본 모델"이 아니라 "플랫폼"으로 생각하라. 베이스 + 좋은 파인튜닝 체크포인트 + 작업별 LoRA + ControlNet 조합이 진짜 힘이다. 정밀한 구도 통제·반복 일관성·온프레미스 요구가 있으면 SDXL이 유일한 현실적 선택지인 경우가 많다.
6. 프롬프트 엔지니어링 — 모델별로 다른 '언어'를 구사하라
가장 흔한 실수는 하나의 프롬프트 스타일을 모든 모델에 그대로 쓰는 것이다. 세 모델은 텍스트 인코더가 달라서 "잘 먹히는 프롬프트 문법"이 다르다.
프롬프트의 보편 골격 (어느 모델이든):
[피사체] + [동작/상태] + [환경/배경] + [구도/앵글] + [조명] + [매체/스타일] + [품질 수식어]
예: 젊은 여성 바리스타가 라떼를 내리는 모습, 작은 카페 안, 미디엄샷, 부드러운 창측 자연광, 35mm 필름 사진, 얕은 심도
모델별 문법 차이:
| 측면 | SDXL | FLUX | Midjourney |
|---|---|---|---|
| 선호 형식 | 콤마 태그 나열 | 자연어 문장 | 짧은 핵심 + -- 플래그 |
| 네거티브 | 강력·필수 | 거의 불필요 | --no |
| 텍스트 렌더 | 약함 | 강함 | 보통 |
| 공간 관계 | 약함 | 강함(T5) | 보통 |
| 길이 | 중간(토큰 75 의식) | 길어도 OK | 짧을수록 좋음 |
같은 의도, 모델별 프롬프트 예시:
# 의도: "OPEN 간판이 걸린 따뜻한 서점 카페"
# SDXL (태그형 + 네거티브)
prompt: cozy bookstore cafe, warm afternoon light, wooden interior,
shelves of books, photorealistic, sharp focus, 35mm
negative: blurry, lowres, distorted text, watermark, oversaturated
# FLUX (자연어 + 텍스트 명시)
prompt: A cozy bookstore cafe interior bathed in warm afternoon light.
A wooden sign on the wall clearly reads "OPEN".
Shelves of books line the walls. Photorealistic, shallow depth of field.
# Midjourney (간결 + 플래그)
prompt: cozy bookstore cafe, warm afternoon light, wooden sign reading OPEN
--ar 3:2 --style raw --s 100
가중치(emphasis) 문법 (SDXL/diffusers·A1111 계열):
(word:1.3)— 강조(가중 1.3배),(word:0.7)— 약화. compel 같은 라이브러리로 더 세밀하게 제어 가능.- 과한 가중(1.6+)은 artifact를 부른다. 1.1~1.4 범위가 안전.
함정 1 — "masterpiece, best quality, 8k, ultra detailed" 토큰 스팸: SD1.5 시절 미신이 SDXL·FLUX엔 거의 안 통하거나 오히려 해롭다. 구체적 묘사(조명·렌즈·재질)가 추상적 품질 수식어보다 훨씬 효과적이다.
함정 2 — 토큰 한도 인지 실패: CLIP 인코더는 77토큰 단위로 끊는다. SDXL에서 프롬프트가 너무 길면 뒷부분이 사실상 무시되거나 약해진다. 중요한 건 앞쪽에 배치하라.
함정 3 — 모순 프롬프트: "미니멀하고 디테일이 풍부한", "넓은 광각이고 얕은 심도" 같은 충돌 지시는 모델을 흔들어 평균적인 흐릿한 결과를 낸다.
베스트 프랙티스: 프롬프트를 코드처럼 버전 관리하라. 잘 된 프롬프트는 seed·파라미터와 함께 저장하고, 변형은 한 번에 한 요소만 바꿔라. 팀이면 프롬프트 템플릿을 변수화(피사체/스타일/조명 슬롯)해 재사용한다.
7. ControlNet과 img2img — 구도를 손으로 잡는 정밀 통제
텍스트만으로는 "포즈를 정확히 이렇게", "이 스케치 그대로", "이 깊이 구조로"를 지정할 수 없다. ControlNet은 추가 조건 이미지(엣지·포즈·깊이·스케치)를 디퓨전에 주입해 구조를 강제한다. SDXL 생태계의 핵심 무기다.
ControlNet 종류와 용도:
| 타입 | 입력 | 용도 |
|---|---|---|
| Canny | 엣지맵 | 윤곽 보존, 리스타일링 |
| Depth | 깊이맵 | 3D 공간 구조 유지 |
| OpenPose | 인체 스켈레톤 | 정확한 포즈 |
| Scribble/Lineart | 손그림·라인 | 스케치 → 완성 이미지 |
| Tile | 저해상 이미지 | 업스케일·디테일 추가 |
SDXL + Canny ControlNet (diffusers):
import torch, cv2, numpy as np
from PIL import Image
from diffusers import StableDiffusionXLControlNetPipeline, ControlNetModel
# 1) 원본에서 엣지맵 추출
img = cv2.imread("reference.png")
edges = cv2.Canny(img, 100, 200)
edges = np.stack([edges]*3, axis=-1)
control_image = Image.fromarray(edges)
# 2) ControlNet 로드
controlnet = ControlNetModel.from_pretrained(
"diffusers/controlnet-canny-sdxl-1.0", torch_dtype=torch.float16
)
pipe = StableDiffusionXLControlNetPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
controlnet=controlnet, torch_dtype=torch.float16,
).to("cuda")
# 3) 구조는 유지하되 스타일만 바꾸기
image = pipe(
prompt="a futuristic building, neon cyberpunk style, night",
image=control_image,
controlnet_conditioning_scale=0.8, # 통제 강도 (0~1+)
num_inference_steps=30,
guidance_scale=6.0,
).images[0]
controlnet_conditioning_scale 튜닝이 핵심:
1.0— 구조를 강하게 강제(원본 윤곽에 거의 묶임).0.5~0.7— 구조 참고하되 창의적 변형 허용.- 너무 높으면 프롬프트 무시·경직, 너무 낮으면 통제 의미 없음.
img2img (구조+질감 통째로 변형):
ControlNet 대신 원본 이미지 자체를 잠재 노이즈 출발점으로 쓰는 방식. strength가 핵심.
from diffusers import StableDiffusionXLImg2ImgPipeline
# strength=0.3 → 원본 거의 유지(색보정 수준)
# strength=0.7 → 구도만 참고, 내용 크게 변형
out = pipe(prompt=prompt, image=init_image, strength=0.6, guidance_scale=6.0).images[0]
함정 1 — 해상도 불일치: control 이미지와 출력 해상도가 어긋나면 구조가 왜곡된다. control 이미지를 출력 해상도(SDXL 1024 버킷)에 맞춰 리사이즈하라.
함정 2 — Canny 임계값 방치: cv2.Canny(img, low, high)의 임계값이 엣지 품질을 좌우한다. 디테일이 너무 많거나 적으면 결과가 망가진다. 대상마다 조정 필요.
함정 3 — 여러 ControlNet 무분별 결합: Depth + Pose + Canny를 다 높은 scale로 쌓으면 서로 충돌해 깨진다. 멀티 ControlNet은 각 scale을 낮춰 합이 과하지 않게.
베스트 프랙티스: "리브랜딩·스타일 변경은 ControlNet, 톤·보정은 img2img(low strength), 완전 신규는 txt2img"로 작업 유형을 분리하라. 제품 라인업처럼 구도는 같고 스타일만 바꿔야 하는 배치 작업에서 ControlNet이 압도적 생산성을 낸다.
8. 인페인팅·아웃페인팅과 LoRA — 부분 수정과 스타일 주입
생성은 끝이 아니라 시작이다. 실무의 절반은 **부분 수정(인페인팅)**과 **스타일/캐릭터 일관성(LoRA)**이다.
인페인팅 — 마스크 영역만 다시 생성: 손 망가짐, 불필요한 객체 제거, 배경 일부 교체 등. 마스크(흰색=재생성, 검정=유지)를 함께 넘긴다.
from diffusers import StableDiffusionXLInpaintPipeline
from PIL import Image
pipe = StableDiffusionXLInpaintPipeline.from_pretrained(
"diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
torch_dtype=torch.float16,
).to("cuda")
base = Image.open("photo.png").convert("RGB")
mask = Image.open("mask.png").convert("RGB") # 흰=수정, 검=유지
out = pipe(
prompt="a bouquet of fresh flowers", # 마스크 영역에 넣을 것
image=base, mask_image=mask,
strength=0.99, # 인페인팅은 보통 높게
num_inference_steps=30, guidance_scale=7.0,
).images[0]
아웃페인팅 — 캔버스 확장: 원본을 더 큰 캔버스에 배치하고, 새로 생긴 빈 영역을 마스크로 지정해 인페인팅. 종횡비 변경·여백 추가에 사용. (FLUX/SDXL inpaint, 또는 Adobe Firefly의 generative expand류 도구.)
LoRA — 가벼운 스타일/캐릭터 주입: LoRA는 전체 모델을 파인튜닝하지 않고, 작은 어댑터(수십 MB)만 학습해 특정 스타일·캐릭터·개념을 주입한다. 베이스에 "끼웠다 뺐다" 할 수 있다.
pipe.load_lora_weights("path/or/hub/my-style-lora.safetensors")
# 강도 조절 (0~1, 너무 높으면 베이스를 잡아먹음)
pipe.fuse_lora(lora_scale=0.8)
image = pipe(prompt="a portrait, <my-style>", ...).images[0]
pipe.unfuse_lora() # 다음 작업 위해 해제
여러 LoRA 조합:
pipe.load_lora_weights("styleA.safetensors", adapter_name="a")
pipe.load_lora_weights("charB.safetensors", adapter_name="b")
pipe.set_adapters(["a", "b"], adapter_weights=[0.7, 0.6])
함정 1 — 마스크 경계 이음새: 인페인팅 결과가 주변과 색·질감이 안 맞는 경우. 마스크를 살짝 블러(feather)하고, 프롬프트에 주변 맥락(조명·스타일)을 같이 써 자연스럽게 잇는다.
함정 2 — LoRA 호환성: SD1.5용 LoRA는 SDXL에 안 붙고, SDXL용은 FLUX에 안 붙는다. 베이스 아키텍처가 일치해야 한다. 또 LoRA가 학습된 베이스와 다른 체크포인트에 끼우면 효과가 약하거나 깨진다.
함정 3 — LoRA 강도 과다: lora_scale을 1.0+로 쌓으면 베이스 능력이 망가져 모든 결과가 똑같은 톤으로 수렴하고 해부학이 무너진다. 0.6~0.85에서 시작하라.
함정 4 — 트리거 워드 누락: 많은 LoRA는 학습 시 정한 트리거 토큰(예: <my-style>, ohwx person)을 프롬프트에 넣어야 발동한다. 모델 카드의 트리거를 확인하라.
베스트 프랙티스: "생성 → 마음에 드는데 일부만 아쉬움 → 그 부분만 인페인팅"이 가장 비용 효율적이다. 전체 재생성은 좋은 부분까지 잃는다. 캐릭터·브랜드 일관성은 LoRA(또는 FLUX의 레퍼런스/IP-Adapter)로 재현 가능한 자산으로 만들어 두면 시리즈 생산이 안정된다.
9. 프로덕션 통합 — API, 비동기 큐, 재현성·로깅
데모와 프로덕션은 다르다. 프로덕션에선 지연 시간, 비용, 실패 처리, 재현성, 로깅이 본질이다.
아키텍처 결정 — 자체 호스팅 vs 매니지드 API:
| 기준 | 자체 GPU(SDXL/FLUX 로컬) | 매니지드 API(Replicate, fal, BFL 등) |
|---|---|---|
| 초기 비용 | GPU·운영 부담 큼 | 0, 종량제 |
| 단가 | 대량 시 저렴 | 소량·변동 트래픽에 유리 |
| 통제 | 완전(LoRA·ControlNet 자유) | 제공 모델·옵션 한정 |
| 운영 | OOM·드라이버·스케일 직접 | 위임 |
| 콜드스타트 | 모델 로딩 수십 초 | 매니지드(여전히 존재 가능) |
핵심: 이미지 생성은 느리다(수 초~수십 초). 동기 HTTP 요청 안에서 처리하지 마라. 작업을 큐에 넣고 비동기로 처리한 뒤 결과를 폴링/웹훅으로 받는다.
# FastAPI + 작업 큐 패턴 (개념 골격)
from fastapi import FastAPI
from uuid import uuid4
app = FastAPI()
@app.post("/generate")
async def generate(req: GenReq):
job_id = str(uuid4())
# 동기로 모델 돌리지 말 것 — 큐에 적재 (Celery/RQ/Cloud Tasks 등)
enqueue_job(job_id, req.dict())
return {"job_id": job_id, "status": "queued"}
@app.get("/result/{job_id}")
async def result(job_id: str):
return get_job_status(job_id) # queued | processing | done(url) | failed
워커(실제 생성)에서 반드시 기록할 메타데이터:
record = {
"job_id": job_id,
"model": "flux-dev", # 어떤 모델/변종
"prompt": prompt,
"negative_prompt": negative,
"seed": seed, # ★ 재현성의 핵심
"steps": steps, "cfg": cfg, "sampler": sampler,
"width": w, "height": h,
"lora": [("styleA", 0.7)],
"created_at": now_iso(),
"output_url": url,
}
이걸 남기지 않으면 "이 이미지 어떻게 만들었지?"에 영원히 답할 수 없다. seed·전체 파라미터·모델 버전은 비협상 항목이다.
매니지드 API 호출 + 재시도:
import replicate, time
def gen_with_retry(prompt, retries=3):
for i in range(retries):
try:
return replicate.run("black-forest-labs/flux-dev",
input={"prompt": prompt, "seed": 42})
except Exception as e:
if i == retries - 1:
raise
time.sleep(2 ** i) # 지수 백오프
함정 1 — 콜드 스타트 무시: 자체 호스팅에서 매 요청마다 모델을 로드하면 수십 초가 날아간다. 워커 프로세스에서 모델을 한 번 로드해 상주시켜라.
함정 2 — NSFW/세이프티 필터 응답 처리 누락: 많은 API가 부적절 판정 시 빈 결과·블랙 이미지·에러를 반환한다. 이걸 "성공"으로 오해해 깨진 이미지가 사용자에게 나간다. 응답 상태·safety flag를 명시적으로 체크하라.
함정 3 — 생성 비결정성: 같은 seed라도 라이브러리/드라이버/하드웨어 버전이 바뀌면 미세하게 달라질 수 있다. 완벽한 비트 단위 재현이 필요하면 환경(컨테이너 이미지)까지 고정하라.
베스트 프랙티스: 처음엔 매니지드 API로 빠르게 검증하고, 트래픽·단가가 임계점을 넘으면 자체 호스팅으로 이전하는 단계적 전략. 모든 생성에 메타데이터를 남기고, 출력 이미지에 보이지 않는 출처 메타(또는 C2PA류)를 붙여 추적성·책임성을 확보하라.
10. 비용·성능 최적화 — 배치, 양자화, 업스케일, 캐싱
이미지 생성은 GPU 시간이 곧 돈이다. 같은 결과를 더 싸게·빠르게 뽑는 레버가 여럿 있다.
1) Steps를 비용으로 인식하라 생성 시간은 대략 steps에 비례한다. 30→20스텝은 ~33% 절감이다. 섹션 1에서 찾은 "수렴 지점"까지만 써라. schnell(4스텝)이 dev(28스텝)보다 7배 싼 이유다 — 초안·썸네일엔 schnell, 최종만 고스텝.
2) 배치(batch) 생성
같은 프롬프트로 여러 장이 필요하면 num_images_per_prompt로 묶어라. 매번 따로 호출하는 것보다 GPU 활용도가 높다.
images = pipe(prompt=prompt, num_images_per_prompt=4, ...).images
단, VRAM은 배치 크기에 비례해 늘어난다 — OOM 한계 내에서.
3) 양자화·정밀도
fp16/bf16이 기본. fp32는 거의 불필요(2배 메모리).- FLUX 같은 대형 모델은 fp8·GGUF 양자화 빌드로 VRAM을 크게 줄일 수 있다(품질 약간 손해). 24GB 미만 GPU에서 FLUX를 돌리는 현실적 방법.
4) 컴파일·어텐션 최적화
pipe.enable_xformers_memory_efficient_attention() # 메모리·속도
pipe.unet = torch.compile(pipe.unet, mode="reduce-overhead") # 첫 호출은 느리지만 이후 가속
torch.compile은 첫 실행에 컴파일 오버헤드가 크니, 상주 워커에서만 의미 있다.
5) 저해상 생성 → 업스케일 파이프라인 고해상도를 직접 생성하면 느리고 비싸며 구도도 흔들린다. 저해상(1024)으로 빠르게 생성 → 업스케일러로 키우는 2단계가 보통 더 싸고 안정적이다.
1024 생성 (빠름)
└─→ ESRGAN/Real-ESRGAN 업스케일 또는
SDXL Tile ControlNet으로 디테일 추가 업스케일(2~4x)
SDXL Tile 업스케일은 단순 보간보다 디테일을 "새로 그려" 넣어 훨씬 선명하다.
6) 캐싱·디듀프 동일 (프롬프트+파라미터+seed) 요청은 결정적이므로 결과를 캐시하라. 파라미터를 정규화해 해시 키로 쓰면 중복 생성을 막는다.
import hashlib, json
def cache_key(params: dict) -> str:
norm = json.dumps(params, sort_keys=True)
return hashlib.sha256(norm.encode()).hexdigest()
함정 1 — 과도한 해상도 직접 생성: SDXL에 2048×2048을 직접 요구하면 학습 해상도를 벗어나 인물 복제(두 머리)·반복 패턴이 생긴다. 항상 학습 해상도로 생성 후 업스케일.
함정 2 — torch.compile을 단발 스크립트에 사용: 컴파일 비용이 생성 절약분보다 커서 손해. 장기 상주 서비스에서만.
함정 3 — 배치 크기 욕심: OOM은 워커를 죽이고 큐 전체를 막는다. 안전 배치 크기를 측정하고 여유를 둬라.
베스트 프랙티스: "저스텝 초안 다량 → 사람/룰로 선별 → 선별분만 고스텝·업스케일"의 깔때기 구조가 비용 대비 품질이 가장 좋다. 비용은 steps×해상도×장수의 곱이라는 걸 항상 의식하고, 각 축을 따로 최적화하라.
11. 모델 선택 의사결정·라이선스·실무 함정 종합
마지막으로 "그래서 내 프로젝트엔 뭘 쓰지"에 답하는 의사결정 프레임과, 실무에서 반복되는 함정·법적 고려를 모았다.
의사결정 표:
| 요구사항 | 1순위 추천 | 이유 |
|---|---|---|
| 최고 미적 무드보드·키비주얼(사람이 선별) | Midjourney | 미적 완성도, 적은 노력 |
| 글자/간판/포스터 텍스트 정확도 | FLUX | T5 인코더, 텍스트 렌더 강함 |
| 자연어 지시·공간관계 충실도 | FLUX | 프롬프트 어드히어런스 |
| 정밀 구도 통제(포즈/깊이/스케치) | SDXL + ControlNet | 생태계 유일 수준 |
| 캐릭터/브랜드 시리즈 일관성 | SDXL+LoRA / FLUX 레퍼런스 | 재현 가능 자산화 |
| 온프레미스·데이터 외부 반출 금지 | SDXL / FLUX 로컬 | 자체 호스팅 |
| 무인 대량·종량제·빠른 PoC | FLUX-schnell API | 상업 자유·저비용 |
| 인페인팅·부분 수정 워크플로 | SDXL inpaint / FLUX | 마스크 기반 통제 |
라이선스 — 코드보다 먼저 확인할 것:
- FLUX [dev]: 비상업 성격 라이선스. 상업 제품에 쓰기 전 약관 확인. 상업은 schnell 또는 [pro] API.
- FLUX [schnell]: Apache-2.0 — 상업 자유.
- SDXL: 오픈 라이선스(상업 가능)이나, 사용하는 파인튜닝 체크포인트·LoRA의 개별 라이선스가 더 엄격할 수 있다. Civitai 모델마다 상업 사용·머지·재배포 조건이 다르다 — 모델 카드를 반드시 확인.
- Midjourney: 플랜·약관에 따라 상업 이용 조건이 정해짐. 결과물 권리·자동화 제약을 약관에서 확인.
- 생성물 저작권: 순수 AI 생성물은 인간 저작자성 부재로 일부 관할(예: 미국)에서 저작권이 인정되지 않을 수 있다. 상표·브랜드 자산으로 쓸 땐 법적 검토 필요.
전 모델 공통 함정 체크리스트:
- 손·손가락·치아·작은 글자는 여전히 약점 — 중요하면 인페인팅으로 보정.
- 얼굴 일관성은 txt2img만으론 안 됨 — LoRA/레퍼런스/IP-Adapter 필요.
- 저작권·상표 침해 프롬프트(실존 IP·로고·연예인) 회피 — 법적·약관 리스크.
- 세이프티 필터 반환값을 성공으로 오인하지 말 것(섹션 9).
- 재현성 메타데이터 미기록 — 좋은 결과를 못 만든다(섹션 9).
- 모델 버전 드리프트 — API 모델이 조용히 업데이트되면 같은 프롬프트 결과가 바뀐다. 모델 버전을 핀(pin)하라.
- 워터마크/출처 표기 — 생성 이미지임을 표시·추적할 책임(플랫폼 정책·규제 대응).
하이브리드 파이프라인 (실무 권장 패턴):
Midjourney/FLUX → 컨셉·무드 탐색, 키비주얼 후보
│ (좋은 구도/스타일 선택)
SDXL + ControlNet → 구도 고정, 변형·배치 양산
│
SDXL/FLUX inpaint → 결함 보정, 부분 교체
│
Upscale (Tile/ESRGAN) → 최종 해상도
│
메타데이터+출처 기록 → 프로덕션 적재
최종 베스트 프랙티스: "하나의 만능 모델"을 찾지 말고 단계별로 최적 모델을 갈아 끼우는 파이프라인을 설계하라. 각 단계의 입력/출력과 파라미터를 코드·메타데이터로 관리하면, 생성이 일회성 마법이 아니라 재현·확장·감사 가능한 엔지니어링 자산이 된다. 이것이 데모와 프로덕션을 가르는 진짜 차이다.