4개 모델을 순서대로 벤치마크하던 중, 이상하게 다음 모델의 로딩이 평소보다 느리게 느껴지는 순간이 있었다. ps aux로 확인해보니 원인이 바로 보였다.

$ ps aux | grep llama-server
simon  41203  539.6  ...  llama-server --model .../qwen2.5-coder-14b ...
simon  40891    0.0  ...  llama-server --model .../deepseek-coder-v2-16b ...

새로 시작한 14B 테스트는 CPU 539.6%로 활발히 계산 중인데, 바로 아래에 방금 전에 테스트했던 DeepSeek 16B 프로세스가 CPU 0%로 여전히 살아있었다. 이미 다른 터미널에서 Aider 세션을 종료했는데도 말이다.

keep_alive, 알아서 안 꺼주는 5분

원인은 Ollama의 keep_alive 메커니즘이었다. Ollama는 한 번 로드한 모델을 요청이 끝난 뒤에도 기본 5분간 메모리에 유지한다. 같은 모델로 연속 요청이 들어올 걸 대비한 최적화인데, 이게 여러 모델을 짧은 간격으로 번갈아 테스트하는 상황에서는 오히려 방해가 된다 — 이전 모델이 여전히 RAM을 점유하고 있어서, 새 모델이 로드될 때 쓸 수 있는 메모리 대역폭이 줄어들기 때문이다.

확인하고 정리하는 법

지금 메모리에 뭐가 올라와 있는지는 이 명령으로 확인한다.

ollama ps
NAME                     SIZE      PROCESSOR    UNTIL
deepseek-coder-v2:16b    13 GB     100% CPU     4 minutes from now

UNTIL 컬럼이 바로 keep_alive 카운트다운이다. 다음 모델을 깨끗한 상태에서 테스트하고 싶다면 명시적으로 내려주면 된다.

ollama stop deepseek-coder-v2:16b

명령어에 주석을 달았다가 걸린 함정

여기서 사소하지만 짜증나는 실수를 하나 했다. 명령어 뒤에 설명을 남기려고 이렇게 썼다.

ollama stop deepseek-coder-v2:16b   # 명시적으로 언로드
Error: accepts 1 arg(s), received 4

bash에서는 # 뒤가 주석 처리되지만, 대화형 zsh는 인라인 # 주석을 기본으로 지원하지 않는다. 뒤에 붙인 한글 설명이 그대로 인자 4개로 쪼개져서 명령어에 전달된 것이다. 이후로는 셸에 실행할 명령에는 절대 줄 끝에 설명을 붙이지 않기로 했다.

정리

여러 로컬 모델을 오가며 테스트할 계획이라면, 모델을 바꾸기 전에 ollama ps로 현재 상태를 확인하고 ollama stop으로 이전 모델을 명시적으로 내려주는 습관을 들이는 게 좋다. 그렇지 않으면 “모델이 원래 이렇게 느린 건지, 이전 모델이 메모리를 붙잡고 있어서 느린 건지” 헷갈리는 상황이 계속 반복된다.