Published on

SDXL+ComfyUI로 VRAM OOM 없이 4K 배치 생성

Authors

서버급 GPU가 아니어도 SDXL로 4K 이미지를 배치로 뽑고 싶을 때 가장 먼저 부딪히는 벽이 VRAM OOM 입니다. 특히 ComfyUI는 노드 그래프가 명확한 만큼, 어디서 메모리가 터지는지 역추적이 가능하고, 반대로 작은 설정 하나로도 사용량이 급변합니다.

이 글에서는 SDXL을 ComfyUI에서 4K 배치 생성할 때 OOM이 나는 지점을 단계별로 쪼개고, 실전에서 가장 효과가 컸던 해결책을 워크플로우 관점으로 정리합니다. 목표는 “한 장은 되는데 배치가 안 된다”를 “배치도 안정적으로 돈다”로 바꾸는 것입니다.

OOM이 나는 진짜 지점: 4K 그 자체가 아니라 중간 텐서

4K는 결과 이미지 해상도일 뿐이고, VRAM을 터뜨리는 건 대개 다음 중 하나입니다.

  • SDXL 베이스 샘플링을 4K에서 직접 수행
  • 업스케일 단계에서 타일링 없이 한 번에 디코드 또는 디테일러를 수행
  • VAE 디코딩을 GPU에서 계속 잡고 있어 피크가 치솟음
  • 배치에서 latent, cond, control 텐서가 그래프에 남아 누적

핵심은 “피크 VRAM”입니다. 배치는 평균 사용량이 아니라 피크가 조금만 올라가도 바로 OOM이 납니다. 따라서 전략은 아래처럼 잡는 게 안전합니다.

  • 샘플링은 낮은 해상도에서
  • 4K는 업스케일 + 타일 기반 디테일로
  • VAE 디코드는 필요할 때만, 가능하면 CPU로
  • 배치는 큐 단위로 쪼개고, 노드에서 캐시를 줄이고, 불필요한 연결을 끊기

권장 파이프라인: 저해상도 생성 + 4K 타일 업스케일

가장 안정적인 접근은 “Base 생성”과 “4K화”를 분리하는 것입니다.

  1. SDXL Base로 1024 또는 1216 정도에서 1차 생성
  2. 업스케일러로 2배 또는 4배 확대
  3. 타일 기반으로 SDXL Refiner 또는 디테일러를 적용
  4. 최종 4K 저장

이렇게 하면 SDXL U-Net이 가장 무거운 구간을 상대적으로 작은 latent에서 돌게 되어 피크 VRAM이 확 내려갑니다.

왜 4K에서 바로 샘플링하면 터질까

SDXL은 U-Net 자체도 크고, attention 계열 연산이 해상도에 민감합니다. 1024에서 1장 생성이 되더라도 4K는 단순히 4배가 아니라 중간 텐서가 훨씬 더 커져서 VRAM이 기하급수로 압박됩니다. 여기에 배치까지 얹으면 사실상 “메모리 폭주”가 됩니다.

이때의 현상은 인프라에서 흔히 보는 급격한 리소스 스파이크와 유사합니다. 쿠버네티스에서 CrashLoopBackOff 원인을 리소스 관점으로 쪼개듯, 그래프의 피크를 줄이는 방식으로 접근하는 게 정답입니다. 참고로 이런 장애 분해 방식은 Kubernetes CrashLoopBackOff 12가지 원인·해결 글의 접근과도 결이 같습니다.

ComfyUI에서 바로 적용하는 VRAM 절약 체크리스트

아래 항목은 “효과 대비 부작용이 적은” 순서로 정리했습니다.

1) VAE를 CPU로 내리기

VAE 디코딩은 생각보다 VRAM 피크를 올립니다. ComfyUI에서 VAE를 CPU로 돌리거나, 디코드 타이밍을 늦추면 피크가 내려갑니다.

  • 가능하면 latent 상태로 다음 노드까지 넘기고
  • 이미지로 디코드하는 노드는 마지막에 배치로 모아서 수행

만약 노드 구성상 중간에 이미지가 필요하다면, 타일 업스케일 이후에만 디코드하도록 변경하세요.

2) 타일 기반 업스케일과 타일 디테일

4K는 한 번에 처리하지 말고 타일로 쪼개야 합니다.

  • 업스케일은 Tiled 계열 노드 사용
  • 디테일링도 타일 단위로 적용
  • 타일 크기는 512 또는 768부터 시작

타일 크기를 키울수록 이음새는 줄지만 VRAM 피크는 올라갑니다. 반대로 타일을 너무 작게 하면 경계 아티팩트가 증가합니다.

3) 배치 전략을 “Batch Size”가 아니라 “Queue 분할”로

ComfyUI에서 배치 사이즈를 올리면 한 번에 잡는 텐서가 늘어납니다. 대신 다음을 추천합니다.

  • Batch Size는 1로 두고
  • 프롬프트나 시드를 바꿔 여러 작업을 큐에 쌓기
  • 워크플로우는 동일하되 입력만 바꿔 반복

이 방식은 처리량은 비슷하게 유지하면서, 피크 VRAM을 낮게 유지합니다.

4) Refiner는 “필요할 때만”

SDXL Refiner는 품질 개선 효과가 있지만, 무거운 파이프라인을 하나 더 태우는 셈입니다.

  • 전체 이미지에 Refiner를 거는 대신
  • 업스케일 이후 타일 디테일 단계에서만 약하게 적용
  • 또는 인물/얼굴만 디테일러로 국소 적용

이렇게 하면 품질을 챙기면서도 VRAM을 절약할 수 있습니다.

5) ControlNet, IP-Adapter는 최소 연결로

컨트롤 계열은 conditioning 텐서가 커질 수 있고, 그래프에 남으면 누적됩니다.

  • 꼭 필요한 컨트롤만 사용
  • 강도와 해상도를 낮추고
  • 4K 단계에서만 쓰지 말고 “저해상도 단계에서 구조를 잡고” 4K는 디테일만

예시 워크플로우 구성(개념도)

ComfyUI의 실제 노드 이름은 설치된 커스텀 노드에 따라 다르지만, 구조는 아래처럼 잡으면 됩니다.

  • Checkpoint Loader (SDXL Base)
  • CLIP Text Encode (Prompt / Negative)
  • Empty Latent Image (예: 1024x1024 또는 1216x832)
  • KSampler (Base)
  • Latent Upscale (2x) 또는 Upscale Model Loader + Upscale
  • Tiled VAE Decode 또는 VAE Decode (CPU)
  • Tiled Detail / Tiled KSampler (선택: Refiner 또는 Base 재주입)
  • Save Image (4K)

포인트는 “샘플링은 저해상도 latent에서”, “4K는 타일 처리로” 입니다.

ComfyUI 실행 옵션과 PyTorch 메모리 튜닝

ComfyUI는 실행 옵션 및 PyTorch 설정에 따라 메모리 거동이 달라집니다. 아래는 많이 쓰는 방향성입니다.

  • --lowvram 또는 --medvram 계열 옵션을 시도
  • xFormers 또는 SDP attention 경로를 테스트
  • VRAM이 빡빡하면 FP16을 기본으로

환경별로 최적 조합이 달라 “정답 옵션”은 없지만, 접근은 같습니다. 한 번에 여러 옵션을 바꾸지 말고, 한 가지씩 바꾼 뒤 동일 워크로드로 VRAM 피크를 비교하세요.

VRAM 피크를 재현 가능한 방식으로 측정하기

배치 생성은 “가끔 터지는” 상태가 가장 디버깅이 어렵습니다. 아래처럼 재현성을 확보하세요.

  • 시드를 고정
  • 동일 프롬프트
  • 동일 해상도와 동일 스텝
  • 동일 업스케일 배율

그리고 GPU 모니터링을 켠 상태에서 “어느 노드 구간에서 피크가 뜨는지”를 기록합니다. 이런 식의 병목 추적은 프론트엔드에서 Long Task를 추적하는 방식과도 닮아 있습니다. 성능 문제를 단계별로 쪼개는 방법론이 궁금하면 Chrome INP 점수 급락? Long Task 추적·해결도 같이 참고할 만합니다.

실전 레시피: 8GB, 12GB, 24GB VRAM별 권장값

정확한 수치는 모델, 노드, 드라이버, xFormers 여부에 따라 달라지지만 “안정적으로 시작할 수 있는 값”을 제시합니다.

8GB VRAM

  • Base 생성: 768 또는 896 권장
  • Batch Size: 1
  • 업스케일: 2x 후 타일 디테일
  • 타일 크기: 512
  • Refiner: 가급적 생략 또는 매우 약하게
  • VAE: CPU 디코드 우선

12GB VRAM

  • Base 생성: 1024 또는 1216 계열 가능
  • Batch Size: 1, 큐로 반복
  • 업스케일: 2x 또는 4x(타일 필수)
  • 타일 크기: 512 또는 768
  • Refiner: 타일 단계에서만 제한적으로

24GB VRAM

  • Base 생성: 1024에서 여유
  • Batch Size: 2도 가능하나, OOM 방지를 위해 큐 분할이 여전히 유리
  • 업스케일: 4x 타일 + 디테일러 조합
  • 타일 크기: 768 또는 1024도 시도 가능

코드 예제: 배치 큐를 안정적으로 돌리는 스크립트

ComfyUI는 API를 통해 프롬프트 워크플로우를 큐에 넣을 수 있습니다. 아래는 “Batch Size는 1로 유지하고, N개를 순차 큐잉”하는 형태의 예시입니다.

주의: 워크플로우 JSON 포맷은 ComfyUI 버전 및 그래프에 따라 달라집니다. 핵심은 seed 와 출력 파일명 등을 반복마다 바꿔서 큐에 넣는 방식입니다.

import json
import time
import requests

COMFY_URL = "http://127.0.0.1:8188"

# 미리 내보낸 workflow api json을 로드했다고 가정
with open("workflow_api.json", "r", encoding="utf-8") as f:
    workflow = json.load(f)


def set_seed(wf: dict, seed: int) -> dict:
    # 예시: KSampler 노드 id가 "3"이고 seed 입력 키가 seed 라고 가정
    # 실제로는 본인 워크플로우의 노드 id와 입력 키에 맞게 수정
    wf = json.loads(json.dumps(wf))
    wf["3"]["inputs"]["seed"] = seed
    return wf


def set_prompt(wf: dict, text: str) -> dict:
    # 예시: CLIP Text Encode 노드 id가 "5"이고 text 입력 키가 text 라고 가정
    wf = json.loads(json.dumps(wf))
    wf["5"]["inputs"]["text"] = text
    return wf


def queue_prompt(wf: dict):
    payload = {"prompt": wf}
    r = requests.post(f"{COMFY_URL}/prompt", json=payload, timeout=30)
    r.raise_for_status()
    return r.json()


base_prompt = "ultra detailed photo, cinematic lighting, 35mm"

for i in range(20):
    seed = 1000 + i
    wf = set_seed(workflow, seed)
    wf = set_prompt(wf, base_prompt)

    resp = queue_prompt(wf)
    print("queued", i, resp)

    # 큐를 너무 빨리 밀어 넣으면 디스크 I/O나 CPU 디코드가 병목이 될 수 있어 약간의 간격 권장
    time.sleep(0.2)

이 방식의 장점은 명확합니다.

  • GPU는 항상 “한 장씩” 처리하므로 피크 VRAM이 안정적
  • 결과적으로 OOM으로 전체 런이 깨질 확률이 크게 감소
  • 프롬프트, 시드, LoRA 조합을 바꿔도 동일한 안정성을 유지

자주 터지는 구간별 처방전

OOM 로그를 보면 대개 “마지막 한 끗”에서 터집니다. 그때는 아래처럼 구간별로 조치하세요.

업스케일 직후 디테일에서 터질 때

  • 타일 크기를 768에서 512로 낮추기
  • 디테일 스텝 수 줄이기
  • Refiner를 빼고 Base로만 디테일
  • VAE 디코드를 CPU로

Base 샘플링에서 이미 터질 때

  • 해상도를 1024에서 896 또는 768로
  • 스텝 수를 줄이고, 대신 업스케일 디테일로 보완
  • CFG를 과도하게 올리지 않기

배치만 올리면 터질 때

  • Batch Size를 올리는 대신 큐 분할
  • 그래프에서 불필요한 미리보기 노드 제거
  • 중간 디코드 노드를 뒤로 미루기

운영 관점 팁: “안 터지는” 것만큼 “복구 가능한” 것도 중요

장시간 배치 생성은 결국 운영입니다. 한 번 OOM이 나면 GPU 컨텍스트가 꼬이거나, 큐가 중단되거나, 결과 파일이 부분 저장되는 등 후처리가 더 귀찮아집니다.

  • 작업을 작은 큐 단위로 쪼개기
  • 결과 저장 경로를 작업 단위로 분리하기
  • 실패 시 재시도 가능한 형태로 시드와 입력을 기록하기

이런 관점은 쿠버네티스에서 파드가 비정상 상태에 빠졌을 때 “원인 분석”과 “재발 방지”를 동시에 하는 것과 같습니다. 장애가 반복된다면 Azure AKS에서 Pod가 Terminating에 멈출 때 해결법처럼 상태 전이를 기준으로 문제를 분해하는 습관이 큰 도움이 됩니다.

마무리: 4K 배치는 해상도 문제가 아니라 피크 제어 문제

SDXL+ComfyUI에서 4K 배치 생성의 본질은 “최대 메모리 구간을 어떻게 낮추느냐”입니다. 정리하면 아래 3가지만 지키면 성공 확률이 급상승합니다.

  • 4K에서 직접 샘플링하지 말고, 저해상도 생성 후 타일 업스케일
  • VAE 디코드와 Refiner 같은 피크 상승 요인을 분리하고 필요할 때만 사용
  • Batch Size를 키우지 말고 큐를 분할해 한 장씩 안정적으로 처리

여기까지 적용했는데도 OOM이 난다면, 다음 단계는 “어느 노드에서 피크가 뜨는지”를 특정한 뒤 그 구간만 타일링하거나 CPU로 내리는 식으로 미세 조정하면 됩니다.