- Published on
SDXL+ControlNet 메모리폭주 OOM 해결 8가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
SDXL에 ControlNet(특히 Canny, Depth, OpenPose, Lineart 등)을 붙이면, 같은 해상도에서도 SD 1.5 대비 VRAM 사용량이 훨씬 가파르게 증가합니다. 이유는 단순히 모델이 커서가 아니라, UNet 내부 피처맵과 어텐션 텐서가 해상도에 따라 제곱으로 커지고, ControlNet이 중간 피처를 추가로 보관하거나 복제하는 경로가 생기기 때문입니다.
이 글은 "왜 갑자기 메모리 폭주가 나는지"를 먼저 짚고, 그 다음 "품질을 크게 깎지 않으면서" OOM을 줄이는 실전 설정 8가지를 우선순위대로 정리합니다. WebUI(Automatic1111/ComfyUI) 사용자와, diffusers 파이프라인 사용자 모두를 기준으로 설명합니다.
참고: 성능 문제를 진단하는 접근 자체는 웹 성능의 Long Task 쪼개기와 비슷합니다. 병목을 분해해 원인을 좁히는 방식은 Chrome INP 폭증? Long Task 추적·분해 실전 글의 사고방식과도 통합니다.
OOM이 나는 대표 패턴 3가지
1) 해상도와 배치가 임계점을 넘는 순간 폭발
SDXL은 기본 1024 계열을 권장하지만, VRAM은 대략 H*W에 비례가 아니라 어텐션/피처맵 때문에 체감상 더 가파르게 증가합니다. 여기에 batch size 또는 batch count를 올리면 텐서가 복제되어 즉시 OOM이 납니다.
2) ControlNet이 "추가 UNet"처럼 동작
ControlNet은 조건을 주입하기 위해 UNet의 여러 블록에 잔차를 더합니다. 구현에 따라 중간 피처를 더 오래 잡고 있거나, 여러 ControlNet을 동시에 쓰면 메모리 상주량이 누적됩니다.
3) VAE 디코드/인코드가 끝에서 한 번 더 터짐
생성 마지막에 VAE가 latent를 이미지로 디코드할 때, 순간적으로 VRAM이 튀는 경우가 있습니다. "거의 다 됐는데 마지막에 OOM" 패턴이 여기에 해당합니다.
해결 1) 해상도를 "SDXL 친화"로 재설계하기
가장 확실한 방법은 픽셀 수 자체를 줄이는 겁니다. 다만 무작정 줄이면 구도가 깨지니, SDXL이 학습에서 많이 본 종횡비 프리셋으로 맞추는 게 중요합니다.
- 권장:
1024x1024,1152x896,1216x832,896x1152,832x1216 - 피해야 할 조합: 애매한 고해상도 + 긴 변 과도(예:
1536x1024같은 조합은 급격히 비싸질 수 있음)
실전 팁:
- 최종 출력이 1536이 필요하면, 본생성은
1024계열로 하고 업스케일(라텍트 업스케일 또는 ESRGAN)로 마무리하세요. - ControlNet 입력도 동일 해상도로 맞추되, 꼭 필요 없으면 ControlNet 프리프로세서 해상도를 한 단계 낮추는 게 효과적입니다.
해결 2) 배치 전략 바꾸기: batch size는 1로, batch count로 돌리기
VRAM은 batch size에 선형으로 늘어납니다. 반면 batch count는 순차 실행이라 VRAM에 거의 영향이 없습니다.
batch size: 1유지- 여러 장 필요하면
batch count를 늘리기
특히 SDXL+ControlNet은 batch size 2부터 "갑자기" 터지는 GPU가 많습니다.
해결 3) ControlNet 개수와 weight를 줄이고, 역할을 분리하기
ControlNet을 2~3개 동시에 쓰면, 체감상 "UNet을 여러 번 돌리는" 느낌의 메모리 압박이 생깁니다. 다음처럼 역할을 분리하세요.
- 1차 패스: 구도/포즈 고정용 ControlNet 1개(예: OpenPose 또는 Depth)
- 2차 패스: 디테일/라인 보정용 ControlNet 1개(예: Lineart 또는 Canny)
또한 weight를 무조건 1.0으로 두기보다, 0.5~0.8 범위에서 타협하면 품질 대비 메모리/안정성이 좋아집니다(일부 구현은 weight가 직접 VRAM을 줄이진 않지만, 과한 조건으로 인한 추가 샘플링/재시도 비용을 줄입니다).
해결 4) FP16/BF16 및 xFormers 또는 SDPA로 어텐션 메모리 절감
어텐션이 VRAM을 가장 많이 먹는 구간 중 하나입니다. 다음 중 가능한 조합을 선택하세요.
torch의scaled_dot_product_attention(SDPA)사용xformers메모리 효율 어텐션 사용- dtype을
fp16또는 GPU 지원 시bf16
diffusers 기준 예시입니다.
import torch
from diffusers import StableDiffusionXLPipeline, ControlNetModel
controlnet = ControlNetModel.from_pretrained(
"diffusers/controlnet-canny-sdxl-1.0",
torch_dtype=torch.float16,
)
pipe = StableDiffusionXLPipeline.from_pretrained(
"stabilityai/stable-diffusion-xl-base-1.0",
controlnet=controlnet,
torch_dtype=torch.float16,
variant="fp16",
)
# 1) xFormers (설치되어 있을 때)
pipe.enable_xformers_memory_efficient_attention()
# 2) 또는 PyTorch SDPA를 쓰는 환경이면(버전에 따라 자동)
# 별도 설정 없이도 SDPA가 적용될 수 있습니다.
pipe.to("cuda")
WebUI 계열이라면 보통 설정에서 xformers 또는 SDP 관련 옵션을 켜는 것으로 대응합니다.
해결 5) VAE를 타일링하거나 CPU 오프로딩으로 "마지막 OOM" 막기
"샘플링은 끝났는데 VAE 디코드에서 OOM"이면, VAE가 순간적으로 큰 텐서를 잡는 겁니다.
- VAE tiling: VAE 디코드를 타일 단위로 쪼개 VRAM 피크를 낮춤
- VAE를 CPU로: 속도는 느려지지만 안정성은 크게 증가
diffusers 예시:
# VAE 디코드 메모리 피크 감소
pipe.enable_vae_tiling()
# 더 강한 절감이 필요하면(속도 손해)
pipe.enable_vae_slicing()
ComfyUI도 VAE 타일 관련 노드/옵션이 있으며, WebUI는 확장 또는 설정으로 제공되는 경우가 많습니다.
해결 6) sequential CPU offload 또는 model CPU offload 적용
VRAM이 8GB~12GB급이면 SDXL+ControlNet이 특히 빡빡합니다. 이때는 "필요할 때만 GPU로 올리는" 오프로딩이 현실적인 해법입니다.
sequential_cpu_offload: 모듈 단위로 순차 오프로딩(가장 절약, 가장 느림)model_cpu_offload: 모델 일부를 CPU에 두고 필요 시 이동
diffusers 예시:
# accelerate 설치가 필요할 수 있습니다.
pipe.enable_sequential_cpu_offload()
# 또는
# pipe.enable_model_cpu_offload()
체감 팁:
- NVMe가 빠를수록(스왑/페이지 캐시 포함) 오프로딩의 체감이 좋아집니다.
- Windows에서는 드라이버/메모리 정책 영향으로 오프로딩 효율이 다를 수 있습니다.
해결 7) ControlNet 입력 전처리 해상도와 채널을 최적화
ControlNet은 "입력 이미지"도 텐서로 들고 갑니다. 프리프로세서가 불필요하게 큰 해상도/채널을 만들면 VRAM이 같이 올라갑니다.
- Canny/Lineart: 프리프로세서 해상도를 한 단계 낮추고, 최종 샘플링 해상도는 유지
- Depth/OpenPose: 필요 이상 디테일한 맵을 만들지 않기(예: 과도한 포즈 디테일)
- 가능하면 8-bit 이미지로 유지하고, 파이프라인에서만 float로 변환
ComfyUI에서는 전처리 노드의 resolution 파라미터가 핵심이고, WebUI에서도 ControlNet 패널에 전처리 해상도 옵션이 있는 경우가 많습니다.
해결 8) 메모리 누수처럼 보이는 "캐시/세션 잔존" 제거
같은 설정인데 "몇 번 돌리면 점점 VRAM이 찬다"면, 진짜 OOM 최적화 이전에 세션/캐시가 쌓이는 경우를 의심해야 합니다.
체크리스트:
- 동일 프로세스에서 파이프라인을 반복 생성하지 말고 재사용
- 사용 끝난 텐서는 참조를 끊고
torch.cuda.empty_cache()호출 - 그래프가 쌓이지 않게
torch.inference_mode()사용
diffusers 루프 예시:
import torch
@torch.inference_mode()
def run(pipe, prompt, image, control_image):
out = pipe(
prompt=prompt,
image=image,
control_image=control_image,
num_inference_steps=30,
)
return out.images[0]
# 반복 실행 시
for i in range(10):
img = run(pipe, "a photo of a cat", image=None, control_image=None)
# 저장 후 참조 제거
del img
torch.cuda.empty_cache()
이 문제는 "캐시가 꼬여서 계속 이상해지는" 유형과 닮았습니다. CI 캐시가 문제를 재현/은폐하는 것처럼, GPU 메모리도 세션이 길어질수록 상태가 누적될 수 있습니다. 캐시 진단 관점은 GitHub Actions 캐시로 CI 꼬일 때 진단·해결 가이드도 참고할 만합니다.
추천 조합: VRAM 용량별 현실적인 프리셋
8GB
- 해상도:
832x1216또는896x1152도 위험할 수 있어1024x1024에서 시작 - ControlNet: 1개만, 전처리 해상도 낮춤
- 필수:
xformers또는 SDPA, VAE tiling, CPU offload 중 1개 이상
12GB
- 해상도:
1024x1024안정, 종횡비 프리셋 가능 - ControlNet: 1~2개(동시 2개는 설정에 따라 위험)
- 권장: VAE tiling, 메모리 효율 어텐션
16GB 이상
- 해상도:
1152x896등도 여유 - ControlNet: 2개도 가능하나, 업스케일/Hi-res fix까지 하면 피크가 커질 수 있음
OOM을 줄이면서 품질을 지키는 운영 팁
- "본생성은 안전한 해상도" + "업스케일"이 총비용 대비 품질이 좋습니다.
- ControlNet은 "한 번에 여러 개"보다 "패스로 분리"가 안정적입니다.
- 마지막 OOM은 VAE가 원인인 경우가 많으니, 샘플링 단계만 보고 판단하지 마세요.
마무리
SDXL+ControlNet OOM은 단일 옵션으로 해결되기보다, 해상도/배치/어텐션/오프로딩/VAE를 함께 조합해야 안정화됩니다. 가장 효과가 큰 순서대로 정리하면 다음 흐름이 실전에서 성공률이 높습니다.
- 해상도 프리셋 재설계
- batch size 1 고정
- ControlNet 동시 개수 축소 및 패스 분리
- 메모리 효율 어텐션(xformers 또는 SDPA) + FP16/BF16
- VAE tiling/slicing
- CPU offload
- ControlNet 전처리 해상도 최적화
- 세션/캐시 잔존 제거
원하는 환경(Automatic1111, ComfyUI, diffusers)과 GPU VRAM 용량, 사용 중인 ControlNet 종류를 알려주면, 그 조합에 맞춰 "안 터지는" 구체 설정값(해상도/steps/CFG/Control weight/전처리 해상도)을 더 촘촘히 추천해드릴 수 있습니다.