- Published on
Stable Diffusion VRAM OOM 해결 - xFormers·VAE 타일링
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Stable Diffusion을 돌리다 보면 가장 자주 마주치는 장애가 VRAM OOM(out of memory)입니다. 특히 1024x1024 이상, hires.fix, ControlNet, 여러 LoRA를 얹는 순간 “조금만 더”가 바로 크래시로 이어집니다. 이 글은 단순히 “해상도 낮추세요”가 아니라, 어디서 VRAM이 터지는지(UNet vs VAE vs attention) 를 나눠 보고, xFormers/SDP 기반 attention 최적화와 VAE 타일링(tiling/slicing) 을 중심으로 재현 가능한 튜닝 방법을 정리합니다.
또한 OOM은 ML만의 이슈가 아니라 “리소스 한도 초과”라는 점에서 운영 이슈와 닮아 있습니다. 컨테이너에서 메모리 한도 초과를 추적하는 관점은 K8s CrashLoopBackOff에서 OOMKilled 원인 추적 글의 접근을 참고하면 도움이 됩니다.
VRAM OOM의 구조: 어디서 메모리가 폭발하나
Stable Diffusion 파이프라인을 크게 쪼개면 다음이 VRAM 사용량을 좌우합니다.
- UNet(확산 모델 본체)
- 해상도(정확히는 latent 크기)에 비례해 activation이 커집니다.
- attention 연산이 특히 메모리 폭탄입니다.
- Text Encoder(CLIP 등)
- 상대적으로 작지만, 긴 프롬프트/가중치/배치가 커지면 영향이 있습니다.
- VAE(디코드/인코드)
- 최종 이미지로 디코딩할 때 한 번 크게 VRAM을 씁니다.
hires.fix나 업스케일 후 디코드에서 OOM이 자주 납니다.
- 부가 모듈(ControlNet, T2I-Adapter, IP-Adapter, 여러 LoRA)
- 파라미터 자체보다도 중간 activation, 추가 UNet 분기, 추가 attention으로 VRAM이 증가합니다.
OOM을 줄이려면 “전체를 조금씩 줄이기”보다, 가장 큰 피크를 만드는 구간을 정확히 줄이는 것이 효율적입니다. 실전에서는 보통 다음 두 군데가 원흉입니다.
- UNet attention 피크: xFormers/SDP로 크게 줄일 수 있음
- VAE 디코드 피크: VAE tiling/slicing으로 해결 가능
먼저 확인할 것: OOM 재현 조건을 고정하기
튜닝은 재현성이 중요합니다. 다음을 고정하고 시작하세요.
- 샘플러/스텝 수 고정(예:
DPM++ 2M Karras,30 steps) - 해상도 고정(예:
768x768) - 배치 고정(예: batch size
1, batch count1) - ControlNet/LoRA는 일단 최소로
그리고 “언제 OOM이 나는지”를 구분합니다.
- 샘플링 도중 OOM: UNet/attention 쪽일 확률이 큼
- 마지막에 이미지 저장/디코드 시점 OOM: VAE 쪽일 확률이 큼
- hires.fix 2차 패스에서만 OOM: 2차 해상도/denoise/업스케일러/ControlNet 조합을 의심
xFormers와 PyTorch SDP: attention 메모리 줄이기
attention은 토큰 수가 늘어나면 메모리 사용량이 급증합니다. SD에서는 latent 해상도가 올라갈수록 spatial 토큰이 늘어나서 attention이 폭발합니다. 이를 줄이는 대표적인 방법이 xFormers 또는 PyTorch Scaled Dot Product Attention(SDP) 입니다.
(1) AUTOMATIC1111(WebUI)에서 xFormers 적용
환경에 따라 옵션이 다르지만, 핵심은 “attention 구현을 메모리 효율적인 커널로 바꾸는 것”입니다.
- 실행 인자 예시
# 예시: webui-user.sh / webui-user.bat에 추가
set COMMANDLINE_ARGS=--xformers --medvram
--xformers가 불안정하거나 특정 GPU/드라이버에서 충돌하면, WebUI 빌드/버전에 따라--opt-sdp-attention같은 SDP 옵션이 더 안정적인 경우가 있습니다(옵션명은 배포판에 따라 다를 수 있음).
(2) diffusers 파이프라인에서 xFormers 적용
diffusers를 직접 쓰는 경우가 튜닝 관점에서 가장 명확합니다.
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")
# xFormers 메모리 효율 attention
pipe.enable_xformers_memory_efficient_attention()
# 추가로 VRAM 절약: CPU 오프로딩(속도는 느려질 수 있음)
# pipe.enable_model_cpu_offload()
image = pipe(
prompt="a photo of a city at night, ultra detailed",
num_inference_steps=30,
guidance_scale=7.0,
).images[0]
image.save("out.png")
팁: xFormers가 만능은 아니다
- 속도는 빨라질 수도/느려질 수도 있습니다(해상도, GPU 아키텍처, 드라이버에 따라 다름).
- 특정 환경에서 xFormers가 오히려 불안정하면 SDP(PyTorch 내장)로 전환하는 편이 낫습니다.
- attention 최적화는 주로 샘플링 중 OOM을 잡는 데 효과가 큽니다.
VAE 타일링(tiling)과 slicing: “마지막 디코드 OOM” 잡기
샘플링이 끝나고 “마지막에 저장하는 순간” 터진다면, 범인은 VAE 디코드일 가능성이 큽니다. 이때 가장 강력한 해법이 VAE 타일링입니다.
VAE 타일링은 큰 이미지를 한 번에 디코드하지 않고, 여러 타일로 쪼개서 디코드한 뒤 합치는 방식입니다. VRAM 피크가 줄어드는 대신 경계(seam) 처리가 필요할 수 있습니다.
(1) WebUI에서 VAE tiling/slicing
배포판마다 UI가 조금씩 다르지만, 보통 설정에 다음과 같은 토글이 있습니다.
VAE tiling또는Tiled VAEVAE slicing
권장 순서:
- 먼저
VAE slicing을 켜서 영향 확인(품질 영향이 상대적으로 적은 편) - 그래도 OOM이면
VAE tiling을 켜서 피크를 확실히 낮추기
hires.fix를 쓰는 경우, 2차 패스에서 디코드가 더 무거워지므로 타일링이 체감 효과가 큽니다.
(2) diffusers에서 VAE tiling/slicing
diffusers는 VAE에 대해 slicing/tiling을 제공하는 버전이 많습니다.
import torch
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16,
).to("cuda")
# attention 최적화
pipe.enable_xformers_memory_efficient_attention()
# VAE slicing: 디코드 시 메모리 피크 감소
pipe.enable_vae_slicing()
# VAE tiling: 더 강하게 피크 감소(버전에 따라 지원)
# pipe.enable_vae_tiling()
image = pipe(
"a cinematic portrait, 85mm, shallow depth of field",
height=1024,
width=1024,
num_inference_steps=25,
).images[0]
image.save("out_1024.png")
타일링 품질 이슈(경계선) 줄이기
- 타일 간 오버랩이 있는 구현을 쓰면 seam이 줄어듭니다.
- 디코드 후 약한 후처리(예: 경계 블렌딩)가 들어간 구현이 품질이 좋습니다.
- 타일링은 “OOM을 피하는 마지막 안전장치”로 두고, 가능하면 attention/해상도/정밀도 튜닝으로 먼저 안정화하는 편이 결과 품질이 좋습니다.
실전 튜닝 체크리스트: 무엇부터 바꿀까
OOM을 빨리 잡는 순서를 “효과 대비 부작용” 기준으로 정리하면 다음이 현실적입니다.
1) 배치 관련: batch size와 batch count
- VRAM에 가장 직접적으로 영향
- 우선
batch size를1로 고정 - 여러 장이 필요하면
batch count를 늘리되, 이는 순차 실행이라 VRAM 피크에는 덜 민감
2) 정밀도: FP16/BF16, 그리고 안전장치
- 대다수 소비자 GPU에서 FP16이 기본 절감 카드입니다.
- 환경에 따라 BF16이 더 안정적인 경우도 있지만, 지원 여부가 갈립니다.
diffusers 예시:
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16,
).to("cuda")
3) attention 최적화: xFormers 또는 SDP
- 샘플링 중 OOM이면 최우선
- ControlNet을 켜면 attention 부담이 커져서 효과가 더 크게 느껴질 수 있음
4) VAE slicing/tiling
- “마지막 디코드 OOM”에 직빵
hires.fix나1024x1024이상에서 특히 유효
5) 해상도 전략: 한 번에 크게 말고, 2단계로
- 무작정
1024x1024를 정면 돌파하기보다- 1차:
768x768또는512x768 - 2차: 업스케일 + 낮은 denoise
- 1차:
- 단, 2차 패스에서 ControlNet을 다시 태우면 VRAM이 다시 터질 수 있어 옵션을 분리해야 합니다.
6) 불필요한 모듈 정리: ControlNet/LoRA/업스케일러
- ControlNet 여러 개 동시 사용은 VRAM을 빠르게 갉아먹습니다.
- LoRA는 “개수”보다도 결합 방식/추가 모듈에 따라 영향이 달라질 수 있으니, OOM 재현 시에는 하나씩 추가하며 임계점을 찾는 게 좋습니다.
OOM을 “원인별”로 읽는 로그 습관
OOM 메시지는 대개 “얼마를 할당하려 했고, 남은 VRAM이 얼마인지”를 보여줍니다. 여기에 더해 다음을 함께 보면 원인 파악이 빨라집니다.
- OOM 발생 시점이 UNet forward인지, VAE decode인지
- 같은 설정인데도 랜덤하게 터진다면 메모리 단편화 가능성
- Windows 환경에서는 다른 프로그램(브라우저, 오버레이, 게임 런처)이 VRAM을 잡아먹는 경우도 흔함
운영 관점에서 “한도 초과”를 추적하는 방법론은 쿠버네티스에서 OOMKilled를 파는 방식과 유사합니다. 필요하면 Kubernetes CrashLoopBackOff 원인별 10분 진단에서처럼 “관찰 지표를 먼저 고정하고, 변경을 한 번에 하나씩” 적용하는 접근이 그대로 통합니다.
추천 조합(보수적, 안정 지향)
환경마다 다르지만, 범용적으로 성공률이 높은 조합을 제안하면 다음과 같습니다.
torch_dtype=torch.float16- attention: xFormers가 안정적이면 xFormers, 아니면 SDP
VAE slicing기본 ON1024이상이나hires.fix에서만VAE tilingON- batch size
1 - ControlNet은 1개부터 시작, 필요 시 추가
마무리: “UNet 피크”와 “VAE 피크”를 분리하면 답이 보인다
Stable Diffusion의 VRAM OOM은 대부분 두 갈래로 정리됩니다.
- 샘플링 중에 터진다: attention 최적화(xFormers/SDP) 를 최우선으로
- 마지막에 터진다: VAE slicing/tiling 으로 디코드 피크를 낮추기
여기에 FP16, 배치, 해상도 2단계 전략을 더하면 “GPU를 바꾸지 않고도” 꽤 많은 작업을 안정화할 수 있습니다. 중요한 건 한 번에 여러 옵션을 섞지 말고, OOM 재현 조건을 고정한 뒤 한 가지씩 적용해서 임계점을 찾는 것입니다.