Published on

Stable Diffusion 색감·품질 붕괴, VAE·fp16·sRGB로 복구

Authors

서로 다른 체크포인트를 쓰는데도 결과가 유독 탁해지고(회색빛), 피부가 붉게 뜨거나, 그라데이션이 계단처럼 깨지고(밴딩), 디테일이 뭉개지는 경우가 있습니다. 흔히 “프롬프트 문제”로 오해하지만, 실제로는 VAE(디코더) 불일치, fp16 정밀도/클리핑, 색공간(ICC 프로파일) 처리가 겹치며 발생하는 경우가 많습니다.

이 글은 Stable Diffusion(WebUI/ComfyUI/파이프라인 공통)에서 색감·품질 붕괴를 재현 가능하게 진단하고, VAE 교체와 fp16 이슈 회피, 그리고 sRGB ICC 기반의 안전한 저장/후처리로 안정화하는 실전 체크리스트를 제공합니다.

증상으로 빠르게 원인 범주 나누기

아래 증상은 서로 비슷해 보여도 원인 레이어가 다릅니다.

1) 색이 탁하고 콘트라스트가 죽는다

  • 전반적으로 회색 막이 낀 느낌
  • 검은색이 검지 않고 떠 보임
  • 채도가 낮아지고 피부 톤이 생기 없음

가능성이 큰 원인

  • 체크포인트와 VAE 불일치
  • 디코딩 과정에서의 정밀도 손실(fp16) 또는 특정 백엔드의 디코드 버그

2) 과포화/붉은기/녹색기 등 색이 비정상적으로 튄다

  • 특정 색 채널이 과하게 밀리는 느낌
  • 같은 시드인데 저장 방식에 따라 색이 달라짐

가능성이 큰 원인

  • ICC 프로파일 미포함/무시로 인한 색공간 해석 불일치
  • 뷰어/편집기에서 색관리 동작이 제각각

3) 밴딩, 블록 노이즈, 디테일 뭉개짐

  • 하늘/피부 그라데이션에 계단 현상
  • 미세 디테일이 “물감처럼” 뭉침

가능성이 큰 원인

  • fp16 연산에서의 클리핑/언더플로/오버플로
  • VAE 디코더에서의 정밀도 이슈
  • 저장 포맷/후처리(특히 JPEG)로 인한 손실

핵심 개념: VAE, 정밀도, 색공간은 “서로 다른 층”이다

  • VAE: latent를 RGB 이미지로 복원하는 디코더. 체크포인트마다 기대하는 VAE 성향이 달라서 불일치하면 색감/콘트라스트가 급격히 틀어질 수 있습니다.
  • fp16: GPU 메모리 절약을 위해 half precision을 쓰면 빠르고 가볍지만, 특정 연산 경로에서 값이 잘리거나(클리핑) 정밀도 손실이 누적될 수 있습니다.
  • sRGB/ICC: 생성된 픽셀 값 자체는 같아도 “이 숫자가 어떤 색공간의 값인가”를 명시하지 않으면, 앱마다 다르게 해석해 색이 달라 보입니다.

이 셋을 분리해 다루면, “왜 내 PC에서는 정상인데 업로드하면 이상해지지?” 같은 문제도 깔끔하게 정리됩니다.

1단계: VAE 교체로 색감 붕괴부터 잡기

언제 VAE를 의심해야 하나

  • 같은 체크포인트/같은 프롬프트/같은 시드인데, 환경을 바꾸면 색이 확 달라짐
  • 특정 모델에서만 유독 탁하거나 붉게 뜸
  • WebUI에서 VAE: Automatic일 때만 이상하고, 특정 VAE를 고정하면 안정적

실전 권장 전략

  1. **VAE를 “자동”이 아닌 “명시적으로 고정”**합니다.
  2. 체크포인트 제작자가 권장하는 VAE가 있으면 그걸 우선 사용합니다.
  3. SD 1.5 계열과 SDXL 계열은 VAE 기대치가 다르므로 섞지 않습니다.

A1111(WebUI) 기준 체크

  • Settings의 VAE 관련 옵션에서
    • VAE를 자동 추정하지 말고 특정 파일로 고정
    • “VAE를 fp16으로 돌릴지” 옵션이 있다면, 문제 재현 시 fp32로 테스트

ComfyUI에서 VAE 교체 예시

아래는 VAE를 명시적으로 연결하는 전형적인 구성입니다.

CheckpointLoaderSimple
  ├─ model
  ├─ clip
  └─ vae  ───────────────┐
                    VAEDecode  ──► SaveImage
                    samples (latent)

추가로, 외부 VAE를 쓰려면 VAELoader를 사용해 VAEDecode에 연결합니다.

VAELoader(vae_name="your_vae.safetensors") ──► VAEDecode

VAE 교체 테스트 팁(재현성 확보)

  • 샘플러/스텝/CFG/시드/해상도를 고정
  • 후처리(업스케일, 하이레즈, 리터치) 모두 끄고 베이스 생성만 비교
  • 같은 latent를 저장/재디코드하는 기능이 있다면, “디코드만 바꿔서” 비교

2단계: fp16 클리핑/정밀도 이슈 회피하기

VAE를 바꿔도 밴딩이 남거나, 특정 상황에서만 결과가 무너진다면 fp16 경로를 의심해야 합니다. 특히 아래 조합에서 문제가 잘 드러납니다.

  • 고해상도 + 하이레즈 픽스 + 디노이즈가 낮음
  • 특정 VAE/특정 커스텀 노드에서 half 연산
  • xFormers/SDPA/FlashAttention 등 백엔드 변경 후 품질 변화

빠른 진단: fp32로 한 번만 돌려보기

가능하면 다음 중 하나로 “품질이 돌아오는지” 확인합니다.

  • VAE만 fp32로 디코드
  • 전체 UNet까지 fp32로 실행(느리지만 진단에는 확실)

품질이 fp32에서 정상화되면, 원인은 대체로

  • half 정밀도 누적 오차
  • 특정 레이어에서의 오버플로/언더플로
  • VAE 디코드에서의 클리핑 중 하나입니다.

Diffusers 파이프라인에서 안전한 설정 예시

아래 예시는 “학습이 아니라 추론” 기준이며, 문제 진단용으로 fp32 또는 VAE만 fp32로 두는 패턴을 보여줍니다.

import torch
from diffusers import StableDiffusionPipeline, AutoencoderKL

model_id = "runwayml/stable-diffusion-v1-5"

# 1) 기본 파이프라인은 fp16
pipe = StableDiffusionPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
    safety_checker=None,
)
pipe = pipe.to("cuda")

# 2) VAE만 fp32로 교체(디코드 품질 안정화에 자주 도움)
vae = AutoencoderKL.from_pretrained(
    model_id,
    subfolder="vae",
    torch_dtype=torch.float32,
).to("cuda")
pipe.vae = vae

prompt = "portrait photo, natural skin tone, soft light"
image = pipe(prompt, num_inference_steps=30).images[0]
image.save("out.png")

핵심은 “모든 걸 fp32로”가 아니라, 문제의 병목인 VAE 디코드만 fp32 두는 선택지가 꽤 효과적이라는 점입니다.

fp16 클리핑을 줄이는 운영 팁

  • VAE/UNet 중 하나라도 fp32로 올렸을 때 개선되면, 그 레이어를 우선 고정
  • 백엔드(xFormers/SDPA) 변경 후 품질이 흔들리면, 동일 시드로 A/B 테스트해 고정
  • 저장 포맷은 우선 PNG로 고정(후처리 단계에서 손실 제거)

3단계: sRGB ICC 프로파일로 “보는 색”을 고정하기

모델이 생성한 픽셀 값이 정상이어도, 저장/업로드/뷰어에서 색이 달라지는 경우가 많습니다. 특히

  • 어떤 뷰어는 ICC를 무시
  • 어떤 플랫폼은 업로드 시 메타데이터를 제거
  • 어떤 편집기는 작업 색공간을 임의로 바꾸거나 디스플레이 프로파일을 잘못 적용 같은 일이 벌어집니다.

결론: 최종 산출물은 sRGB로 명시하고 ICC를 포함하자

웹/모바일/대부분의 뷰어에서 가장 안전한 타겟은 sRGB입니다.

  • 작업 중: 광색역(P3, AdobeRGB)을 쓰더라도
  • 배포/업로드: sRGB로 변환 + ICC 포함

ImageMagick로 sRGB 변환 + ICC 포함

작업 파일이 어떤 색공간이든, 최종 결과는 sRGB로 맞추는 게 안전합니다.

# 입력이 어떤 프로파일이든 sRGB로 변환하고 프로파일을 포함
magick input.png -profile sRGB.icc -strip -profile sRGB.icc output.png

여기서 -strip은 메타데이터를 제거하지만 ICC까지 날릴 수 있으니, -strip 후에 다시 -profile로 sRGB를 넣는 순서를 권장합니다.

Pillow(Python)로 sRGB ICC 포함 저장

서버 사이드에서 생성 결과를 저장/리사이즈하면서 ICC를 포함하고 싶을 때 유용합니다.

from PIL import Image, ImageCms

img = Image.open("out.png").convert("RGB")

# sRGB 프로파일 생성(플랫폼에 따라 다를 수 있어, 운영에서는 파일로 관리 권장)
srgb = ImageCms.createProfile("sRGB")

# Pillow는 icc_profile 바이트를 요구
icc_bytes = ImageCms.ImageCmsProfile(srgb).tobytes()

img.save("out_srgb.png", icc_profile=icc_bytes)

운영 환경에서는 sRGB.icc 파일을 리포지토리에 포함하거나, 컨테이너 이미지에 넣어 항상 동일한 프로파일을 쓰는 편이 재현성이 좋습니다.

“업로드하면 색이 바뀌는” 케이스의 실전 대응

  • 업로드 대상이 ICC를 제거하는 플랫폼이면, 플랫폼이 기대하는 sRGB 값으로 이미 변환해 두는 게 최선입니다.
  • 썸네일 생성/리사이즈 파이프라인에서도 ICC를 유지하거나, 마지막에 다시 sRGB로 확정합니다.

체크리스트: 원인별로 가장 효율적인 해결 순서

A. 생성 직후부터 색이 이상하다

  1. VAE를 자동이 아닌 고정으로 변경
  2. 체크포인트 권장 VAE로 교체
  3. VAE 디코드만 fp32로 테스트

B. 생성물은 정상인데, 저장/업로드/뷰어에서 색이 바뀐다

  1. 최종 산출물을 sRGB로 변환
  2. ICC 프로파일 포함 저장
  3. 리사이즈/압축 파이프라인에서도 동일 정책 적용

C. 밴딩/뭉개짐이 랜덤하게 발생한다

  1. 동일 시드로 fp16 vs fp32 비교
  2. VAE fp32 고정으로 개선되는지 확인
  3. 백엔드/최적화 옵션 변경을 최소화하고 A/B 테스트로 고정

운영 관점: “재현 가능한 파이프라인”이 품질을 지킨다

Stable Diffusion 품질 이슈는 감각적으로 접근하면 끝이 없습니다. 대신 아래처럼 “환경을 고정”하면 문제를 빠르게 분리할 수 있습니다.

  • 모델(체크포인트) 버전 고정
  • VAE 파일명/해시 고정
  • fp16/fp32 정책 고정(특히 VAE)
  • 최종 저장은 PNG + sRGB + ICC 포함
  • 배치/서버에서 동일한 컨테이너 이미지로 실행

이 접근은 인프라/배포에서도 그대로 통합니다. 예를 들어, 환경 차이로만 발생하는 문제를 줄이려면 리소스/설정 편차를 빠르게 진단하는 습관이 중요한데, 쿠버네티스에서도 비슷합니다. 관련해서는 Kubernetes OOMKilled 메모리 튜닝 실전 가이드처럼 “재현 가능한 관측과 원인 분리”가 핵심입니다.

또한 생성 API를 외부에 노출해 운영한다면, 트래픽 급증 시 재시도/백오프 설계를 같이 챙겨야 합니다. 장애 시 품질 이슈로 오인되는 케이스도 많으니, OpenAI 429/Rate Limit 재시도·백오프 설계 같은 패턴을 참고해 호출 안정성을 확보하는 것도 도움이 됩니다.

마무리: 가장 흔한 정답 조합

색감·품질 붕괴를 가장 빠르게 복구하는 조합은 보통 다음 순서로 수렴합니다.

  1. VAE를 명시적으로 고정(자동 추정 끄기)
  2. 문제가 지속되면 VAE 디코드만 fp32
  3. 최종 산출물은 PNG로 저장 + sRGB 변환 + ICC 포함

프롬프트를 바꾸기 전에, 디코딩과 색관리부터 고정하면 “같은 시드인데 왜 다르지?” 같은 스트레스를 크게 줄일 수 있습니다.