- Published on
Stable Diffusion VRAM OOM 해결 - xFormers·VAE 타일링
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버나 로컬 PC에서 Stable Diffusion을 돌리다 보면 가장 흔하게 마주치는 에러가 VRAM OOM(Out Of Memory)입니다. 특히 해상도를 조금만 올리거나 배치 크기를 키우는 순간 CUDA out of memory가 터지면서 생성이 중단됩니다. 문제는 “VRAM이 부족하니 해상도를 낮추세요” 같은 조언만으로는 운영 환경에서 원하는 품질을 확보하기 어렵다는 점입니다.
이 글에서는 VRAM이 어디에서 소모되는지(UNet, 어텐션, VAE, 텍스트 인코더)부터 짚고, 실전에서 효과가 큰 두 가지 축인 xFormers(메모리 효율 어텐션)와 VAE 타일링(디코딩/인코딩 타일 분할)을 중심으로 OOM을 안정적으로 줄이는 방법을 정리합니다.
운영 중 장애를 추적하는 방식은 결국 로그와 관찰이 핵심인데, 이런 접근은 systemd 서비스가 계속 재시작될 때 원인 추적법에서 다룬 “증상-원인-재현-완화” 흐름과도 비슷합니다.
VRAM OOM이 나는 구조: 어디서 메모리가 터지나
Stable Diffusion 파이프라인에서 VRAM을 크게 먹는 구간은 다음입니다.
- UNet 추론: 디퓨전 스텝마다 실행되며, 특히 어텐션이 메모리 피크를 만들기 쉽습니다.
- 어텐션(Attention) 연산: 토큰 수(텍스트 길이)와 latent 해상도에 따라 메모리 사용량이 커집니다.
- VAE 디코딩/인코딩: 최종 이미지로 변환하는 단계에서 VRAM 피크가 생길 수 있습니다. 고해상도에서 특히 두드러집니다.
- 배치 크기 및 동시성: 한 번에 여러 장 또는 여러 요청을 동시에 처리하면 선형에 가깝게 증가합니다.
OOM은 보통 “평균”이 아니라 **피크(peak)**에서 발생합니다. 따라서 해결 전략은 “전체를 조금씩 줄이기”가 아니라 **피크를 만드는 구간을 분리하거나(타일링), 더 메모리 효율적인 커널로 바꾸는 것(xFormers)**이 효과적입니다.
먼저 확인할 것: OOM 재현과 측정
해결 전에 현재 상황을 수치로 잡아두면, 옵션을 바꿨을 때 효과를 빠르게 검증할 수 있습니다.
nvidia-smi로 피크 관찰
아래처럼 1초 간격으로 VRAM 변화를 보면서 어떤 단계에서 급증하는지 확인합니다.
watch -n 1 nvidia-smi
PyTorch 메모리 통계(가능한 경우)
코드에 접근 가능하면 torch.cuda.max_memory_allocated() 같은 지표로 피크를 찍어두는 것도 좋습니다.
import torch
# ... inference 실행 후
print("max allocated:", torch.cuda.max_memory_allocated() / 1024**3, "GB")
print("max reserved:", torch.cuda.max_memory_reserved() / 1024**3, "GB")
참고로 reserved가 allocated보다 큰 것은 정상일 수 있습니다(캐싱/메모리 풀). 다만 OOM이 난다면 reserved가 이미 GPU 한계에 근접했을 가능성이 큽니다.
해결 1순위: xFormers로 어텐션 메모리 절감
xFormers는 메모리 효율적인 어텐션 구현(대표적으로 memory efficient attention)을 제공해, UNet의 피크 VRAM을 크게 줄여줍니다. 특히 고해상도나 긴 프롬프트, ControlNet 등 어텐션 부담이 큰 구성에서 체감이 큽니다.
AUTOMATIC1111(WebUI)에서 xFormers 적용
환경마다 다르지만, 일반적으로는 실행 옵션에 --xformers를 추가합니다.
# 예시: webui-user.sh 또는 실행 커맨드에 추가
./webui.sh --xformers
Windows라면 webui-user.bat의 COMMANDLINE_ARGS에 넣는 방식이 흔합니다.
set COMMANDLINE_ARGS=--xformers
주의점
- 드라이버, CUDA, PyTorch 조합에 따라 xFormers 설치/동작이 깨질 수 있습니다.
- xFormers가 활성화됐는지 로그에서 확인하세요. “xformers available” 류 메시지가 보이면 대체로 정상입니다.
diffusers(Python)에서 xFormers 적용
Hugging Face diffusers를 쓴다면 아래처럼 명시적으로 켤 수 있습니다.
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
# xFormers 메모리 효율 어텐션 활성화
pipe.enable_xformers_memory_efficient_attention()
기대 효과
- UNet 어텐션의 피크 VRAM이 줄어 OOM이 사라지거나, 같은 VRAM에서 더 높은 해상도/스텝을 시도할 여지가 생깁니다.
- 다만 모든 상황에서 속도가 빨라지는 것은 아닙니다. 환경에 따라 속도는 비슷하거나 약간 느려질 수도 있지만, “안 죽고 돌아간다”는 가치가 큽니다.
해결 2순위: VAE 타일링으로 고해상도 디코딩 OOM 방지
xFormers로도 OOM이 나면, 다음 병목은 VAE 디코딩/인코딩입니다. 특히 1024x1024 이상, 업스케일 파이프라인, 또는 배치 처리에서 VAE가 마지막에 VRAM을 확 잡아먹고 터지는 경우가 많습니다.
이때 VAE 타일링은 이미지를 여러 타일로 쪼개서 VAE를 순차 처리함으로써, 한 번에 필요한 메모리 피크를 낮춥니다.
diffusers에서 VAE 타일링 켜기
diffusers는 VAE 타일링을 간단히 켤 수 있습니다.
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
pipe.enable_xformers_memory_efficient_attention()
# VAE 타일링 활성화
pipe.enable_vae_tiling()
image = pipe(
prompt="a detailed portrait photo, 85mm lens, natural light",
height=1024,
width=1024,
num_inference_steps=30,
).images[0]
image.save("out.png")
트레이드오프
- VRAM은 확실히 줄어드는 편이지만, 타일을 나눠 처리하므로 속도는 느려질 수 있습니다.
- 타일 경계에서 미세한 이음새(artifact)가 생길 수 있는데, 구현/버전에 따라 다르며 보통은 큰 문제 없이 넘어가는 경우가 많습니다.
WebUI 계열에서의 VAE 타일링
WebUI는 버전/확장에 따라 “VAE tiling” 옵션이 있거나, --medvram, --lowvram 같은 모드가 VAE/UNet 메모리 사용 패턴을 바꿉니다. 다만 이 글의 핵심은 “VAE를 타일로 쪼개 피크를 낮춘다”는 원리이므로, 사용 중인 UI가 제공하는 VAE 타일링 옵션을 우선 확인하세요.
함께 쓰면 좋은 보조 옵션(우선순위 포함)
xFormers와 VAE 타일링만으로도 대부분의 OOM은 해결되지만, 그래도 부족하면 아래를 조합합니다.
1) Half precision: fp16 또는 bf16
대부분의 소비자 GPU에서는 fp16이 VRAM 절감에 즉효입니다.
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
bf16은 지원 GPU에서 안정성이 좋은 경우가 있지만, 환경에 따라 속도/호환성이 다릅니다.
2) Attention slicing
어텐션을 슬라이스로 나눠 계산해 피크를 줄입니다.
pipe.enable_attention_slicing()
xFormers가 안 되는 환경에서 특히 유용합니다.
3) Sequential CPU offload / Model CPU offload
GPU 메모리가 극도로 부족한 경우 CPU로 일부를 오프로딩합니다. 대신 속도는 크게 느려질 수 있습니다.
pipe.enable_sequential_cpu_offload()
4) 해상도/배치/스텝의 현실적인 조정
- 배치 크기:
batch_size또는 한 번에 생성하는 장수를 1로 - 해상도:
1024가 필요하면 가로세로 중 한 축을 조금 줄이거나, 2단계 업스케일 전략 사용 - 스텝 수: 스텝은 주로 시간에 영향이 크지만, 일부 구성에서는 메모리에도 간접 영향이 있습니다
이 부분은 성능 튜닝의 감각과 비슷합니다. 예를 들어 DB에서 느린 쿼리를 “인덱스/히스토그램으로 병목을 찌르듯” GPU도 병목 구간(어텐션/VAE)을 먼저 찌르는 게 효율적입니다. 관련 사고방식은 MySQL 8.0 쿼리 느림? 히스토그램·인덱스 튜닝과도 통합니다.
OOM이 계속 난다면: 흔한 함정 체크리스트
캐시/누수처럼 보이는 “VRAM이 안 내려감”
PyTorch는 메모리를 캐시합니다. 작업이 끝나도 nvidia-smi 상 VRAM이 바로 내려가지 않을 수 있습니다. 하지만 다음 요청에서 OOM이 난다면 캐시가 아니라 실제로 피크가 한계를 넘는 겁니다.
가능하면 요청 단위로 파이프라인을 재생성하지 말고(오히려 파편화/오버헤드), 하나를 재사용하면서 동시성만 조절하세요.
동시 요청 처리
서빙 환경에서 가장 흔한 원인은 “한 번에 두 요청이 겹쳤다”입니다. 단일 요청은 되는데, 트래픽이 들어오면 OOM이 나는 패턴이죠.
- GPU 1장이라면 워커 수를 1로 제한
- 큐잉을 도입해 동시 실행을 막기
고정된 최대 해상도 제한
사용자가 임의 해상도를 넣을 수 있으면 언젠가 터집니다. API 레벨에서 상한을 두는 편이 안전합니다.
실전 권장 조합(8GB~12GB VRAM 기준)
아래는 “품질-속도-안정성” 균형을 맞추기 위한 출발점입니다.
fp16사용- xFormers 활성화
- 그래도 불안하면 VAE 타일링 활성화
- 여전히 부족하면 attention slicing 추가
- 마지막 수단으로 CPU offload
diffusers 기준으로는 다음처럼 시작하면 됩니다.
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
pipe.enable_xformers_memory_efficient_attention()
pipe.enable_vae_tiling()
# 부족하면 다음 줄도 추가
# pipe.enable_attention_slicing()
result = pipe(
prompt="ultra detailed landscape, golden hour, volumetric light",
width=1024,
height=768,
num_inference_steps=28,
)
result.images[0].save("landscape.png")
마무리: OOM은 ‘옵션 나열’이 아니라 피크 제어 문제
Stable Diffusion의 VRAM OOM은 대부분 “총량 부족”이 아니라 특정 단계에서의 피크 메모리가 원인입니다. 그래서 xFormers로 어텐션 피크를 낮추고, VAE 타일링으로 디코딩 피크를 분할하는 접근이 가장 재현 가능하고 효과가 큽니다.
여기까지 적용했는데도 OOM이 난다면, 그 다음은 모델/컨트롤넷/업스케일러 등 구성요소별로 하나씩 끄면서 피크를 만드는 범인을 찾는 방식이 가장 빠릅니다. 운영 장애를 추적할 때처럼, 조건을 고정하고 변수를 하나씩만 바꾸는 식으로 접근하면 해결 시간이 확 줄어듭니다.