- Published on
Stable Diffusion VRAM 부족 OOM 해결 7단계
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버든 로컬 PC든 Stable Diffusion을 돌리다 보면 가장 자주 마주치는 오류가 CUDA out of memory(OOM)입니다. 모델이 커지고(예: SDXL), 해상도가 커지고, 배치가 늘고, ControlNet/LoRA/업스케일러까지 얹히면 VRAM은 순식간에 바닥납니다.
이 글은 “무작정 옵션을 끄는” 방식이 아니라, VRAM을 누가 얼마나 쓰는지를 기준으로 문제를 단계적으로 줄여 나가는 7단계 체크리스트입니다. WebUI(AUTOMATIC1111), ComfyUI, diffusers 기반 파이프라인에 모두 적용할 수 있도록 범용적으로 설명합니다.
참고: 성능 튜닝은 항상 트레이드오프입니다. 품질(디테일), 속도(초당 스텝), 안정성(재현성) 중 무엇을 우선할지 먼저 정해두면 시행착오가 줄어듭니다.
OOM이 나는 대표 시나리오(원인 지도)
OOM은 단순히 “모델이 커서”만 발생하지 않습니다. 보통 아래 조합으로 터집니다.
- 해상도가 커짐: latent 텐서가 커져서 U-Net의 중간 activation 메모리가 폭증
- 배치(
batch size) 또는 배치 카운트가 증가 - Sampler / CFG / step 증가로 중간 버퍼가 늘거나 오래 유지
- ControlNet / T2I-Adapter / IP-Adapter 등 추가 네트워크가 U-Net 경로에 붙음
- Hires.fix / 업스케일로 2단계 생성(저해상도 + 고해상도) 시 고해상도 단계에서 폭발
- VAE가 FP32로 고정되거나, 디코딩 타이밍이 잘못되어 피크 VRAM 상승
- PyTorch 메모리 파편화(reserved는 큰데 allocated는 작은 상태)로 “남아 보이는데도” OOM
이제부터는 효과가 큰 순서로 7단계를 진행합니다.
1단계: 현재 VRAM 사용량과 피크를 먼저 계측하기
최적화는 측정 없이는 감으로 하게 됩니다. 먼저 “실제로 VRAM이 어디서 피크를 찍는지”를 확인하세요.
nvidia-smi로 실시간 확인
watch -n 0.5 nvidia-smi
MiB단위 사용량이 스텝 진행 중 어디서 튀는지 관찰합니다.- 다른 프로세스(게임, 브라우저, 다른 파이썬)가 VRAM을 잡고 있으면 먼저 정리합니다.
PyTorch에서 메모리 요약(스크립트/노트북용)
import torch
def vram_report(tag=""):
torch.cuda.synchronize()
alloc = torch.cuda.memory_allocated() / 1024**2
reserv = torch.cuda.memory_reserved() / 1024**2
peak = torch.cuda.max_memory_allocated() / 1024**2
print(f"[{tag}] allocated={alloc:.0f}MiB reserved={reserv:.0f}MiB peak={peak:.0f}MiB")
# 사용 예
vram_report("before")
# ... inference ...
vram_report("after")
reserved가 비정상적으로 큰데allocated가 낮다면 파편화 가능성이 큽니다(6단계에서 다룹니다).
2단계: 해상도/배치/HR 옵션부터 줄여서 피크 VRAM을 즉시 낮추기
OOM 해결에서 가장 즉효는 해상도와 배치입니다. 이유는 간단합니다. U-Net의 activation 메모리는 대략적으로 텐서 크기에 비례하고, 텐서 크기는 해상도에 비례합니다.
우선순위 체크
batch size를1로batch count를 줄이기(여러 장은 반복 실행으로 대체)- 해상도를 모델 권장 범위로(예: SD 1.5는
512x512근처, SDXL은1024x1024근처) - Hires.fix를 끄거나, 업스케일 배율/2차 해상도를 낮추기
WebUI에서 자주 터지는 조합
1024x1024+ Hires.fix2x+ ControlNet 다중 사용은 12GB VRAM에서도 쉽게 OOM이 납니다.- “한 번에 여러 장”은 VRAM 절약에 불리합니다. 생성 시간을 조금 더 쓰더라도 1장씩 반복이 안전합니다.
3단계: 정밀도(precision)와 메모리 효율 옵션 적용(FP16/BF16)
VRAM을 가장 많이 먹는 축은 **가중치(weight)**와 activation입니다. FP32 대신 FP16/BF16을 쓰면 대개 체감적으로 VRAM이 크게 줄고 속도도 빨라집니다(카드/드라이버/커널에 따라 예외는 존재).
diffusers 파이프라인 예시(FP16)
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
)
pipe = pipe.to("cuda")
image = pipe(
"a photo of a red car",
num_inference_steps=25,
guidance_scale=7.0,
).images[0]
BF16이 유리한 경우
- 일부 GPU(특히 Ampere 이후)에서 BF16이 안정적일 때가 있습니다.
- 단, 모든 구성에서 BF16이 항상 더 낫지는 않으니 OOM과 NaN 여부를 기준으로 선택합니다.
4단계: Attention 최적화(XFormers/SDPA)로 VRAM 피크를 낮추기
OOM의 “피크”는 attention에서 크게 발생합니다. 특히 고해상도에서 self-attention의 비용이 커지기 때문에, 메모리 효율 attention을 켜는 것만으로도 한 단계 큰 해상도가 열리기도 합니다.
diffusers에서 SDPA 사용(권장 경로)
import torch
from diffusers import StableDiffusionXLPipeline
pipe = StableDiffusionXLPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
torch_dtype=torch.float16,
).to("cuda")
# PyTorch scaled dot-product attention 사용
pipe.enable_attention_slicing() # 추가로 메모리 절약(느려질 수 있음)
img = pipe("cinematic portrait", num_inference_steps=30).images[0]
WebUI/ComfyUI에서의 포인트
- 환경에 따라
xformers가 더 잘 먹히거나, PyTorch의 SDPA가 더 안정적인 경우가 있습니다. - “속도는 조금 느려져도 OOM만 피하자”면
attention slicing같은 옵션이 큰 도움이 됩니다.
5단계: VAE/텍스트 인코더/ControlNet을 분리 로딩하거나 오프로딩하기
추가 모듈이 많을수록 VRAM은 누적됩니다. 특히 SDXL은 텍스트 인코더(2개)와 큰 U-Net 때문에 기본 점유가 높습니다.
diffusers CPU 오프로딩
GPU VRAM이 부족하면 CPU로 일부를 내리는 것이 가장 확실합니다(대신 속도는 느려짐).
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
)
# GPU 메모리를 아끼는 대신 CPU/RAM을 사용
pipe.enable_model_cpu_offload()
image = pipe("a watercolor landscape", num_inference_steps=25).images[0]
실전 팁
- ControlNet을 여러 개 쓰면 VRAM이 빠르게 증가합니다. “정말 필요한 ControlNet만” 남기고, 해상도 높은 단계(예: Hires 단계)에서는 ControlNet을 끄는 구성도 고려하세요.
- 업스케일러를 바꿔도 VRAM 피크가 달라집니다. 타일 기반 업스케일(타일 크기 조절 가능)은 고해상도에서 특히 유리합니다.
6단계: PyTorch 메모리 파편화/캐시 문제 해결(환경 변수, 재시작, 설정)
nvidia-smi 상 여유가 있어 보이는데도 OOM이 나는 경우가 있습니다. 이때는 파편화 또는 캐시 정책 문제가 흔합니다.
가장 쉬운 해결: 프로세스 재시작
- WebUI/ComfyUI를 오래 켜두고 여러 모델을 갈아 끼우면 VRAM이 파편화됩니다.
- OOM이 반복되면 일단 프로세스를 완전히 종료 후 재실행이 가장 확실합니다.
PYTORCH_CUDA_ALLOC_CONF로 파편화 완화
아래 설정은 “큰 덩어리로 잡았다가 쪼개 쓰는” 전략을 조절해 파편화를 줄이는 데 도움될 수 있습니다.
export PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb:128"
python your_script.py
- 값은 환경에 따라 달라서
64,128,256등을 바꿔가며 실험합니다. - 이 설정은 만능은 아니지만, “reserved는 큰데 allocated가 낮고 OOM”인 케이스에서 체감이 나올 때가 있습니다.
캐시 비우기(스크립트용)
import torch
import gc
gc.collect()
torch.cuda.empty_cache()
- 주의:
empty_cache()는 “사용 중인 텐서”를 줄이지 않습니다. 다만 캐시 반환으로 다음 할당이 쉬워질 수 있습니다.
7단계: 최후의 수단—타일링, 더 작은 모델, 또는 분산/양자화로 구조적으로 해결
위 1~6단계로도 안 되면, 이제는 “워크로드 자체를 쪼개거나” “모델 자체를 줄이는” 구조적 접근이 필요합니다.
타일링(고해상도 이미지 생성/업스케일)
- 타일 기반 생성/업스케일은 한 번에 처리하는 텐서 크기를 줄여 피크 VRAM을 제한합니다.
- 타일 크기, 오버랩(경계 블렌딩)을 조절해 이음새를 줄입니다.
더 작은 체크포인트/경량화
- SDXL이 버거우면 SD 1.5 기반(또는 경량화된 turbo/LCM 계열)로 목표를 재정의하는 것도 현실적인 선택입니다.
- LoRA를 많이 얹는 경우, LoRA 수를 줄이거나 가중치(강도)를 조절해 불필요한 조합을 제거합니다.
8bit/4bit 양자화(가능한 환경에서)
- 일부 로더/백엔드에서는 텍스트 인코더나 U-Net 일부를 8bit로 내려 VRAM을 크게 줄일 수 있습니다.
- 다만 품질/호환성/속도에 영향이 있고, 구성 복잡도가 올라가므로 팀/프로덕션 환경에서는 재현성 확보가 중요합니다.
자주 묻는 질문(실전에서 많이 헷갈리는 지점)
Q1. VRAM 12GB인데 SDXL이 자꾸 OOM입니다
- SDXL은 기본 점유가 높습니다.
1024x1024에 ControlNet/업스케일을 얹으면 쉽게 터집니다. - 2단계(해상도/HR)와 4단계(attention 최적화), 5단계(CPU 오프로딩)를 조합하는 것이 현실적입니다.
Q2. reserved가 엄청 큰데 왜 OOM이 나죠
- 파편화로 인해 “연속된 큰 블록”을 못 잡는 상황일 수 있습니다.
- 6단계의 재시작 +
PYTORCH_CUDA_ALLOC_CONF튜닝을 우선 시도하세요.
Q3. 속도를 조금 희생해도 안정적으로 돌리고 싶습니다
attention slicing, CPU 오프로딩, 타일링은 속도 비용을 내고 안정성을 얻는 대표 옵션입니다.
체크리스트 요약(7단계 한 장 정리)
nvidia-smi와 PyTorch 리포트로 피크 구간을 측정batch size=1, 해상도/HR/업스케일을 먼저 줄이기- FP16/BF16로 전환해 가중치/activation 메모리 절감
- SDPA/XFormers 및
attention slicing로 attention 피크 감소 - VAE/텍스트 인코더/ControlNet 관리, 필요 시 CPU 오프로딩
- 파편화 대응: 재시작,
PYTORCH_CUDA_ALLOC_CONF적용 - 타일링/경량 모델/양자화로 구조적으로 해결
마무리: OOM은 “옵션 하나”가 아니라 “피크 관리” 문제다
Stable Diffusion OOM은 대부분 피크 VRAM을 어디서 만들고 있는지만 잡히면 해결 방향이 명확해집니다. 개인적으로는 2단계(해상도/배치)와 4단계(attention 최적화)만 잘 적용해도 체감이 가장 컸고, 그 다음이 5단계(오프로딩)였습니다.
튜닝을 할 때는 한 번에 여러 설정을 바꾸지 말고, 한 가지씩 변경 후 peak를 기록해 “내 환경에서의 정답”을 찾는 편이 빠릅니다. 이런 접근은 벡터DB 인덱스 튜닝이나 시스템 성능 튜닝과 비슷한데, 관심 있다면 RAG용 Qdrant HNSW 튜닝 실전 가이드나 Milvus IVF_PQ 튜닝으로 recall 올리고 지연 줄이기처럼 “측정하고 병목을 좁혀가는” 방식이 큰 도움이 됩니다.
원하시면 사용 중인 환경(예: GPU 모델, VRAM 용량, WebUI 또는 ComfyUI 여부, 목표 해상도, ControlNet/LoRA 사용 개수)을 기준으로 가장 효과 좋은 조합을 추천하는 형태로도 정리해 드릴게요.