환경
- GPU: NVIDIA L4 (VRAM 22GB)
- 모델: FLUX.1-dev (Transformer 12B + T5 Encoder 4.5B)
- 양자화: BitsAndBytes 8-bit
- 프레임워크: diffusers, transformers, torch
오류 1. enable_model_cpu_offload() + 8-bit 양자화 동시 사용 시 OOM
발생 상황
FLUX.1-dev를 8-bit 양자화로 로드한 뒤, VRAM을 아끼려고 enable_model_cpu_offload()을 호출했더니 오히려 OOM이 발생했다.
# 이렇게 하면 OOM 발생
self.pipe = FluxPipeline.from_pretrained(
FLUX_MODEL_ID,
transformer=transformer, # 8-bit 양자화됨
text_encoder_2=text_encoder_2, # 8-bit 양자화됨
torch_dtype=torch.bfloat16,
)
self.pipe.enable_model_cpu_offload() # 여기서 문제 발생
원인
BitsAndBytes 8-bit 양자화된 모델은 GPU 메모리에 고정되어야 한다. enable_model_cpu_offload()은 추론 단계마다 모델을 CPU↔GPU 사이에서 옮기는데, 양자화된 텐서는 이 이동 과정에서 정상적으로 처리되지 않아 GPU 메모리가 제대로 해제되지 않고 누적되면서 OOM이 터진다.
해결
enable_model_cpu_offload()을 제거하고 pipe.to(device)로 GPU에 직접 올린다.
# 수정: CPU offload 제거, GPU 직접 할당
self.pipe = FluxPipeline.from_pretrained(
FLUX_MODEL_ID,
transformer=transformer,
text_encoder_2=text_encoder_2,
torch_dtype=torch.bfloat16,
)
self.pipe.to("cuda") # GPU에 고정
오류 2. VAE 디코딩 단계에서 OOM
발생 상황
모델 추론(denoising)은 통과했는데, 최종 이미지를 디코딩하는 VAE 단계에서 OOM이 터졌다. 특히 1024x1024 이상 해상도에서 빈번하게 발생.
원인
FLUX의 VAE는 latent를 한번에 전체 디코딩하려고 시도한다. 이미지 해상도가 커지면 VAE 디코딩에 필요한 VRAM이 급격히 증가하는데, 이미 Transformer와 T5 인코더가 VRAM 대부분을 점유하고 있어서 VAE가 쓸 여유가 없다.
해결
VAE Slicing + VAE Tiling을 둘 다 활성화한다.
self.pipe.to("cuda")
# VAE 메모리 최적화 — 이 두 줄이 핵심
self.pipe.enable_vae_slicing() # 배치를 슬라이스로 나눠 디코딩
self.pipe.enable_vae_tiling() # 큰 이미지를 타일 단위로 나눠 디코딩
- VAE Slicing: 여러 이미지를 한 번에 디코딩하지 않고 하나씩 슬라이스해서 처리
- VAE Tiling: 하나의 큰 이미지를 작은 타일로 쪼개서 디코딩 후 합침
둘 다 적용하면 VAE 디코딩 시 VRAM 피크가 크게 내려간다. 속도는 약간 느려지지만 OOM이 안 나는 게 훨씬 중요하다.
오류 3. 1024px 해상도에서 간헐적 OOM
발생 상황
VAE 최적화를 적용했는데도 1024x1024, 1152x896, 1344x768 같은 고해상도에서 간헐적으로 OOM이 발생했다. 이미지 1~2장은 되는데 상세페이지처럼 4~6장 연속 생성하면 중간에 터지는 패턴.
원인
연속 생성 시 VRAM 파편화가 누적된다. torch.cuda.empty_cache()로 정리해도 파편화된 VRAM이 완전히 회수되지 않는 경우가 있고, 1024px 기준 해상도에서는 여유 VRAM이 거의 없어서 파편화만으로도 OOM 임계치를 넘긴다.
해결
해상도 테이블을 768px 기준으로 전면 하향 조정했다.
# Before — OOM 위험
FLUX_RESOLUTIONS = [
(1024, 1024), # 1:1
(1152, 896), # 약 4:3
(1344, 768), # 약 16:9
...
]
# After — L4 22GB 안전 범위
FLUX_RESOLUTIONS = [
(768, 768), # 1:1
(896, 640), # 약 4:3
(832, 576), # 약 3:2
(896, 512), # 약 16:9
...
]
IP-Adapter 사용 시에도 1024x1024 → 768x768로 고정:
# Before
if use_ref:
gen_w, gen_h = 1024, 1024
# After
if use_ref:
gen_w, gen_h = 768, 768
해상도를 1024→768로 줄이면 픽셀 수가 약 44% 감소하고, 실제 VRAM 사용량은 수백 MB 단위로 줄어든다.
오류 4. IP-Adapter 상시 로드로 인한 VRAM 부족
발생 상황
참조 이미지를 사용하는 섹션(hero, intro)이 끝난 뒤에도 IP-Adapter가 GPU에 남아있어서, 이후 일반 생성 섹션에서 VRAM이 부족해지는 현상.
원인
IP-Adapter(CLIP ViT + projection layer)가 약 1GB의 VRAM을 차지한다. 일반 생성 시에는 set_ip_adapter_scale(0.0) + 더미 이미지로 비활성화하고 있었지만, 모델 자체가 GPU에 올라가 있으므로 VRAM은 그대로 점유된다.
해결
IP-Adapter가 필요한 섹션이 끝나면 즉시 언로드하는 로직을 추가했다.
model_loader.py:
def unload_ip_adapter(self):
if not self._ip_adapter_loaded:
return
self.pipe.unload_ip_adapter()
self._ip_adapter_loaded = False
if self.device == "cuda":
torch.cuda.empty_cache()
agent_engine.py 워크플로우에서 자동 언로드:
ip_sections_done = False
for i, section in enumerate(plan):
# ... 섹션 처리 ...
# IP-Adapter 필요 섹션이 끝나면 즉시 언로드
if (
not ip_sections_done
and reference_image
and not _should_use_ip_adapter(section)
and i > 0
):
ip_sections_done = True
local_generator.unload_ip_adapter() # ~1GB VRAM 회수
오류 5. 멀티스레드 환경에서 GPU 동시 접근 충돌
발생 상황
FastAPI 비동기 서버에서 동시에 2건 이상의 이미지 생성 요청이 들어올 때, GPU에서 동시에 추론이 실행되면서 CUDA 오류 또는 OOM이 발생.
원인
CUDA는 기본적으로 같은 디바이스에서 여러 커널을 동시에 실행할 수 있지만, FLUX처럼 VRAM을 거의 다 쓰는 모델에서는 2개가 동시에 돌면 확실히 OOM이 터진다.
해결
클래스 레벨에 threading.Semaphore(1)을 걸어서 GPU 추론을 직렬화했다.
class ImageGenerator:
_gpu_lock = threading.Semaphore(1) # 클래스 레벨 — 인스턴스가 여러 개여도 1개
def generate(self, ...):
with ImageGenerator._gpu_lock:
if self.device == "cuda":
torch.cuda.empty_cache()
image = self.pipe(**gen_kwargs).images[0]
if self.device == "cuda":
torch.cuda.empty_cache()
전처리(프롬프트 생성, 이미지 로드)와 후처리(base64 변환, 파일 저장)는 lock 바깥에서 동시에 실행되고, 실제 GPU 추론만 한 번에 하나씩 실행된다.
정리: L4 22GB에서 FLUX.1-dev 안정 운영 체크리스트
항목 설정
| 양자화 | BitsAndBytes 8-bit (Transformer + T5) |
| CPU Offload | 사용 금지 (양자화와 충돌) |
| GPU 할당 | pipe.to("cuda") 직접 할당 |
| VAE 최적화 | enable_vae_slicing() + enable_vae_tiling() |
| 최대 해상도 | 768px 기준 (896x640, 832x576 등) |
| IP-Adapter | 필요 시에만 로드, 끝나면 즉시 unload_ip_adapter() |
| 동시성 제어 | threading.Semaphore(1)로 GPU 추론 직렬화 |
| VRAM 정리 | 생성 전후 torch.cuda.empty_cache() |
'AI 엔지니어준비' 카테고리의 다른 글
| 코드 한 줄 안 쓰고 실제 카페에 투입한 재고관리 앱을 하루 만에 만든 방법 (0) | 2026.03.24 |
|---|---|
| 자주 사용하는 Python 패턴 (0) | 2026.02.19 |
| 📝 "L4 GPU 22GB에서 SDXL을 실전 서비스에 올리며 겪은 삽질들" (1) | 2026.02.11 |
| gcp git 설정 매번 아이디 패스워드 치기 귀찮을때 (0) | 2026.02.09 |
| gcloud ssh timeout 뜰때! (0) | 2026.02.09 |