research

Local LLM VRAM 모델 스왑 방식

Local LLM VRAM 모델 스왑 방식

질문: llama.cpp/ollama로 LLM을 돌릴 때, 모델 전환 시 VRAM에 올린 채 swap하는가, 내리고 다시 올리는가? (API 호출 자체는 논외 — 인프라 레벨 질문)


결론부터

둘 다 "내리고 다시 올리는" 방식이야. VRAM 안에서 in-place swap 없음.

모델 A 실행 중 (VRAM 점유)
  → 모델 B 요청
  → 모델 A unload (VRAM 해제)
  → 모델 B load (수 초~수십 초 소요)
  → 모델 B 실행

llama.cpp

기본 동작

  • 서버 시작 시 지정된 모델 1개를 VRAM에 로드
  • 해당 모델로 요청 처리
  • 다른 모델로 전환하려면 서버 재시작 필요 (기존 방식)

router 모드 (2025년 말 추가)

llama-server --models-dir ./models -c 8192 -ngl 99
  • 여러 모델을 등록해두고 요청 시 자동 load/unload
  • API:
    POST /models/load   {"model": "qwen3-7b.gguf"}
    POST /models/unload {"model": "qwen3-7b.gguf"}
    GET  /models        → loaded/loading/unloaded 상태
    
  • --max-models N: 동시에 VRAM에 올려둘 모델 수 (기본 1)
  • 첫 요청 시 자동 load, 동일 모델 재요청은 즉시 처리

주의

  • --max-models로 2개 이상 동시 로드 가능하나 VRAM 여유 필요
  • RTX 5080 16GB에서 7B + VLM 동시 → 거의 불가능
  • 모델 교체 시 unload 후 load = VRAM 재할당
  • VRAM 부족 시 자동 eviction 미지원 (수동 unload 필요)
    • 자동 VRAM 기반 eviction은 커뮤니티 feature request로 논의 중

Ollama

기본 동작 (LRU 자동 eviction)

OLLAMA_KEEP_ALIVE=5m  # 기본값: idle 모델을 5분간 VRAM 유지
OLLAMA_KEEP_ALIVE=0   # 요청 후 즉시 unload
OLLAMA_KEEP_ALIVE=30s # 30초 유지
  • 새 모델 요청 시 VRAM 부족하면 LRU(가장 오래 사용 안 된) 모델 먼저 unload
  • 사람이 직접 관리 안 해도 됨 (llama.cpp router보다 편함)
  • 내부는 llama.cpp wrapper

swap 시 지연

  • 2~10초 (모델 크기에 따라 다름)
  • 자주 전환하면 성능 저하

전략 선택 기준

| 상황 | 전략 | |------|------| | 모델 항상 1개만 사용 | 단일 llama-server, unload 없음 | | 가벼운 모델 상시 + 가끔 무거운 모델 | Ollama keep-alive 조정 | | 명시적 제어 필요 | llama.cpp router 모드 + /models/load | | 동시 여러 모델 유지 | VRAM이 허용할 때만 --max-models |


내 일정 프로그램에 적용하면

RTX 5080 16GB 기준:

상시: Qwen 7B (경량 작업, 과제 파싱 등) ~8GB
  → keep-alive 길게 설정 (항상 VRAM 상주)

필요 시: VLM (묵연 PDF 변환) ~12GB 이상
  → Qwen 먼저 unload → VLM load (20~60초)
  → VLM 작업 완료 → Qwen 다시 load

판단 필요: Claude API
  → VRAM 무관, 항상 사용 가능

핵심 설계 결정: LLM 라우터가 VRAM 상태를 체크하고 작업 큐를 직렬화해야 함. 동시 LLM 요청은 불가.

작업 큐 직렬화 패턴 (Python pseudo)

class VRAMRouter:
    def __init__(self):
        self.current_model = "qwen-7b"  # 상시 상주
        self.queue = asyncio.Queue()

    async def route(self, task):
        if task.requires_vlm:
            # 1. 현재 모델 unload
            await llama_server.unload(self.current_model)
            # 2. VLM load (20~60초)
            await llama_server.load("vlm-model")
            # 3. 작업 실행
            result = await run_vlm(task)
            # 4. 다시 경량 모델로 복귀
            await llama_server.unload("vlm-model")
            await llama_server.load("qwen-7b")
            return result
        elif task.requires_judgment:
            # VRAM 무관
            return await claude_api.complete(task)
        else:
            # 상시 상주 Qwen 직접 사용
            return await llama_server.complete(task)

pi-ai와의 관계

pi-ai의 getModel(), complete(), stream()어느 모델에 요청을 보낼지 결정하는 라우팅 레이어야.

실제 VRAM 관리는 pi-ai 범위 밖 — llama.cpp/ollama 서버가 담당.

pi-ai (라우팅 결정):
  작업 유형 → 모델 선택 → API 호출

llama-server / ollama (VRAM 관리):
  요청 받으면 필요한 모델 load/unload 처리

두 레이어가 분리되어 있음. pi SDK를 쓰든 직접 구현하든 VRAM 관리 로직은 별도로 작성해야 한다.


관련