Published on

Stable Diffusion VRAM OOM 없이 2배 빠르게

Authors

서로 엮여 있는 두 문제부터 정리하겠습니다. Stable Diffusion에서 CUDA out of memory 가 터지는 이유는 단순히 모델이 커서가 아니라, 해상도·배치·어텐션 구현·VAE·업스케일 파이프라인이 동시에 VRAM 피크를 만들기 때문입니다. 반대로 속도는 커널 선택(Flash/XFormers/SDPA), 정밀도(fp16/bf16), 스케줄러, VAE 디코드 비용, 그리고 불필요한 그래프/캐시가 좌우합니다.

이 글의 목표는 “품질을 크게 해치지 않으면서”

  • VRAM 피크를 낮춰 OOM을 방지하고
  • 같은 GPU에서 초당 스텝 처리량을 높여
  • 결과적으로 체감 속도를 2배 수준까지 끌어올리는

실전 조합을 만드는 것입니다.

OOM이 나는 지점부터 분해하기

Stable Diffusion의 VRAM 사용은 대략 다음 항목의 합으로 이해하면 튜닝이 쉬워집니다.

  1. UNet(확산 본체): 어텐션과 중간 feature map이 VRAM을 가장 많이 씁니다.
  2. 텍스트 인코더(CLIP/T5): SDXL은 텍스트 인코더가 더 무겁고, 프롬프트 길이에 영향받습니다.
  3. VAE 디코더/인코더: 고해상도에서 디코드 비용이 크게 늘고, 메모리 피크도 만듭니다.
  4. 해상도와 배치: latent 크기는 width/8 * height/8 에 비례합니다. 해상도를 조금만 올려도 기하급수로 피크가 튑니다.
  5. 어텐션 구현: 같은 모델이라도 xformers / sdpa / FlashAttention 계열에 따라 VRAM과 속도가 크게 달라집니다.

핵심은 “피크를 만드는 구간을 줄여서 OOM을 막고, 동시에 빠른 커널을 선택해 속도를 올리는 것”입니다.

2배 속도를 만드는 우선순위 체크리스트

체감 2배는 한 방 옵션 하나로 나오기보다, 아래 조합이 누적되어 만들어집니다.

1) fp16 또는 bf16 강제 (기본 중의 기본)

  • NVIDIA 소비자 GPU는 보통 fp16이 가장 무난합니다.
  • Ampere 이후 일부 환경에서는 bf16이 안정적인 경우도 있습니다.

diffusers 예시:

import torch
from diffusers import StableDiffusionPipeline

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

정밀도를 낮추면 VRAM과 대역폭 부담이 줄어 속도도 같이 오릅니다.

2) 어텐션 커널을 바꿔라: xformers 또는 SDPA

속도와 VRAM 모두에 가장 큰 영향을 주는 구간이 UNet의 어텐션입니다.

  • PyTorch 2.x라면 SDPA(Scaled Dot Product Attention) 를 우선 고려
  • 환경이 맞으면 xformers 도 여전히 강력

diffusers에서 SDPA 강제:

import torch

torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cuda.enable_math_sdp(False)

diffusers에서 xformers:

pipe.enable_xformers_memory_efficient_attention()

주의: xformers는 버전 조합에 따라 설치/호환 이슈가 잦습니다. 이럴 때는 SDPA가 운영 관점에서 더 편합니다.

3) VAE를 가볍게: tilingslicing

고해상도에서 OOM이 나는 대표 원인이 VAE 디코드 피크입니다. VAE는 속도에도 영향을 주는데, “한 번에 큰 텐서를 디코드”하는 방식이 VRAM 피크를 만들기 때문입니다.

diffusers:

pipe.enable_vae_tiling()
pipe.enable_vae_slicing()
  • tiling: 타일 단위로 디코드해 VRAM 피크를 낮춤
  • slicing: 채널/배치 축을 잘라 처리해 피크를 낮춤

속도는 약간 손해일 수 있지만, OOM을 막아 “재시도 비용”을 없애면 전체 처리량이 오히려 좋아지는 경우가 많습니다.

4) 해상도 전략: 업스케일은 2단계로

1024x1024 를 한 번에 뽑으려다 OOM이 나는 경우가 많습니다. 실무적으로는 다음이 가장 안정적입니다.

  1. 1차 생성: 512 또는 768 에서 구도/프롬프트를 먼저 확정
  2. 2차: hires fix 또는 latent 업스케일 + 디노이즈 낮게

이 방식은 VRAM 피크가 훨씬 낮고, 실패율이 줄어 “총 소요 시간”이 크게 단축됩니다.

5) 배치 대신 시드 반복으로 처리량 최적화

배치(batch_size)를 올리면 VRAM이 급증합니다. 대신 아래를 권장합니다.

  • 배치는 1 유지
  • 여러 장이 필요하면 루프를 돌며 생성

diffusers:

images = []
for i in range(8):
    g = torch.Generator("cuda").manual_seed(1000 + i)
    img = pipe(
        "a photo of a cat",
        num_inference_steps=25,
        generator=g,
    ).images[0]
    images.append(img)

배치를 올려 생기는 OOM/스왑/드라이버 리셋 같은 리스크가 줄어 운영이 편해집니다.

WebUI(AUTOMATIC1111)에서 OOM 없이 빠르게: 추천 조합

WebUI는 실행 옵션과 체크박스 조합이 성능을 좌우합니다. 아래는 “속도 우선 + OOM 방지” 기준의 현실적인 조합입니다.

실행 옵션(예시)

  • --xformers 또는 PyTorch 2.x면 SDPA 기반으로 두고 xformers를 빼도 됨
  • --medvram 또는 정말 빡빡하면 --lowvram
  • --no-half-vae 는 상황에 따라(일부 VAE에서 fp16 깨짐 방지)

주의: --lowvram 은 확실히 OOM을 줄이지만 속도 손해가 큽니다. 가능한 --medvram 선에서 해결하고, 해상도/hires fix 전략으로 피크를 관리하는 편이 낫습니다.

UI 설정

  • VAE 관련 최적화(가능하다면 타일/슬라이스 계열 옵션)
  • Hires fix는 업스케일 배수와 디노이즈를 보수적으로
    • 예: 업스케일 1.5 ~ 2.0, denoise 0.25 ~ 0.45

ComfyUI에서 OOM 방지와 속도: 그래프 설계가 성능이다

ComfyUI는 노드 그래프가 곧 메모리 라이프사이클입니다.

  • VAE Decode를 마지막으로 미루기: 중간 단계에서 디코드하지 말고 latent로 최대한 유지
  • Preview 노드 최소화: 미리보기 이미지 생성이 의외로 VRAM 피크를 만들 수 있음
  • 업스케일은 latent 업스케일 노드를 우선 고려(픽셀 업스케일보다 메모리 효율이 좋은 경우가 많음)

이건 서버 운영에서 OOMKilled를 추적하듯, “어느 단계에서 피크가 생기는지”를 관찰하고 그래프를 재배치하는 접근과 유사합니다. OOM이 났다면 단순히 VRAM을 늘리기보다 피크 구간을 쪼개는 게 정답인 경우가 많습니다. 이 관점은 인프라 장애 추적에도 그대로 적용됩니다: K8s CrashLoopBackOff와 OOMKilled 원인 추적

diffusers에서 “OOM 없이 빠르게” 템플릿 코드

아래 코드는 SD 1.5 계열을 예로 들었지만, SDXL도 동일한 원리로 적용됩니다.

import torch
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler

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

torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cuda.enable_math_sdp(False)

pipe = StableDiffusionPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
)

# 스케줄러는 속도/품질 균형에서 DPM++ 계열이 무난
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)

pipe = pipe.to("cuda")

# VRAM 피크 완화
pipe.enable_vae_tiling()
pipe.enable_vae_slicing()

# VRAM이 정말 빡빡할 때만(속도 손해)
# pipe.enable_model_cpu_offload()

prompt = "highly detailed photo, 35mm, natural light"

with torch.inference_mode():
    image = pipe(
        prompt,
        num_inference_steps=25,
        guidance_scale=6.5,
        height=512,
        width=512,
    ).images[0]

image.save("out.png")

여기서 2배 체감이 나는 지점은 보통

  • steps를 40 에서 20 ~ 28 로 줄이되(스케줄러를 DPM 계열로)
  • 어텐션 커널을 빠른 것으로 바꾸고(SDPA/Flash 또는 xformers)
  • VAE 피크를 타일/슬라이스로 눌러 OOM 재시도를 없애는

조합에서 나옵니다.

스텝 수를 줄여도 품질을 유지하는 방법

속도 = 대체로 스텝 수에 비례합니다. 스텝을 줄이면 품질이 흔들릴 수 있으니 다음 순서로 보정합니다.

  1. 스케줄러를 DPM++ 계열로 변경
  2. guidance_scale 을 약간 낮춰 과도한 노이즈/뭉개짐을 줄임
  3. 고해상도는 2단계(hires fix)로 디테일 복원

특히 SDXL은 기본 해상도가 높아 스텝을 무작정 늘리면 VRAM과 시간이 같이 폭증합니다. “스텝을 늘리기”보다 “2단계 생성”이 더 경제적입니다.

자주 터지는 OOM 패턴과 처방

패턴 1: 특정 해상도에서만 OOM

  • 처방: height/width 를 64의 배수로 맞추고, 1차 생성 해상도를 낮춘 뒤 2차 업스케일

패턴 2: Hires fix에서만 OOM

  • 처방: 업스케일 배수 낮추기, denoise 낮추기, 업스케일러를 가벼운 것으로 변경
  • VAE 타일/슬라이스 활성화

패턴 3: 여러 장 연속 생성하면 점점 느려지다 OOM

  • 처방: 캐시/그래프가 쌓이거나, 미리보기/후처리에서 텐서가 해제되지 않는 경우가 있습니다.
  • diffusers라면 torch.inference_mode() 사용, 불필요한 텐서 참조 제거

이 문제는 DB에서 커넥션 누수처럼 “조금씩 쌓이다 터지는” 형태라 관찰 방법이 유사합니다. 누수 탐지 관점은 아래 글도 참고할 만합니다: Spring Boot HikariCP 커넥션 누수 감지·해결 가이드

VRAM이 적을수록 효과가 큰 옵션들(요약)

  • fp16 또는 bf16
  • SDPA/Flash 또는 xformers 어텐션
  • VAE tiling/slicing
  • 배치 1 고정, 반복 생성
  • 2단계 생성(저해상도 생성 후 업스케일)
  • 정말 마지막 수단으로 CPU offload(속도 손해 감수)

벤치마크할 때 주의할 점

속도 튜닝은 “한 번 빨라졌다”가 아니라, 재현 가능한 조건에서 비교해야 합니다.

  • 동일 프롬프트/시드/스텝/해상도/스케줄러로 비교
  • 첫 실행은 모델 로딩/커널 컴파일로 느릴 수 있으니 워밍업 후 측정
  • OOM 재시도까지 포함한 총 처리시간을 보라(실사용 체감은 여기서 갈림)

정리하면, Stable Diffusion을 OOM 없이 2배 빠르게 만드는 가장 현실적인 루트는

  1. fp16, 2) 빠른 어텐션(SDPA/Flash 또는 xformers), 3) VAE 타일/슬라이스, 4) 2단계 생성 전략

의 조합입니다. VRAM을 무작정 늘리는 대신 피크를 낮추고, 스텝과 커널을 최적화하면 “안정성”과 “처리량”을 같이 얻을 수 있습니다.