- Published on
Stable Diffusion OOM - xFormers·SDPA로 VRAM 최적화
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Stable Diffusion을 돌리다 보면 가장 흔하게 마주치는 벽이 CUDA OOM(Out Of Memory)입니다. 특히 6GB~12GB VRAM 환경에서 고해상도, 배치 증가, ControlNet, 여러 LoRA를 얹는 순간 갑자기 터집니다. 이 글은 “왜 OOM이 나는지”를 구조적으로 이해하고, 실전에서 가장 효과가 큰 두 축인 xFormers와 PyTorch의 SDPA(Scaled Dot-Product Attention)로 VRAM을 줄이는 방법을 정리합니다.
아래 내용은 AUTOMATIC1111(WebUI), ComfyUI, 그리고 diffusers 기반 파이프라인 모두에 적용 가능한 개념이며, 구현 포인트만 약간 다릅니다.
OOM이 터지는 진짜 이유: VRAM은 어디서 새나
Stable Diffusion의 VRAM은 대략 다음 항목에서 크게 소비됩니다.
- UNet의 중간 활성값(activation)
- 해상도가 커질수록 attention의 토큰 수가 늘고, 중간 텐서가 기하급수로 커집니다.
- Attention 연산의 메모리 패턴
- 전통적인 attention은
QK^T를 만들고 softmax 후V에 곱하는 과정에서 큰 임시 텐서를 생성합니다.
- 전통적인 attention은
- VAE 디코딩/인코딩
- 고해상도에서 VAE 단계도 은근히 VRAM을 잡아먹습니다.
- 추가 모듈(ControlNet, T2I Adapter, IP-Adapter 등)
- UNet과 유사한 크기의 네트워크가 추가로 붙으니 VRAM이 훅 늘어납니다.
- 배치/스텝/샘플러 설정
- 배치는 즉시 VRAM을 올리고, 스텝은 주로 시간만 늘리지만 일부 캐시/버퍼가 영향을 줄 수 있습니다.
결론적으로 OOM을 줄이려면, 가장 큰 덩어리인 attention의 메모리 사용량을 줄이거나, 중간 텐서를 덜 들고 있도록 계산 방식을 바꾸는 것이 핵심입니다. 여기서 xFormers와 SDPA가 등장합니다.
xFormers vs SDPA: 뭐가 다르고 언제 쓰나
xFormers (memory efficient attention)
- Meta에서 만든 attention 최적화 커널 모음입니다.
- 일반적으로 VRAM 절감 효과가 크고, 특히 구형 PyTorch 조합에서도 잘 쓰였습니다.
- 단점은 환경에 따라 설치가 까다롭고, CUDA 및 PyTorch 버전 매트릭스가 맞지 않으면 오류가 납니다.
SDPA (PyTorch Scaled Dot-Product Attention)
- PyTorch 2.x에서 기본 제공하는 attention 최적화 경로입니다.
- 내부적으로 Flash Attention 계열 또는 메모리 효율 커널을 선택해줍니다.
- 장점은 “PyTorch 공식”이라 유지보수가 쉽고, 설치가 단순합니다.
- 단점은 GPU 아키텍처, 드라이버, dtype에 따라 최적 경로가 안 타면 효과가 제한될 수 있습니다.
실전 권장 순서는 보통 이렇습니다.
- PyTorch 2.x 환경이면 SDPA 우선
- SDPA로도 부족하거나 특정 UI/플러그인에서 xFormers가 더 잘 먹으면 xFormers 병행/대체
(WebUI) AUTOMATIC1111에서 xFormers 적용
WebUI는 실행 옵션으로 적용하는 패턴이 일반적입니다.
1) 실행 옵션에 xFormers 켜기
- Windows라면
webui-user.bat의COMMANDLINE_ARGS에 추가합니다.
set COMMANDLINE_ARGS=--xformers
- Linux라면 실행 스크립트에 플래그를 추가합니다.
./webui.sh --xformers
2) 설치가 꼬일 때 체크 포인트
- PyTorch 버전과 CUDA 버전이 엇갈리면 xFormers import에서 터집니다.
- 이때는 “무작정 최신”이 아니라, WebUI가 권장하는 조합을 따르는 편이 안정적입니다.
특히 다음 유형의 오류가 잦습니다.
ModuleNotFoundError: No module named 'xformers'RuntimeError: CUDA error: invalid device functionundefined symbol류의 바이너리 호환성 문제
이런 문제는 대부분 “xFormers wheel이 내 CUDA 아키텍처와 안 맞음”에서 시작합니다. 해결은 결국 PyTorch와 CUDA를 맞추고, 그 조합에 맞는 xFormers를 설치하는 것입니다.
(WebUI) SDPA 적용: xFormers 없이도 VRAM 절감
WebUI는 내부적으로 PyTorch attention 경로를 타는데, PyTorch 2.x라면 SDPA 최적화가 작동할 수 있습니다. 다만 WebUI 버전과 설정에 따라 다르게 동작하므로 다음을 확인합니다.
- PyTorch 버전 확인
import torch
print(torch.__version__)
print(torch.cuda.is_available())
- SDPA 강제/검증(개념 예시)
import torch
from torch.backends.cuda import sdp_kernel
print("Flash:", torch.backends.cuda.flash_sdp_enabled())
print("MemEff:", torch.backends.cuda.mem_efficient_sdp_enabled())
print("Math:", torch.backends.cuda.math_sdp_enabled())
# 특정 커널만 허용하는 식의 제어(환경에 따라 필요)
with sdp_kernel(enable_flash=True, enable_mem_efficient=True, enable_math=False):
pass
WebUI에서 SDPA가 제대로 타는지의 체감 신호는 보통 다음입니다.
- 같은 설정에서 VRAM 피크가 낮아짐
- 같은 VRAM에서 해상도나 ControlNet 조합을 더 올릴 수 있음
(diffusers) SDPA로 파이프라인 VRAM 줄이기
diffusers 기반 코드에서는 설정이 명확합니다. 핵심은 attention 구현체를 SDPA로 바꾸고, 추가로 VAE/UNet의 메모리 최적화를 곁들이는 것입니다.
import torch
from diffusers import StableDiffusionPipeline
model_id = "runwayml/stable-diffusion-v1-5"
dtype = torch.float16
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=dtype,
)
pipe = pipe.to("cuda")
# SDPA 사용(버전에 따라 API 명칭이 다를 수 있음)
pipe.set_attention_processor("sdpa")
# 메모리 절감 옵션들
pipe.enable_attention_slicing() # 속도는 다소 느려질 수 있음
pipe.enable_vae_slicing()
prompt = "a photo of a futuristic city at night, ultra detailed"
image = pipe(prompt, num_inference_steps=25).images[0]
image.save("out.png")
추가로 VRAM이 빠듯하면 다음 조합이 매우 강력합니다.
- CPU 오프로딩(속도 희생)
pipe.enable_model_cpu_offload()
- xFormers 적용(설치되어 있을 때)
pipe.enable_xformers_memory_efficient_attention()
SDPA와 xFormers는 환경에 따라 “둘 다 켜면 더 좋아지는” 경우도 있고, 한쪽이 다른 쪽을 대체하는 경우도 있습니다. 따라서 최종적으로는 둘 중 하나를 기준으로 A/B 테스트하는 게 가장 확실합니다.
(ComfyUI)에서의 관점: 노드보다 “attention 백엔드”가 먼저
ComfyUI는 워크플로가 복잡해질수록 OOM이 자주 나는데, 원인은 대개 다음입니다.
- ControlNet을 여러 개 연결
- 고해상도 2-pass(hires fix 유사) 구성
- KSampler에서 latent 크기 과도
이때도 첫 번째로 확인할 것은 “attention 최적화가 켜져 있나”입니다. ComfyUI는 빌드/설치 상태에 따라 xFormers 또는 PyTorch 최적 경로를 사용합니다. 결론적으로는 다음 순서가 실전적입니다.
- PyTorch 2.x 기반으로 맞추고 SDPA 경로를 확보
- 필요 시 xFormers를 추가
- 그래도 부족하면 해상도, ControlNet 수, 배치, 업스케일 전략을 조정
VRAM 절감 체감이 큰 설정 조합(우선순위)
OOM을 “근본적으로” 줄이는 데 보통 효과가 큰 순서입니다.
- Attention 최적화:
SDPA또는xFormers - 해상도/latent 크기 조정
- 예: 512 기반에서 시작해 768은 점진적으로
- 배치 1 고정
- VAE slicing / tiling
- ControlNet 개수 줄이기 또는 해상도 낮춰서 적용
- 오프로딩(CPU offload)
특히 “해상도”는 단순히 픽셀만 늘어나는 게 아니라 attention 토큰 수가 늘어나는 구조라, 512에서 1024로 올리는 순간 VRAM이 선형이 아니라 훨씬 가파르게 증가합니다.
OOM 트러블슈팅: 자주 나오는 케이스별 체크리스트
1) OOM이 간헐적으로만 난다
- 원인 후보
- 다른 프로세스가 GPU VRAM을 점유(브라우저, 게임, 다른 모델)
- PyTorch 메모리 단편화
- 대응
- 작업 전 GPU 점유 확인
nvidia-smi
- 파이프라인을 반복 실행한다면, 불필요한 텐서 참조를 끊고 캐시를 정리
import torch, gc
gc.collect()
torch.cuda.empty_cache()
2) xFormers 켰더니 오히려 느리거나 깨진다
- 일부 GPU나 드라이버 조합에서 커널 선택이 비최적일 수 있습니다.
- SDPA로 전환해 비교해보는 것이 가장 빠른 판단입니다.
3) SDPA가 전혀 효과가 없다
- 다음 조건에서 최적 커널이 비활성일 수 있습니다.
- dtype이
float32로 고정됨 - GPU 아키텍처가 Flash 계열에 불리함
- PyTorch 빌드 옵션/버전 이슈
- dtype이
- 대응
- 가능하면
float16또는bfloat16경로를 검토 - PyTorch 2.x 최신 마이너 버전으로 업데이트 후 재확인
- 가능하면
운영 관점 팁: “리소스 부족”은 AI만의 문제가 아니다
Stable Diffusion OOM 대응은 결국 “제한된 자원에서 피크 메모리를 낮추는” 문제입니다. 이 관점은 서버 운영에서도 동일하게 반복됩니다. 예를 들어 쿠버네티스에서 파드가 Pending으로 남는 이슈도 결국 리소스(노드, 스케줄링, 제한) 관점에서 구조적으로 접근해야 빠르게 풀립니다.
- 리소스 병목을 구조적으로 진단하는 접근은 이 글도 참고할 만합니다: EKS Pod Pending 0/XX nodes available 원인별 해결
또한 모델 추론을 API로 감싸 운영할 때는 지연과 콜드스타트가 비용과 직결됩니다. VRAM 최적화로 GPU 인스턴스를 낮추는 것과 함께, 서비스 레벨에서는 콜드스타트 최소화 전략도 같이 보면 좋습니다.
- 서버리스/컨테이너 콜드스타트 관점: GCP Cloud Run 503·Cold Start 지연 최소화 7가지
결론: OOM을 “설정 꼼수”가 아니라 백엔드 선택으로 해결하자
Stable Diffusion의 OOM은 단순히 “VRAM이 부족해서”가 아니라, attention의 메모리 패턴과 중간 텐서 피크가 원인인 경우가 대부분입니다. 그래서 가장 먼저 손댈 것은 해상도나 배치가 아니라 attention 백엔드입니다.
- PyTorch 2.x 기반이면
SDPA를 우선 확인 - 설치/호환이 맞는 환경이라면
xFormers도 강력한 선택지 - 그래도 부족하면 slicing, VAE 최적화, 오프로딩, 그리고 워크플로 자체(해상도, ControlNet 수)를 조정
이 순서로 접근하면 “무작정 옵션을 다 켜는” 시행착오를 줄이고, 같은 GPU에서 더 높은 해상도와 더 많은 조건부 제어를 안정적으로 처리할 수 있습니다.