
16GB의 유휴 VRAM 되찾기: ComfyUI가 작동을 멈췄을 때 이를 퇴거시키는 30줄짜리 사이드카 (sidecar)
요약
ComfyUI가 작업을 마친 후에도 16GB의 VRAM을 점유하며 해제하지 않는 문제를 해결하는 방법을 다룹니다. 모니터링 대시보드를 통해 유휴 VRAM 점유를 확인하고, 이를 자동으로 정리하는 30줄 규모의 사이드카 컨테이너 구현 사례를 공유합니다.
핵심 포인트
- ComfyUI가 작업 종료 후에도 VRAM을 해제하지 않는 현상 발견
- nvidia-smi만으로는 서비스별 상세 점유 파악이 어려움
- 모니터링 대시보드를 통한 GPU 자원 사용 패턴 분석의 중요성
- 유휴 VRAM을 회수하기 위한 30줄 규모의 사이드카 컨테이너 활용
저의 홈랩(homelab)은 단일 RTX 3090이 장착된 하나의 Linux 박스입니다. 24GB의 VRAM이 있고, 이 자원을 모두 사용하려는 세 가지 GPU 집약적 서비스가 있습니다: 이미지 생성을 위한 ComfyUI, 전사를 위한 WhisperX, 그리고 로컬 LLM을 위한 Ollama입니다. 카드 한 장에서 이들은 이미 협상 대상입니다.
지난주에 그 협상이 깨졌습니다. 제가 직접 만든 모니터링 대시보드가 한눈에 범인을 잡아냈기에, 짧은 요약본을 공유합니다: 무엇이 문제였는지, 어떻게 발견했는지, 그리고 이를 영구적으로 해결한 30줄짜리 컨테이너(container)에 대한 이야기입니다.
(만약 전편을 보고 싶으시다면 — Ollama 분류 모델이 8,000 토큰의 작업을 수행하기 위해 40,000 토큰의 컨텍스트(context)를 예약했던 그 사건 — 그 내용은 Two LLMs, One 3090, Zero OOM에서 확인하실 수 있습니다. 동일한 박스, 동일한 교훈입니다.)
증상
다른 용무로 홈랩 대시보드의 GPU 탭을 열었는데, 아무것도 실행 중이지 않은 상태에서 카드가 71% 차 있는 것을 발견했습니다.
comfyui가 16.3GB를 점유하고 있었습니다. GPU 사용률(utilisation): 0%. 모델은 로드되어 있었지만 아무것도 하지 않고 있었습니다. 서비스별 이력을 통해 상황은 명확해졌습니다 — ComfyUI는 16.3GB까지 치솟은 후 해당 시간 동안 100% 점유 상태를 유지했습니다:
이것이 제가 대시보드를 만든 이유 전부입니다. nvidia-smi는 VRAM이 17/24GB라고 알려줍니다. 하지만 _어떤 서비스_인지, _어떤 모델_인지, _언제부터_인지까지는 알려주지 않습니다. GPU 탭은 VRAM을 사용하는 모든 PID를 자동으로 해당 컨테이너에 매핑하므로,
$ nvidia-smi --query-compute-apps=pid,used_memory,process_name --format=csv,noheader
111465, 16666 MiB, python3 # <- ComfyUI, 유휴 상태 (idle)
109583, 588 MiB, /app/.venv/bin/python
ComfyUI는 다음 요청을 빠르게 처리하기 위해 생성 작업이 끝난 후에도 체크포인트 (checkpoint)를 메모리에 상주 시킵니다. 이미지 생성 전용 장비라면 합리적인 방식입니다. 하지만 24GB 공유 카드의 경우 이는 매우 공격적입니다. FLUX fp8 체크포인트는 약 16GB에 달하며, ComfyUI 0.22 버전에는 이를 반환하기 위한 유휴 타임아웃 (idle timeout) 기능이 없습니다. 일단 이미지를 한 번 생성하고 나면, 컨테이너를 재시작하기 전까지 그 16GB는 사라집니다.
좋은 소식은 ComfyUI에 정확히 이 용도로 사용되는 API가 있다는 점입니다. unload_models 옵션을 포함한 POST /free를 호출하면 VRAM에서 모델을 내립니다.
$ curl -X POST http://localhost:8188/free \
-H 'Content-Type: application/json' \
-d '{"unload_models": true, "free_memory": true}'
단 한 번의 호출로 ComfyUI의 사용량이 16666 MiB에서 378 MiB로 줄었습니다. 모델은 다음 /prompt 요청 시 자동으로 다시 로드됩니다. 해당 요청에 약 20~30초가 추가되지만, 하루에 이미지를 몇 번 생성하지 않는 저에게는 비용이 들지 않는 셈입니다.
따라서 저는 매 작업 후에 /free를 호출하고 싶지는 않습니다 (연속적인 작업 시 웜 캐시 (warm-cache) 속도를 저하시키기 때문입니다). 대신 ComfyUI가 한동안 유휴 (idle) 상태를 유지할 때 호출하고 싶습니다. ComfyUI 자체는 이 기능을 수행하지 않으므로, 저는 외부에서 이 기능을 덧붙였습니다.
해결책: 유휴 상태 언로드 사이드카 (idle-unload sidecar)
ComfyUI 포크 (fork)도, 커스텀 노드 (custom node)도 아닙니다. 큐 (queue)를 감시하다가 몇 분 동안 활동이 없으면 모델을 퇴거시키는 아주 작은 컨테이너입니다.
#!/bin/sh
# 큐 비활성 기간이 지나면 VRAM에서 ComfyUI 모델을 언로드합니다.
INTERVAL=${INTERVAL:-30}
...
이 스크립트는 30초마다 /queue를 폴링 (poll) 합니다. queue_running과 queue_pending이 모두 비어 있으면 유휴 카운터 (idle counter)를 증가시킵니다. 300초 동안 연속으로 유휴 상태가 유지되면 /free를 POST로 호출하고 카운터를 초기화합니다. 어떤 작업이라도 발생하면 카운터가 초기화되므로, 집중적인 생성 작업 중에는 모델이 웜 상태 (warm)로 유지됩니다. 퇴거는 실제로 생성을 멈췄을 때만 발생합니다.
새로 빌드할 이미지는 없습니다. curlimages/curl에는 이미 sh와 curl이 포함되어 있습니다.
docker run -d --name comfyui-idle-unloader --restart unless-stopped \
--network host -e IDLE_SECONDS=300 -e INTERVAL=30 \
-v /opt/comfyui-idle-unloader/unload-idle.sh:/unload-idle.sh:ro \
...
--network host를 사용하여 localhost의 ComfyUI에 도달할 수 있게 하고, --restart unless-stopped를 사용하여 재부팅 후에도 유지되도록 합니다. 이것이 배포의 전부입니다.
전 / 후
동일한 대시보드에서 관찰한 결과는 하나의 절벽과 같습니다:
| ComfyUI, 유휴 상태 | 점유된 VRAM | GPU "사용률" | WhisperX + Ollama 사용 가능 용량 |
|---|---|---|---|
| 전 (Before) | 16.3 GB | 71% | ~7 GB |
| 후 (After) | 0.37 GB | 5% | ~23 GB |
가끔 발생하는 약간 더 느린 이미지 생성이라는 비용을 치르고 약 16GB를 되찾았습니다. WhisperX와 Ollama가 남은 자원을 두고 싸우는 일이 멈췄습니다.
포크(Fork)도, 패치(Patch)도, 기다려야 할 업스트림 PR(Pull Request)도 없습니다. 단 30줄의 sh 스크립트와 한 가지 일만 수행하는 컨테이너일 뿐입니다. 만약 내일 ComfyUI가 유휴 TTL(Time To Live) 기능을 탑재한다면, 저는 이것을 삭제해도 잃을 것이 없습니다.
패치가 아닌 사이드카 (sidecar)를 선택한 이유
- 결합 해제 (Decoupled). ComfyUI의 내부 구조에 대해 전혀 알 필요가 없습니다. 오직 공개된
/queue및/free엔드포인트(Endpoint)만 사용합니다. ComfyUI가 업데이트되어도 영향을 받지 않습니다. - 유지보수할 것이 없음. ComfyUI의 안정적인 HTTP API를 활용하므로, ComfyUI가 업데이트되어도 이 도구에는 영향을 주지 않습니다.
- 다른 곳에서도 동일한 패턴 적용 가능. "모델 언로드 (unload model)" 엔드포인트가 있는 것이라면 무엇이든 (A1111, sleep 모드가 있는 vLLM, TGI 등) 동일한 방식으로 퇴거시킬 수 있습니다.
핵심적인 요점이자 제가 대시보드를 계속 구축하는 이유는 이렇습니다. 저는 로그를 읽어서 이 문제를 발견한 것이 아닙니다. 한 화면에서 특정 이름의 유휴 서비스가 16GB를 점유하고 있다고 알려주는 도구 덕분에 발견했습니다. 보이지 않는 VRAM은 되찾을 수 없습니다.
이 모니터는 하나의 컨테이너로 구성되어 있으며, MIT 라이선스를 따릅니다. docker compose up -d --build 명령어로 실행할 수 있습니다. 현재는 GPU 패널에서 NVIDIA 전용으로 작동하며, 설계상 단일 호스트(single-host)를 지원합니다:
github.com/SikamikanikoBG/homelab-monitor
다른 분들은 공유 GPU 환경에서 유휴 모델 퇴거(idle model eviction) 문제를 어떻게 처리하시나요? 이와 같은 사이드카(sidecar)를 사용하시나요, 모델 서버(model server)에 TTL(Time To Live)을 설정하시나요, 아니면 그냥 docker restart를 하고 넘어가시나요? 어떤 방식이 가장 효과적인지 진심으로 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

