- Published on
Stable Diffusion VRAM OOM, xFormers·VAE로 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Stable Diffusion을 돌리다 보면 가장 흔하게 마주치는 에러가 CUDA out of memory(VRAM OOM)입니다. 특히 512x512에서는 잘 되다가 768x768 이상으로 올리거나, Hires.fix를 켜거나, 배치/스텝을 늘리는 순간 갑자기 터집니다. 문제는 “VRAM이 부족하니 더 큰 GPU를 사라”가 아니라, 어떤 연산이 VRAM을 폭발시키는지를 이해하고 xFormers·VAE·정밀도·오프로딩을 조합하면 같은 GPU로도 꽤 멀리 갈 수 있다는 점입니다.
이 글은 WebUI(AUTOMATIC1111)와 Diffusers(Python) 기준으로, OOM을 재현 가능하게 줄이는 실전 체크리스트를 제공합니다.
VRAM OOM이 터지는 지점: 어텐션과 VAE가 80%
Stable Diffusion 파이프라인에서 VRAM을 크게 먹는 축은 대략 두 가지입니다.
- U-Net의 Self/Cross Attention
- 해상도가 커질수록 토큰(공간 위치) 수가 증가하고, 어텐션은 내부적으로 큰 행렬을 만듭니다.
- 결과적으로
768x768이나1024x1024에서 VRAM이 기하급수적으로 튑니다.
- VAE 디코딩/인코딩
- 샘플링 중에는 주로 U-Net이 바쁘지만, 마지막에 VAE가 latent를 이미지로 풀어낼 때도 순간 피크가 생깁니다.
- 특히 “고화질 + 배치” 조합에서 VAE가 OOM 트리거가 되기도 합니다.
여기에 다음 요소들이 피크 VRAM을 올립니다.
- 배치 크기(
batch size)와 동시 생성 수 Hires.fix(2-pass) 및 업스케일 단계- 고정밀(
fp32) 사용 - ControlNet, LoRA 다중 적용, IP-Adapter 등 추가 모듈
먼저 확인: OOM을 유발하는 설정 5가지
아래 중 하나라도 해당되면, xFormers와 VAE 최적화로 체감 효과가 큽니다.
- 해상도
768x768이상 또는Hires.fix사용 batch size가2이상- ControlNet 1개 이상 사용
--no-half또는fp32강제(정밀도 높임)- VAE가 기본값(특히 구형/비최적 VAE)이고,
vae옵션을 따로 만지지 않음
핵심 1: xFormers(또는 SDPA)로 어텐션 메모리 줄이기
어텐션은 OOM의 주범이라, 여기부터 잡는 게 가장 효율적입니다.
WebUI(AUTOMATIC1111): xFormers 켜기
가장 흔한 방법은 실행 옵션에 --xformers를 추가하는 것입니다.
# 예시: webui-user.sh 또는 실행 커맨드에 추가
COMMANDLINE_ARGS="--xformers"
적용 후에는 생성 로그 또는 Settings에서 xFormers가 활성화되었는지 확인하세요.
- 장점: 고해상도에서 VRAM 사용량이 크게 줄어듭니다.
- 주의: 환경에 따라 설치/호환 이슈가 생길 수 있습니다(특히 특정 CUDA, PyTorch 조합).
Diffusers(Python): xFormers 또는 PyTorch SDPA 사용
Diffusers에서는 두 가지 축이 있습니다.
- xFormers 메모리 효율 어텐션
- PyTorch 2.x의 SDPA(
scaled_dot_product_attention)
둘 중 하나만 잘 잡혀도 효과가 큽니다.
import torch
from diffusers import StableDiffusionPipeline
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16,
).to("cuda")
# 1) xFormers 사용(설치되어 있을 때)
pipe.enable_xformers_memory_efficient_attention()
# 2) 추가 절감: attention slicing
pipe.enable_attention_slicing()
image = pipe(
prompt="a photo of a corgi in space",
num_inference_steps=30,
guidance_scale=7.5,
).images[0]
enable_attention_slicing()은 속도는 조금 희생하지만 피크 VRAM을 더 낮춥니다.
핵심 2: VAE를 ‘메모리 친화’ 조합으로 바꾸기
VAE는 마지막 디코딩에서 피크를 만들 수 있고, 일부 VAE는 품질/색감뿐 아니라 메모리 특성도 다릅니다.
WebUI: VAE 교체 및 설정
WebUI에서는 VAE를 별도 파일로 선택할 수 있습니다.
- Settings에서
VAE를 선택하거나 - 모델 폴더에 VAE를 두고 자동 매칭을 유도합니다.
실무적으로는 아래 전략이 자주 통합니다.
- VAE를 별도로 로딩해 모델과 분리(필요 시만 교체)
- 고해상도/배치에서 OOM이 나면 VAE를 더 가벼운 쪽으로 변경하거나, 아래 오프로딩 옵션과 함께 사용
Diffusers: VAE를 fp16으로, 필요 시 타일 디코딩
Diffusers에서는 VAE를 fp16으로 두는 게 기본이고, 고해상도에서 문제가 생기면 타일링/오프로딩을 고려합니다.
import torch
from diffusers import StableDiffusionPipeline, AutoencoderKL
model_id = "runwayml/stable-diffusion-v1-5"
vae_id = "stabilityai/sd-vae-ft-mse" # 예시
vae = AutoencoderKL.from_pretrained(vae_id, torch_dtype=torch.float16)
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
vae=vae,
torch_dtype=torch.float16,
).to("cuda")
pipe.enable_xformers_memory_efficient_attention()
pipe.enable_attention_slicing()
image = pipe("portrait photo, 85mm", num_inference_steps=25).images[0]
VAE가 OOM 트리거일 때: 타일 디코딩(개념)
VAE 디코딩은 한 번에 큰 텐서를 만들기 때문에, 타일로 쪼개서 디코딩하면 피크를 줄일 수 있습니다. 구현은 라이브러리/버전에 따라 다르지만, 아이디어는 “큰 이미지를 여러 조각으로 나눠 VAE를 돌린 뒤 합친다”입니다.
핵심 3: 정밀도·메모리 옵션(half, TF32, channels last)
WebUI: --medvram, --lowvram은 최후의 보루
--medvram은 속도 손해를 보면서도 성공 확률을 올립니다.--lowvram은 더 강하지만 체감 속도 저하가 큽니다.
다만 xFormers/SDPA, 배치 감소, 해상도 조정으로 해결이 되면 --lowvram까지 갈 필요는 없는 경우가 많습니다.
Diffusers: CPU 오프로딩과 메모리 최적화
Diffusers는 “VRAM이 모자라면 CPU로 일부를 내리는” 옵션이 강력합니다.
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
)
# GPU가 빡빡할 때: CPU 오프로딩
pipe.enable_model_cpu_offload()
# 또는 더 공격적으로(환경에 따라 성능/호환 차이)
# pipe.enable_sequential_cpu_offload()
pipe.enable_attention_slicing()
image = pipe("cinematic lighting", num_inference_steps=30).images[0]
- 장점: 작은 VRAM에서도 돌아가게 만들 수 있음
- 단점: PCIe 전송/CPU 연산으로 속도 저하
해상도·배치·Hires.fix: OOM을 부르는 조합을 분해하기
OOM을 줄이려면 “한 번에 처리하는 텐서 크기”를 줄여야 합니다.
1) 배치부터 줄이기
batch size는 VRAM에 거의 선형으로 영향을 줍니다.- 먼저
batch size=1로 고정하고 문제를 해결한 뒤, 여유가 있으면 늘리세요.
2) Hires.fix는 사실상 2번 생성이다
Hires.fix는 1차 생성 + 업스케일 + 2차 디테일링으로, 피크 구간이 여러 번 생깁니다.
- OOM이 나면 먼저 Hires.fix를 끄고 베이스 해상도에서 안정화
- 그 다음 업스케일 배율/denoise 강도를 낮춰 다시 시도
3) ControlNet/LoRA는 “조금씩”이 아니라 “피크를” 올린다
ControlNet은 추가 네트워크가 붙어 피크 VRAM이 올라갑니다.
- ControlNet을 여러 개 쓰면 OOM이 급격히 늘 수 있음
- LoRA도 여러 개를 쌓으면 메모리/속도에 영향
재현 가능한 트러블슈팅 순서(체크리스트)
아래 순서대로 적용하면, “원인 모르게 옵션만 늘리는” 상황을 피할 수 있습니다.
batch size=1, 해상도512x512로 baseline 확보fp16확인(WebUI에서 half 사용, Diffusers에서torch_dtype=torch.float16)- 어텐션 최적화 적용: WebUI
--xformers또는 Diffusersenable_xformers_memory_efficient_attention() - 그래도 OOM이면
enable_attention_slicing()추가 - 고해상도 필요 시: 해상도를 천천히 올리며 한계점 측정
- VAE 디코딩에서 터지면 VAE 교체 또는 타일 디코딩/오프로딩 검토
- 최후에 WebUI
--medvram또는 Diffusers CPU offload 사용
이 과정은 운영 환경에서 장애를 줄이는 접근과 유사합니다. 예를 들어 레이트 리밋에서 무작정 재시도만 늘리기보다 백오프 전략을 세우듯, OOM도 “피크를 만드는 지점”을 특정하고 단계적으로 완화해야 합니다. 관련해서는 OpenAI 429/Rate Limit 재시도·백오프 실전 가이드처럼 원인 기반 접근이 도움이 됩니다.
OOM인데 VRAM이 남아 보이는 경우: 캐시와 파편화
nvidia-smi로 보면 VRAM이 조금 남아 있는데도 OOM이 뜨는 경우가 있습니다. 이는 “연속된 큰 블록”이 필요하지만 파편화로 인해 할당이 실패하는 케이스가 섞여 있기 때문입니다.
Diffusers: PyTorch 캐시 정리(응급처치)
루프에서 대량 생성하거나, 여러 파이프라인을 번갈아 로딩할 때 도움이 됩니다.
import gc
import torch
def cleanup():
gc.collect()
torch.cuda.empty_cache()
# 생성 작업 사이에 호출
cleanup()
다만 empty_cache()는 만능 해결책이 아니라 “일시적 완화”에 가깝습니다. 근본적으로는 피크 VRAM을 낮추는 옵션(xFormers/슬라이싱/오프로딩/해상도 조절)이 우선입니다.
비슷한 맥락으로, 장시간 실행 프로세스에서 리소스가 누적되는 문제는 서버/런타임 전반에서 반복됩니다. Go의 누수 패턴을 정리한 Go 고루틴 누수 7가지 원인과 채널 종료 패턴도 “누적을 방치하면 결국 터진다”는 점에서 사고방식이 닮아 있습니다.
WebUI 추천 조합(현실적인 프리셋)
GPU가 8GB 전후라고 가정했을 때, 성공률이 높은 조합은 대체로 아래입니다.
--xformersbatch size=1- 베이스
512x512또는640x640부터 - 고해상도는 Hires.fix를 쓰되 업스케일 배율을 과격하게 올리지 않기
- ControlNet은 1개부터, 필요할 때만 추가
- OOM이 계속되면
--medvram을 마지막에 추가
결론: xFormers로 U-Net, VAE로 디코딩 피크를 잡아라
Stable Diffusion VRAM OOM은 대부분 “어텐션 피크”와 “VAE 디코딩 피크”에서 발생합니다. 따라서 해결의 우선순위도 명확합니다.
- 1순위: xFormers(또는 SDPA)로 어텐션 메모리 절감
- 2순위: attention slicing으로 피크를 더 낮추기
- 3순위: VAE 교체/타일링/오프로딩으로 디코딩 피크 제어
- 4순위: 해상도·배치·Hires.fix·ControlNet을 단계적으로 늘리며 한계 측정
이 순서대로 적용하면 “같은 GPU로 더 큰 해상도, 더 많은 옵션”을 안정적으로 소화할 수 있고, 무엇보다 설정이 꼬여도 원인-해결을 추적하기 쉬워집니다.