- Published on
Stable Diffusion VRAM 부족? xFormers·Tiled VAE로 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Stable Diffusion을 돌리다 보면 가장 흔한 벽이 GPU VRAM 부족(OOM, CUDA out of memory)입니다. 특히 1024x1024 이상, 배치 크기 증가, 고해상도 업스케일, ControlNet 다중 적용, AnimateDiff 같은 확장 기능을 얹는 순간 8GB~12GB 카드에서 바로 터집니다.
이 글은 “품질을 크게 깎지 않으면서” VRAM을 줄이는 대표 해법 두 가지인 xFormers(메모리 효율 어텐션)와 Tiled VAE(VAE 디코딩/인코딩 타일링)를 중심으로, 어디에 어떻게 적용하는지까지 한 번에 정리합니다.
중간중간 OOM을 디버깅하는 사고방식도 다룹니다. 문제를 빠르게 좁히는 방식은 인프라 장애를 찾는 것과 유사합니다. 예를 들어 원인 파악을 “10분 내” 구조화하는 방법은 K8s CrashLoopBackOff 원인 10분내 찾는 법 같은 글의 접근과 닮아 있습니다.
VRAM이 어디서 터지는지 먼저 감 잡기
Stable Diffusion 파이프라인에서 VRAM을 많이 쓰는 구간은 대략 3개입니다.
- U-Net(확산 단계): 스텝마다 어텐션/컨볼루션이 반복되며 VRAM을 가장 많이 잡아먹습니다.
- VAE 인코딩/디코딩: 최종 이미지를 만들 때 큰 텐서를 다루며 순간 피크가 생깁니다. 고해상도에서 특히 큽니다.
- 부가 모델: ControlNet, LoRA 다중, IP-Adapter 등 추가 조건 모델이 메모리를 상시 점유합니다.
여기서 xFormers는 주로 1번(U-Net 어텐션)의 VRAM을 줄이고, Tiled VAE는 2번(VAE)의 피크 VRAM을 줄이는 방식입니다. 둘은 경쟁 관계가 아니라 서로 보완입니다.
OOM 로그에서 체크할 포인트
에러 메시지에 다음이 보이면 전형적인 VRAM 부족입니다.
CUDA out of memoryTried to allocate ... GiBreserved memory is ...
이때 “진짜 VRAM 부족”인지 “메모리 단편화”인지도 중요합니다. 단편화가 의심되면 PYTORCH_CUDA_ALLOC_CONF로 완화할 수 있습니다(뒤에서 설명).
1) xFormers: 메모리 효율 어텐션으로 U-Net VRAM 줄이기
xFormers는 PyTorch의 어텐션 연산을 더 메모리 효율적으로 수행하는 구현을 제공합니다. Stable Diffusion에서 가장 큰 메모리 소비처인 어텐션을 바꾸면, 같은 VRAM에서 더 큰 해상도/배치/스텝을 버틸 수 있습니다.
WebUI(AUTOMATIC1111)에서 xFormers 적용
가장 간단한 방법은 실행 옵션에 --xformers를 주는 것입니다.
# 예시: webui-user.sh 또는 webui-user.bat의 COMMANDLINE_ARGS
COMMANDLINE_ARGS="--xformers"
추가로 VRAM이 빡빡하면 아래 옵션도 함께 고려합니다.
--medvram또는--lowvram: 모델을 CPU로 일부 오프로딩(속도는 느려질 수 있음)--opt-sdp-attention: xFormers 대신 PyTorch SDP를 선호할 때(환경에 따라 더 안정적)
주의할 점은, xFormers는 CUDA/PyTorch 버전 조합에 따라 설치가 까다로울 수 있다는 것입니다. 설치가 꼬이면 “성능 튜닝”이 아니라 “환경 디버깅”이 됩니다. 이런 류의 재현/격리 방식은 캐시 문제를 좁히는 것과 비슷한데, CI 캐시 디버깅 관점은 GitHub Actions 캐시가 안 먹을 때 속도 3배 올린 실전 글의 접근이 참고됩니다.
diffusers(Python)에서 xFormers 적용 코드
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()
image = pipe(
prompt="a photo of a cat, 35mm, bokeh",
num_inference_steps=30,
guidance_scale=7.5,
).images[0]
image.save("out.png")
만약 xFormers가 설치되지 않았거나 호환이 안 맞으면 예외가 납니다. 이 경우 대안으로 PyTorch 2.x의 SDP 기반 최적화를 쓰는 선택지도 있습니다.
# PyTorch 2.x에서 attention 구현을 선택(환경에 따라 효과/안정성 차이)
import torch
torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cuda.enable_math_sdp(False)
xFormers를 켰는데도 VRAM이 부족한 이유
xFormers는 “U-Net 어텐션”에는 강하지만, 다음 상황에서는 한계가 있습니다.
- 해상도가 커져서 VAE 디코딩 피크가 더 큰 경우
- ControlNet 여러 개로 상시 점유가 커진 경우
- 배치 크기(
batch size)가 커서 텐서가 선형으로 증가한 경우
그래서 다음 섹션의 Tiled VAE가 같이 필요해집니다.
2) Tiled VAE: VAE 피크 VRAM을 타일로 쪼개기
고해상도에서 자주 터지는 구간이 VAE 디코딩입니다. 최종 latent를 이미지로 복원하는 과정에서 큰 텐서를 한 번에 처리하며 순간적으로 VRAM이 치솟습니다.
Tiled VAE는 이미지를 여러 타일로 나눠 VAE를 적용한 뒤 다시 합치는 방식으로, 피크 VRAM을 크게 낮춥니다. 대신 타일 경계에서 미세한 이음새(seam)가 생길 수 있어 타일 크기/오버랩 설정이 중요합니다.
WebUI에서 Tiled VAE 사용
WebUI에서는 보통 확장(Extension)이나 내장 옵션(버전/포크에 따라 다름)으로 제공합니다. 핵심은 다음 3가지를 조절하는 것입니다.
tile size: 타일 크기(작을수록 VRAM 절약, 속도 저하/이음새 위험 증가)overlap: 타일 겹침(이음새 완화, 약간의 비용 증가)VAE precision:fp16/bf16등
권장 시작점(대략적 가이드)
- 8GB:
tile size 96~128,overlap 16~32 - 12GB:
tile size 128~192,overlap 16~32
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")
# VAE 타일링 활성화
pipe.enable_vae_tiling()
# 필요 시 VAE 슬라이싱도 함께(추가 메모리 절약, 약간의 속도 손해)
pipe.enable_vae_slicing()
image = pipe(
prompt="ultra detailed cityscape, sunset",
height=1024,
width=1024,
num_inference_steps=28,
).images[0]
image.save("tiled_vae.png")
enable_vae_slicing()은 디코딩을 더 잘게 쪼개는 옵션이라, VRAM이 정말 빡빡할 때 유용합니다.
타일 경계 이슈 줄이는 팁
overlap을 늘리면 seam이 줄어듭니다.- 너무 작은 타일은 seam이 늘 수 있습니다. 가능한 한 “VRAM이 허용하는 최대 타일”이 품질에 유리합니다.
- 후처리 업스케일러(예: ESRGAN 계열)를 쓰면 seam이 더 도드라질 수 있으니 타일링 파라미터를 먼저 안정화하세요.
3) 같이 쓰면 좋은 VRAM 절약 옵션들(우선순위 포함)
xFormers와 Tiled VAE만으로도 체감이 크지만, OOM이 계속 난다면 아래를 우선순위대로 적용해 보세요.
(A) 해상도/배치/스텝을 “원인 분리”용으로 낮추기
먼저 재현을 단순화해야 합니다.
batch size를1로height/width를512로steps를20으로
이 상태에서 안정적이면, 하나씩 올리며 어디서 피크가 터지는지 확인합니다. 이런 식의 단계적 원인 분리는 장애 분석 기본기이며, 런타임/메모리 이슈를 단계적으로 좁히는 접근은 Go gRPC 메모리 누수? pprof로 잡는 7단계 같은 글의 흐름과도 통합니다.
(B) Mixed Precision: fp16 또는 bf16
대부분의 consumer GPU 환경에서는 fp16이 VRAM 절약에 직접적입니다.
- WebUI: 설정에서
half-precision관련 옵션 확인 - diffusers:
torch_dtype=torch.float16
단, 일부 카드/드라이버 조합에서 fp16이 NaN을 유발하면 bf16(지원 GPU에서) 또는 특정 모듈만 fp32로 올리는 식의 타협이 필요합니다.
(C) PyTorch CUDA allocator 설정으로 단편화 완화
OOM인데도 “남은 VRAM이 있어 보이는” 상황은 단편화일 수 있습니다. 아래 환경 변수를 시도해볼 만합니다.
# 큰 블록을 더 잘 쪼개서 재사용하도록 유도
export PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb:128"
환경에 따라 64, 128, 256 등으로 조정합니다.
(D) ControlNet/LoRA를 줄이거나, 해상도별로 전략 분리
- ControlNet을 2개 이상 쓰면 상시 점유가 커집니다.
- LoRA를 여러 개 얹으면 작은 차이 같아도 누적됩니다.
전략적으로는
- 저해상도에서 구도/포즈를 잡고
- 고해상도에서는 ControlNet을 줄이거나 끄고
- 업스케일 단계에서만 필요한 조건을 최소로
같은 분리가 효과적입니다.
4) 추천 조합: VRAM 용량별 “현실적인” 프리셋
환경마다 다르지만, 시작점으로 쓸 수 있는 조합을 제안합니다.
8GB GPU
- xFormers: 켬
- VAE tiling: 켬(타일
96~128, 오버랩16~32) - 배치:
1 - 해상도:
768부터 시작,1024는 설정을 타이트하게 - ControlNet: 가능하면
1개로
12GB GPU
- xFormers: 켬
- VAE tiling: 고해상도에서만 켬(타일
128~192) - 배치:
1~2(상황에 따라) 1024x1024가 실사용 가능 라인
16GB 이상
- xFormers: 켬(혹은 PyTorch SDP와 성능 비교)
- VAE tiling: 보통은 꺼도 되지만,
1536이상에서 유용
5) 체크리스트: OOM을 빠르게 끝내는 순서
마지막으로, 시행착오를 줄이는 “적용 순서”를 체크리스트로 정리합니다.
batch size=1, 해상도512, 스텝20으로 안정화fp16적용xFormers또는 PyTorch SDP 최적화 적용- 고해상도에서만
Tiled VAE적용(필요 시vae slicing까지) - ControlNet/LoRA 개수 줄이며 피크 확인
- 단편화 의심 시
PYTORCH_CUDA_ALLOC_CONF조정
이 순서대로 하면 “무작정 옵션을 다 켜서 느려지고도 OOM” 같은 상황을 피할 수 있습니다.
마무리
정리하면, Stable Diffusion의 VRAM 부족은 단순히 “카드가 약해서”가 아니라, U-Net 어텐션과 VAE 디코딩 피크라는 두 개의 병목이 겹쳐서 발생하는 경우가 많습니다. xFormers는 어텐션 병목을, Tiled VAE는 디코딩 피크를 각각 낮춰주기 때문에 함께 적용했을 때 체감이 큽니다.
다음 단계로는, 본인 워크플로우(예: ControlNet 중심인지, 업스케일 중심인지)에 맞게 “고해상도 단계만 타일링” 같은 전략을 세우면 속도와 품질을 동시에 챙길 수 있습니다.