Published on

Stable Diffusion VRAM OOM, xFormers·FP8로 줄이기

Authors

서버나 로컬 GPU에서 Stable Diffusion을 돌리다 보면, 가장 흔한 장애가 CUDA out of memory(VRAM OOM)입니다. 특히 1024x1024 이상, 배치 증가, ControlNet/LoRA 다중 적용, 혹은 hires.fix 같은 2단계 업스케일이 겹치면 “갑자기” 터지는 것처럼 보입니다. 하지만 대부분은 예측 가능한 메모리 패턴(특히 어텐션) 때문에 발생합니다.

이 글은 OOM의 구조를 짚고, xFormers 메모리 효율 어텐션FP8(가능한 환경에서) 또는 혼합정밀을 중심으로 VRAM을 줄이는 방법을 정리합니다. 마지막에는 “무조건 켜면 좋은 옵션”이 아니라, 언제 무엇이 효과적인지를 기준으로 체크리스트를 제공합니다.

VRAM OOM이 나는 진짜 이유: 어텐션이 폭발한다

Stable Diffusion의 메모리 사용은 크게 아래로 나뉩니다.

  1. 모델 가중치(Weights): UNet, VAE, 텍스트 인코더 등. 로드만 해도 일정 VRAM을 점유합니다.
  2. 활성화(Activations): 디노이징 스텝마다 중간 텐서가 생깁니다. 해상도와 배치에 비례합니다.
  3. 어텐션(Attention) 중간 버퍼: 여기서 OOM이 가장 빈번합니다.

특히 UNet 내부의 크로스/셀프 어텐션은 텐서 크기에 따라 메모리 요구량이 급증합니다. 직관적으로는 “해상도 2배면 메모리 2배”라고 생각하기 쉬운데, 어텐션은 경우에 따라 그보다 더 가파르게 늘어납니다.

그래서 다음 조합이 OOM을 잘 만듭니다.

  • 1024x1024 + batch_size 증가
  • hires.fix(2패스) + 고해상도 업스케일
  • ControlNet 여러 개 + 높은 해상도
  • SDXL(기본적으로 더 무거움) + 디폴트 설정

해결책은 어텐션 메모리를 줄이거나, 정밀도를 낮춰 텐서 크기를 줄이거나, 계산을 쪼개서(타일/슬라이싱) 피크를 낮추는 것입니다.

1) xFormers: 메모리 효율 어텐션의 정석

xFormers는 PyTorch에서 어텐션을 더 메모리 효율적으로 계산하도록 돕는 라이브러리입니다. Stable Diffusion 계열에서 체감 효과가 큰 이유는, 기본 어텐션 구현이 만드는 중간 버퍼를 줄여 피크 VRAM을 낮출 수 있기 때문입니다.

xFormers가 특히 잘 듣는 상황

  • 고해상도(특히 SDXL의 1024 계열)
  • 배치가 2 이상
  • ControlNet을 여러 개 쓰는 워크플로

설치(예: Linux, CUDA 환경)

환경마다 호환성이 민감합니다. 가장 안전한 접근은 “현재 PyTorch/CUDA 조합에 맞는 xFormers 휠”을 쓰는 것입니다.

# 가상환경 권장
python -m venv .venv
source .venv/bin/activate

# PyTorch 설치 (예시: CUDA 12.1)
pip install --index-url https://download.pytorch.org/whl/cu121 torch torchvision torchaudio

# xFormers 설치
pip install xformers

설치 후에는 실제로 활성화됐는지 확인해야 합니다. 툴에 따라 다르지만, Diffusers 기반이면 코드로 확인할 수 있습니다.

import torch
from diffusers import StableDiffusionXLPipeline

pipe = StableDiffusionXLPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
).to("cuda")

# xFormers 메모리 효율 어텐션 활성화
pipe.enable_xformers_memory_efficient_attention()

prompt = "a cinematic portrait photo, 35mm lens"
image = pipe(prompt, num_inference_steps=30).images[0]
image.save("out.png")

흔한 함정: 설치는 됐는데 OOM이 그대로인 경우

  • PyTorch 버전과 xFormers가 맞지 않아 내부적으로 비활성화되는 경우가 있습니다.
  • 일부 환경에서는 xFormers가 활성화되어도 특정 연산 경로에서 효과가 제한될 수 있습니다.

이럴 때는 로그를 확인하거나, 파이프라인이 제공하는 “활성화 여부” 확인 API가 있는지 문서를 확인하세요.

2) FP8: 가능하면 가장 강력한 VRAM 절감 카드

FP16(half)만으로도 VRAM이 줄지만, FP8은 더 큰 폭으로 줄일 수 있는 옵션입니다. 다만 FP8은 “아무 GPU에서나” 되는 기능이 아닙니다.

FP8이 현실적으로 가능한 조건

  • 최신 NVIDIA GPU(예: Hopper 계열 등)에서 FP8 텐서 코어 지원
  • PyTorch 및 관련 커널이 FP8 경로를 지원
  • 사용하는 라이브러리(예: Diffusers, ComfyUI, A1111 포크)가 FP8 가중치/연산을 실제로 지원

즉, FP8은 “옵션만 켜면 끝”이 아니라 하드웨어·소프트웨어 스택이 맞아야 합니다.

FP8을 못 쓰는 환경에서의 대안

FP8이 어렵다면 아래 조합이 실전에서 가장 많이 쓰입니다.

  • UNet: float16 또는 bfloat16
  • VAE: float16 또는 필요 시 CPU 오프로딩
  • 텍스트 인코더: float16 + 필요 시 오프로딩

Diffusers에서는 다음처럼 혼합정밀을 기본으로 가져갈 수 있습니다.

import torch
from diffusers import StableDiffusionPipeline

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

# xFormers까지 함께
pipe.enable_xformers_memory_efficient_attention()

image = pipe(
    "highly detailed photo, studio lighting",
    height=768,
    width=768,
    num_inference_steps=25,
).images[0]
image.save("out.png")

3) VRAM 피크를 낮추는 보조 옵션들(효과 큰 순)

OOM은 “총 사용량”보다 “피크”가 문제인 경우가 많습니다. 그래서 아래 옵션이 잘 먹힙니다.

(1) Attention slicing

어텐션 계산을 쪼개서 피크 VRAM을 낮춥니다. 속도는 다소 느려질 수 있습니다.

pipe.enable_attention_slicing()

(2) VAE slicing / tiling

VAE 디코딩이 터지는 케이스(고해상도, 배치 증가)에서 효과적입니다.

pipe.enable_vae_slicing()
# 또는
pipe.enable_vae_tiling()

(3) CPU offload

VRAM이 정말 빡빡하면 CPU로 일부 모듈을 내리는 방법이 있습니다. 속도는 느려지지만 “돌아가게” 만드는 데는 강력합니다.

pipe.enable_model_cpu_offload()

4) “이 설정이면 무조건 된다” 체크리스트

아래는 SDXL 기준으로, VRAM이 부족한 환경에서 실패 확률을 낮추는 조합입니다.

8GB 전후

  • 해상도: 768x768 또는 더 낮게 시작
  • torch_dtype=float16
  • xFormers 활성화
  • attention slicing 활성화
  • VAE tiling 활성화
  • 배치: 1
  • hires.fix는 가급적 피하거나 업스케일 배수를 낮게

12GB 전후

  • 해상도: 1024x1024도 가능하지만 ControlNet/LoRA 다중이면 위험
  • xFormers 활성화
  • 필요 시 VAE tiling
  • 배치 1 유지, 대신 시드/프롬프트 탐색을 반복

24GB 이상

  • 대부분의 워크플로에서 OOM 빈도가 크게 줄지만
  • ControlNet 다중 + 고해상도 + 배치 증가 + 2패스 업스케일을 동시에 하면 여전히 터질 수 있음

5) OOM 디버깅: “원인 모듈”을 좁히는 방법

OOM이 났을 때 “그냥 VRAM이 부족하구나”로 끝내면, 다음에도 같은 워크플로에서 반복됩니다. 아래처럼 원인을 좁히면 해결이 빨라집니다.

PyTorch 메모리 스냅샷 확인

import torch

print(torch.cuda.memory_summary())

실행 중 최대 사용량 체크

import torch

torch.cuda.reset_peak_memory_stats()
# ... inference 실행 ...
print("peak bytes:", torch.cuda.max_memory_allocated())

여기서 피크가 급증하는 지점이 VAE인지, UNet인지, 혹은 ControlNet 경로인지 감을 잡을 수 있습니다.

6) xFormers와 FP8을 같이 쓸 때의 주의점

  • xFormers는 “어텐션 커널” 최적화이고, FP8은 “정밀도/형식” 최적화입니다. 둘은 경쟁이 아니라 보완 관계입니다.
  • 다만 FP8은 지원이 제한적이라, 실제 운영에서는 FP16 + xFormers + slicing/tiling 조합이 가장 범용적입니다.
  • 품질 관점에서 정밀도를 낮추면 미세한 변화가 생길 수 있으니, 대표 프롬프트 3~5개로 회귀 테스트를 권장합니다.

7) 운영 관점 팁: OOM을 장애로 다루기

Stable Diffusion을 서비스/배치로 돌리면 OOM은 단순한 개인 PC 문제를 넘어 “장애”가 됩니다. 재현과 로그가 중요합니다. 이 관점은 인퍼런스 서버 지연 문제를 다루는 방식과 유사합니다.

  • 요청 파라미터(해상도, 스텝, 배치, ControlNet 수)를 로그로 남기기
  • 워크플로별 “안전 상한”을 정책으로 두기
  • OOM 발생 시 자동으로 해상도/배치를 낮춰 재시도하는 폴백 전략

인퍼런스 장애를 체계적으로 추적하는 접근은 아래 글도 참고할 만합니다.

결론: 가장 현실적인 처방 순서

VRAM OOM을 가장 빠르게 줄이는 “실전 우선순위”는 보통 이렇습니다.

  1. xFormers 활성화(가능하면 항상)
  2. float16 또는 bfloat16 혼합정밀로 고정
  3. attention slicing, VAE tiling/slicing로 피크 낮추기
  4. 그래도 안 되면 CPU offload
  5. FP8은 지원 환경에서 가장 강력하지만, 스택 호환성부터 확인

이 순서대로 적용하면, 같은 GPU에서도 “돌아가던 프롬프트만 겨우 돌아가는” 상태에서 벗어나, 해상도·ControlNet·업스케일을 더 예측 가능하게 운영할 수 있습니다.