- Published on
Stable Diffusion VRAM OOM 없애는 6가지 최적화
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Stable Diffusion을 로컬 GPU에서 돌리다 보면 가장 흔하게 만나는 에러가 CUDA out of memory(VRAM OOM)입니다. 특히 txt2img는 괜찮다가 img2img, Hi-Res fix, ControlNet, AnimateDiff 같은 기능을 켜는 순간 VRAM이 급격히 튀면서 터집니다.
이 글은 “왜 OOM이 나는지”를 감으로 때우지 않고, VRAM을 실제로 줄이는 6가지 최적화를 우선순위대로 정리합니다. WebUI(AUTOMATIC1111) 기준 팁도 포함하지만, 핵심은 PyTorch/Diffusers에서도 그대로 적용됩니다.
OOM을 유발하는 대표 원인(먼저 짚고 가기)
Stable Diffusion에서 VRAM을 크게 먹는 축은 대략 다음입니다.
- 해상도:
H x W가 커질수록 U-Net의 feature map이 기하급수적으로 커짐 - 배치 크기: 한 번에 여러 장 생성하면 그만큼 activation/latent가 증가
- 샘플러 steps: steps 자체가 메모리를 선형으로 늘리진 않지만, 일부 구현/옵션(예: 중간 결과 저장, 고급 옵션)과 결합되면 피크가 상승
- 부가 모델: ControlNet, LoRA 다중 적용, T2I-Adapter, IP-Adapter 등 추가 네트워크
- 정밀도/어텐션 구현: FP32, 기본 attention은 VRAM을 많이 사용
진단의 첫 단계는 “피크 메모리가 언제 치솟는지”를 확인하는 것입니다.
import torch
def report(tag: str):
if not torch.cuda.is_available():
print("CUDA not available")
return
torch.cuda.synchronize()
alloc = torch.cuda.memory_allocated() / 1024**2
reserved = torch.cuda.memory_reserved() / 1024**2
peak = torch.cuda.max_memory_allocated() / 1024**2
print(f"[{tag}] allocated={alloc:.1f}MB reserved={reserved:.1f}MB peak={peak:.1f}MB")
# 사용 예: 파이프라인 로드 전/후, generate 전/후에 report("...") 호출
이제부터는 “OOM을 실제로 없애는” 방법을 6개로 나눠 설명합니다.
1) 해상도, 배치, Hi-Res fix를 먼저 줄여라(가장 확실)
VRAM OOM은 대부분 입력 크기와 배치에서 결정됩니다. 가장 먼저 아래를 줄이면 즉시 효과가 납니다.
batch size를1로width/height를 낮추고, 필요하면 업스케일은 후처리로Hi-Res fix를 쓴다면- 1차 생성 해상도를 낮추고
denoising strength를 과하게 높이지 말고- 업스케일 배율을 무리하지 않기
Diffusers에서도 동일합니다.
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
# 배치 1, 해상도 512로 시작
image = pipe(
prompt="a cinematic portrait",
num_inference_steps=25,
guidance_scale=7.5,
height=512,
width=512,
).images[0]
실무 팁:
- VRAM이 8GB 이하라면 768 이상은 옵션을 줄이지 않으면 자주 터집니다.
ControlNet을 켠 상태에서 1024, Hi-Res fix까지 얹으면 12GB에서도 OOM이 날 수 있습니다.
2) FP16/BF16로 바꾸고, 불필요한 FP32 경로를 제거
가장 “가성비” 좋은 최적화는 반정밀도(half precision) 입니다.
- NVIDIA RTX 계열: 보통
fp16이 안정적 - Ampere 이상에서 BF16이 더 안정적인 경우도 있으나, 파이프라인/드라이버 조합에 따라 다름
Diffusers 예시:
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
safety_checker=None,
).to("cuda")
pipe.enable_attention_slicing() # 메모리 절약(속도는 일부 감소)
WebUI(AUTOMATIC1111)에서는 보통 실행 옵션에서 다음 계열을 확인합니다.
--precision full을 쓰고 있다면 중단(가능하면)--no-half를 쓰고 있다면 OOM 가능성이 커짐- VAE만 FP32로 강제하는 옵션이 켜져 있으면 VRAM이 올라갈 수 있음
주의:
- 일부 커스텀 VAE나 확장 기능은 half에서 NaN이 날 수 있습니다. 그 경우에만 예외적으로 해당 컴포넌트만 정밀도를 조정하세요.
3) xFormers 또는 SDPA로 어텐션 메모리를 줄이기
OOM의 주요 범인은 U-Net의 self-attention입니다. 여기서 메모리를 크게 줄이는 방법이 두 가지입니다.
xFormers메모리 효율 어텐션- PyTorch 2.x의
scaled_dot_product_attention(SDPA)
Diffusers에서 xFormers:
pipe.enable_xformers_memory_efficient_attention()
PyTorch SDPA(환경에 따라 자동 적용되기도 함):
import torch
# PyTorch 2.x에서 backend 힌트를 줄 수 있음(환경에 따라 다름)
torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cuda.enable_math_sdp(False)
체감 효과:
- 동일 해상도에서 “아슬아슬하게 터지던” 케이스가 통과하는 일이 많습니다.
- 대신 일부 조합에서 속도/호환성 이슈가 있을 수 있으니, 문제가 생기면 SDPA만 또는 xFormers만 남기는 식으로 단순화하세요.
4) VAE 최적화: 타일링, 경량 VAE, VAE를 CPU로 오프로딩
VAE는 샘플링 전체에서 가장 큰 비중은 아니지만, 디코딩 순간 피크 VRAM을 만들 수 있습니다. 특히 고해상도에서 그렇습니다.
VAE 타일링
Diffusers는 VAE 타일링을 지원합니다.
pipe.enable_vae_tiling() # 디코딩을 타일로 쪼개 VRAM 피크를 낮춤
VAE 스위칭
- 일부 VAE는 품질은 좋지만 메모리가 더 들 수 있습니다.
- VRAM이 빡빡하면 “기본 VAE”로 돌아가거나, 경량 VAE를 고려하세요.
CPU 오프로딩
VRAM이 정말 부족하면 일부를 CPU로 내리는 전략이 있습니다.
pipe.enable_model_cpu_offload() # accelerate 필요
오프로딩은 대개 속도를 희생합니다. 하지만 “생성이 아예 안 되는 상태”라면 가장 현실적인 타협입니다.
5) 타일 기반 생성(고해상도 필수 전략): Tiled Diffusion/Latent 타일링
1024 이상 고해상도에서 OOM이 반복된다면, 근본적으로 “한 번에 큰 텐서를 들고 있는 구조”를 바꿔야 합니다. 이때 가장 강력한 방법이 타일 기반 생성입니다.
- 이미지를 여러 타일로 쪼개서 생성하거나
- latent 공간에서 타일로 처리해 피크 VRAM을 제한
WebUI에서는 확장(예: 타일 기반 diffusion/업스케일 계열)을 통해 접근하는 경우가 많고, Diffusers에서도 비슷한 아이디어로 파이프라인을 구성할 수 있습니다.
타일링의 트레이드오프:
- 경계(seam) 문제를 없애려면 overlap, blending이 필요
- 속도는 느려질 수 있음
- 하지만 VRAM 상한을 “타일 크기”로 고정할 수 있어, OOM 방지에 매우 효과적
6) 메모리 누수처럼 보이는 상황 정리: 캐시, 그래프, 확장 기능 정돈
“같은 설정인데 어느 순간부터만 OOM”이면, 실제로는 아래 중 하나일 가능성이 큽니다.
- PyTorch CUDA 캐시가 예약(reserved) 상태로 남아 VRAM이 꽉 찬 것처럼 보임
- 중간 텐서를 어딘가에 참조로 붙잡고 있어 GC가 안 됨
- WebUI 확장 기능이 매 반복마다 텐서를 누적
Diffusers/PyTorch에서 최소한의 정리 루틴:
import gc
import torch
def cleanup_cuda():
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
# 여러 번 생성 루프를 돌린다면, 작업 단위 끝날 때 cleanup_cuda() 호출
또한 torch.no_grad() 또는 torch.inference_mode()를 보장하세요.
with torch.inference_mode():
image = pipe("a photo of a cat", num_inference_steps=20).images[0]
WebUI라면:
- 확장 기능을 한 번에 많이 켜지 말고, OOM이 나는 시점에 켠 확장을 하나씩 꺼서 범인을 좁히기
- ControlNet을 여러 개 동시 사용 시, 하나씩 줄여 VRAM 임계점을 찾기
OOM을 “진단 프로세스”로 접근하는 관점은 쿠버네티스에서 OOMKilled를 추적할 때와 유사합니다. 원인 분리와 재현이 핵심입니다. 같은 결로 참고할 만한 글로는 K8s CrashLoopBackOff - OOMKilled·Probe·Exit 137 진단이 있습니다.
추천 적용 순서(체크리스트)
아래 순서대로 적용하면 시행착오가 줄어듭니다.
batch size=1, 해상도512또는640으로 낮춰 “일단 성공” 만들기fp16적용(가능하면), 불필요한 FP32 옵션 제거- xFormers 또는 SDPA 적용
- VAE 타일링 적용, 필요 시 CPU 오프로딩
- 고해상도는 타일 기반 생성으로 전환
- 반복 생성/서빙이라면 캐시 정리 및 누수성 확장 제거
자주 묻는 질문
torch.cuda.empty_cache()만 하면 OOM이 해결되나요?
일시적으로는 도움이 되지만, 근본 해결은 아닙니다. 이미 필요한 피크 메모리가 VRAM을 초과한다면 캐시를 비워도 다시 터집니다. 캐시는 “예약(reserved)”을 줄여줄 뿐, 모델/텐서 자체 크기를 줄이진 않습니다.
VRAM이 12GB인데도 OOM이 납니다
가능합니다. 예를 들어 1024 해상도, Hi-Res fix, ControlNet 2개, 고 steps, 고정밀 옵션이 겹치면 12GB도 쉽게 넘습니다. 이 경우는 5번 타일링 전략이 가장 확실합니다.
마무리
Stable Diffusion VRAM OOM은 “GPU가 약해서”라기보다, 피크 메모리를 만드는 조합을 모르고 쌓아서 생기는 경우가 많습니다. 이 글의 6가지 최적화는 서로 중복되지 않게 다른 층(입력 크기, 정밀도, 어텐션, VAE, 타일링, 런타임 정리)을 공략합니다.
OOM이 나는 정확한 상황(사용 모델, VRAM 용량, 해상도, ControlNet/LoRA 개수, WebUI인지 Diffusers인지)을 알려주면, 위 6가지를 기준으로 “최소 변경으로 통과하는 설정”을 역으로 조합해 드릴 수 있습니다.