Published on

Stable Diffusion VRAM OOM, xFormers·Tiling 최적화

Authors

Stable Diffusion을 로컬 GPU에서 돌리다 보면 가장 흔하게 마주치는 벽이 CUDA out of memory 입니다. 특히 1024x1024 이상, Hires.fix, ControlNet, 여러 LoRA/텍스트 인버전, 고스텝 샘플링을 겹치면 VRAM이 급격히 튑니다.

이 글은 “왜 OOM이 나는지”를 메모리 관점에서 쪼개고, 효과가 큰 두 축인 xFormers(메모리 효율 어텐션)와 Tiling(VAE/Latent 타일링)을 중심으로, 품질을 크게 해치지 않으면서 VRAM을 줄이는 실전 설정을 제시합니다.

관련해서 로컬 LLM 최적화에서 OOM을 다루는 글도 함께 보면 메모리 병목을 보는 감이 빨리 잡힙니다: Transformers 로컬 LLM 느림·OOM, 4bit+FlashAttn2

VRAM OOM이 나는 “진짜” 지점들

Stable Diffusion 파이프라인은 대략 다음 덩어리로 VRAM을 씁니다.

  1. UNet 추론(확산 과정): 스텝마다 반복 실행되며, 어텐션과 피처맵이 크게 잡아먹습니다.
  2. 텍스트 인코더(CLIP): 프롬프트 길이/배치에 따라 메모리 사용이 늘지만, 보통 UNet보다 영향이 작습니다.
  3. VAE 디코드/인코드: 최종 이미지로 변환하는 구간에서 순간적으로 피크가 뜨는 경우가 많습니다.
  4. 부가 모듈: ControlNet, IP-Adapter, 여러 LoRA, 업스케일/후처리 등이 누적됩니다.

OOM은 “평균 사용량”이 아니라 피크(peak) 에서 터집니다. 그래서 최적화는 대개 피크를 깎는 방향으로 접근해야 합니다.

해상도가 VRAM에 미치는 영향: 거의 제곱으로 증가

확산 모델은 이미지 공간이 아니라 latent 공간(보통 H/8 x W/8)에서 돌아가지만, 해상도를 올리면 피처맵 크기가 같이 커집니다. 대략적으로 VRAM은 H x W에 비례해 증가하고, 어텐션/피처맵 누적 때문에 체감은 더 가파르게 느껴집니다.

예를 들어 512에서 잘 되던 설정이 768이나 1024로 가면, 단순히 2배가 아니라 훨씬 더 크게 튑니다. 게다가 batch size를 올리면 곱으로 증가합니다.

1) xFormers: 메모리 효율 어텐션으로 UNet 피크 낮추기

xFormers는 어텐션 계산을 더 메모리 효율적인 커널로 바꿔서, 특히 고해상도에서 VRAM 피크를 크게 줄여줍니다. 체감상 “같은 VRAM에서 해상도를 한 단계 올릴 수 있게” 해주는 경우가 많습니다.

어떤 상황에서 특히 효과가 큰가

  • 768 이상 고해상도
  • ControlNet 등으로 UNet이 무거워진 상황
  • 배치가 2 이상이거나, 여러 장을 한 번에 뽑는 경우

설치/활성화(Automatic1111 기준)

환경마다 다르지만, 보통은 WebUI 실행 인자에서 켜고 끌 수 있습니다.

# 예시: webui-user.sh 또는 실행 커맨드에 추가
# xFormers 활성화
python launch.py --xformers

만약 드라이버/파이토치/쿠다 조합이 맞지 않아 설치가 꼬이면, “성능보다 안정성”을 우선해 PyTorch 버전과 CUDA 런타임을 맞추는 것이 중요합니다. xFormers는 커널 호환성 이슈가 잦아서, 되는 조합을 고정해두는 게 운영 비용이 낮습니다.

xFormers를 켰는데도 OOM이면

xFormers는 주로 어텐션 메모리를 줄입니다. 그런데 OOM이 VAE 디코드 순간 피크에서 터지거나, ControlNet이 너무 많거나, Hires.fix가 과도하면 여전히 터질 수 있습니다. 이때는 아래의 Tiling이 훨씬 직접적으로 먹힙니다.

2) Tiling: VAE/Latent 타일링으로 “순간 피크”를 분할

타일링은 큰 텐서를 한 번에 처리하지 않고, 여러 타일로 쪼개서 순차 처리하는 방식입니다. 장점은 명확합니다.

  • VRAM 피크를 강하게 낮춤
  • 특히 VAE 디코드에서 OOM이 나는 케이스에 특효

대신 단점도 있습니다.

  • 타일 경계에서 미세한 이음새(seam)가 생길 수 있음
  • 속도가 느려질 수 있음

하지만 적절한 overlap(겹침)과 타일 크기 선택으로 seam을 상당히 억제할 수 있습니다.

A1111에서 흔한 타일링 옵션들

구성에 따라 메뉴 이름이 조금씩 다르지만, 흔히 다음이 등장합니다.

  • VAE tiling 또는 Tiled VAE
  • Latent tiling(확산 과정 자체를 타일링)

실전에서는 보통 VAE 타일링만으로도 OOM이 해결되는 경우가 많고, 그래도 안 되면 latent 타일링까지 고려합니다.

diffusers에서 VAE 타일링 예시 코드

WebUI가 아니라 파이썬에서 diffusers로 파이프라인을 돌리는 경우, VAE 타일링을 코드로 켤 수 있습니다.

import torch
from diffusers import StableDiffusionPipeline

model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
)
pipe = pipe.to("cuda")

# VAE 타일링 활성화: 큰 해상도에서 VRAM 피크를 낮춤
pipe.vae.enable_tiling()

# 메모리 절약 옵션(상황에 따라)
pipe.enable_attention_slicing()

image = pipe(
    prompt="a photo of a cat, ultra detailed",
    height=1024,
    width=1024,
    num_inference_steps=30,
).images[0]

image.save("out.png")

주의할 점은, 타일링은 “무조건 켜면 좋은 스위치”라기보다 OOM이 나는 구간을 특정해서 쓰는 도구입니다. 예를 들어 512 위주 작업에서는 오히려 속도만 느려질 수 있습니다.

xFormers와 Tiling을 같이 쓸 때의 우선순위

현장에서 추천하는 적용 순서는 다음입니다.

  1. xFormers 활성화: UNet 어텐션 피크 절감
  2. VAE 타일링 활성화: 디코드 피크 절감
  3. 그래도 안 되면 latent 타일링 또는 해상도/배치 감소

즉, xFormers는 “상시 켜두는 편익”이 크고, 타일링은 “필요할 때 피크를 쪼개는 응급 처치” 성격이 강합니다.

함께 적용하면 좋은 메모리 절약 옵션(부작용 포함)

1) --medvram / --lowvram (WebUI)

VRAM이 작은 GPU에서 효과가 있지만, 속도 저하가 큽니다.

python launch.py --xformers --medvram
  • --medvram: 타협점
  • --lowvram: 정말 안 될 때만

2) Half precision(fp16) / bf16

대부분의 컨슈머 GPU에서는 fp16이 기본 선택입니다. 다만 특정 GPU/드라이버 조합에서 bf16이 더 안정적일 수 있습니다.

  • 장점: VRAM 절감, 속도 개선 가능
  • 단점: 일부 설정에서 NaN/검은 이미지 등 수치 불안정 가능

3) Attention slicing

어텐션을 슬라이스로 나눠 계산해서 VRAM을 줄입니다.

  • 장점: VRAM 감소
  • 단점: 속도 감소

diffusers에서는 위 코드처럼 pipe.enable_attention_slicing()로 켭니다.

4) 배치/동시 생성 수 줄이기

batch size 또는 한 번에 뽑는 이미지 수가 2 이상이면 VRAM이 곱으로 늘어납니다. OOM이 난다면 가장 먼저 1로 고정해보는 게 빠릅니다.

5) Hires.fix의 함정

Hires.fix는 “두 번 생성”에 가깝습니다.

  • 1차: 낮은 해상도 latent 생성
  • 2차: 업스케일 후 추가 디노이즈

2차 단계에서 UNet/VAE가 다시 돌면서 피크가 튀기 때문에, OOM의 주범이 되기 쉽습니다. 해결은 다음 중 하나입니다.

  • 업스케일 배율을 낮추기
  • denoise strength를 조절해 스텝/부하 줄이기
  • VAE 타일링 켜기
  • 아예 외부 업스케일러로 분리(후처리 파이프라인)

OOM을 “재현 가능하게” 잡는 체크리스트

OOM은 설정 조합이 많아 감으로 잡으면 시간이 오래 걸립니다. 아래 순서로 “한 번에 하나씩” 바꾸면 원인 추적이 빨라집니다.

  1. 해상도 고정: 예를 들어 1024x1024로 고정
  2. batch size 고정: 1
  3. ControlNet/LoRA 전부 끄기: 순정으로 테스트
  4. xFormers on/off 비교
  5. VAE 타일링 on/off 비교
  6. 그래도 터지면 Hires.fix/업스케일 단계 분리

이 과정은 웹 성능에서 Long Task를 쪼개 원인을 특정하는 방식과 유사합니다. 문제를 덩어리로 보지 말고 “피크를 만드는 구간”을 분해해 확인하는 게 핵심입니다: Chrome INP 급등 원인 - Long Task 분해 실전

실전 권장 프리셋(대략적인 가이드)

GPU VRAM에 따라 “일단 성공”을 목표로 하는 조합을 제안합니다. 모델/ControlNet/샘플러에 따라 달라지니, 아래는 출발점으로만 보세요.

8GB VRAM

  • xFormers: 켬
  • 해상도: 512 또는 768(상황에 따라)
  • Hires.fix: 가능하면 피하거나 보수적으로
  • VAE 타일링: 768 이상 또는 OOM 시 켬
  • ControlNet: 1개부터 시작

12GB VRAM

  • xFormers: 켬
  • 해상도: 768 안정권, 1024는 설정에 따라
  • VAE 타일링: 1024에서 OOM 시 켬
  • Hires.fix: 업스케일 배율/스텝을 보수적으로

24GB VRAM

  • 대부분의 경우 xFormers만으로도 충분
  • 다만 1536 이상, 다중 ControlNet, 대형 배치에서는 타일링이 여전히 의미 있음

품질 저하를 최소화하는 타일링 팁

타일링에서 가장 신경 써야 하는 건 경계 이음새입니다.

  • 타일 크기를 너무 작게 하지 않기
  • 가능하면 overlap(겹침) 옵션을 키기
  • seam이 보이면 VAE 타일링만 켜고 latent 타일링은 끄는 방향부터 시도

또한 후처리에서 약한 디노이즈나 블러가 seam을 숨기는 데 도움이 되기도 하지만, 과하면 디테일이 죽으니 최소한으로만 적용하는 게 좋습니다.

결론: OOM은 “피크를 깎는 게임”이다

Stable Diffusion의 VRAM OOM은 단순히 “VRAM이 부족해서”라기보다, 특정 단계에서 피처맵/어텐션/디코드가 겹치며 순간 피크가 터지는 문제인 경우가 많습니다.

  • UNet 어텐션 피크는 xFormers로 먼저 깎고
  • VAE 디코드 피크는 Tiling으로 쪼개고
  • 그래도 안 되면 배치, Hires.fix, ControlNet 수를 줄여 “피크의 곱”을 줄이는 것이 정석입니다.

이 순서대로 적용하면, 같은 GPU에서도 한 단계 높은 해상도/복잡한 파이프라인을 훨씬 안정적으로 굴릴 수 있습니다.