본문 바로가기
삵
AI

llama.cpp 라우터로 GGUF 여러 개 서빙하기

sstdio.h·2026년 11월 10일·조회 0

로컬에서 모델을 여러 개 돌리다 보면 모델마다 llama-server 프로세스를 따로 띄우고 포트를 나눠 쓰게 된다. 채팅용, 코드용, 임베딩용으로 세 개를 올려 두면 쓰지도 않는 모델이 VRAM을 깔고 앉아 있다. 최근 llama.cpp에 들어온 라우터 모드를 쓰면 이 구조를 엔드포인트 하나로 접을 수 있다.

라우터 모드는 모델을 지정하지 않고 llama-server를 띄우면 켜진다. 모델 디렉터리나 프리셋 파일을 읽어 두고, 요청의 model 필드를 보고 해당 모델을 그 자리에서 로드해 응답을 돌려준다. 동시에 올라가는 모델 수는 --models-max(기본 4개)로 제한하고, 한도를 넘으면 가장 오래 안 쓴 모델부터 내린다. 아래에서 디렉터리로 띄우는 방법부터 프리셋 설정, 그리고 네 개가 한꺼번에 올라가 VRAM을 넘겨 버리는 함정까지 순서대로 다룬다.

1. 라우터 모드가 하는 일

기존 llama-server는 프로세스 하나에 모델 하나다. 라우터 모드는 이 프로세스를 관리자 역할로 돌려서, 내부에 여러 모델 인스턴스를 띄우고 요청을 맞는 인스턴스로 넘긴다. OpenAI 호환 API(/v1/chat/completions 등)는 그대로 쓰고, 요청 본문의 model 값만 바꾸면 백엔드 모델이 바뀐다.

모델을 처음 요청하면 그때 로드된다(lazy load). 쓰지 않으면 메모리에 올라가지 않고, --models-max 한도에 걸리면 LRU(least recently used, 가장 오래 안 쓴 것) 순으로 언로드한다. 모델별로 프로세스와 포트를 나눠 관리하던 걸 설정 파일 한 개로 줄여 준다.

라우터 모드는 비교적 최근 빌드에 들어왔고 플래그 이름이 버전에 따라 조금씩 바뀌었다. 먼저 llama-server --help로 --models-dir, --models-max 항목이 있는지 확인하고 시작하는 편을 권한다.

$ llama-server --help | grep -A1 models-
  --models-dir PATH                   for router server, directory containing models (default: disabled)
  --models-preset PATH                for router server, path to INI file containing model presets
  --models-max N                      for router server, maximum number of models to load simultaneously
                                      (default: 4, 0 = unlimited)
  --models-memory-margin N            for router server, MiB of device memory to keep free per device,
                                      unload LRU models until a new one fits (default: 0, 0 = disabled)
  --no-models-autoload                for router server, whether to automatically load models (default: enabled)

2. 디렉터리 하나로 띄우기

가장 간단한 방식은 GGUF 파일을 한 디렉터리에 모아 두고 --models-dir로 가리키는 것이다.

$ ls ./models
qwen2.5-7b-instruct-q4_k_m.gguf
qwen2.5-coder-7b-q4_k_m.gguf
nomic-embed-text-v1.5.Q8_0.gguf

$ llama-server --models-dir ./models -c 8192 -ngl 99 --host 0.0.0.0 --port 8080

여기서 -c(컨텍스트 크기)와 -ngl(GPU에 올릴 레이어 수)는 라우터 레벨 설정이라, 이 디렉터리의 모든 모델이 같은 값을 물려받는다. 임베딩 모델까지 컨텍스트 8192에 전부 GPU로 올리면 모델별 최적값과 어긋날 수 있다. 모델마다 다르게 주고 싶으면 다음 절의 프리셋을 쓴다.

모델 목록은 GET /models로 확인한다. 디스커버리된 모델과 현재 로드 상태가 같이 나온다.

$ curl -s http://localhost:8080/models | jq '.data[] | {id, status}'
{
  "id": "qwen2.5-7b-instruct-q4_k_m.gguf",
  "status": "unloaded"
}
{
  "id": "qwen2.5-coder-7b-q4_k_m.gguf",
  "status": "unloaded"
}

처음엔 전부 unloaded다. 채팅 요청을 한 번 던지면 해당 모델만 로드된다.

$ curl -s http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5-7b-instruct-q4_k_m.gguf",
    "messages": [{"role": "user", "content": "ping"}]
  }' | jq -r '.choices[0].message.content'
pong

첫 요청은 모델을 디스크에서 읽어 GPU에 올리는 시간 때문에 느리다. 모델 크기와 디스크 속도에 따라 다르지만 첫 응답은 수 초가 걸릴 수 있고, 두 번째부터는 이미 올라가 있으니 평소 속도가 나온다.

3. 프리셋으로 모델마다 다르게

모델별 컨텍스트 크기나 GPU 레이어 수를 따로 주려면 INI 프리셋 파일을 만들고 --models-preset으로 넘긴다. 섹션 이름(대괄호 안)이 클라이언트가 model 필드에 쓰는 식별자가 된다.

$ cat models.ini
[chat]
model = /opt/models/qwen2.5-7b-instruct-q4_k_m.gguf
ctx-size = 16384
ngl = 99
load-on-startup = true

[coder]
model = /opt/models/qwen2.5-coder-7b-q4_k_m.gguf
ctx-size = 32768
ngl = 99

[embed]
model = /opt/models/nomic-embed-text-v1.5.Q8_0.gguf
ctx-size = 2048
ngl = 99
$ llama-server --models-preset ./models.ini --host 0.0.0.0 --port 8080

이제 클라이언트는 짧은 이름으로 모델을 부른다. "model": "coder"로 보내면 코더 모델이, "embed"로 보내면 임베딩 모델이 응답한다.

load-on-startup = true를 준 chat 모델은 서버가 뜨면서 미리 로드된다. 자주 쓰는 모델은 이걸 켜 두면 첫 요청의 로드 지연을 피한다. 단 이 설정은 서버 기동 시점에만 적용된다. 프리셋을 나중에 고쳐 모델을 추가하면 목록에는 보여도 자동으로 올라가지는 않는다.

전역 플래그 --no-models-autoload를 주면 자동 로드 자체가 꺼진다. 이 플래그는 프리셋의 load-on-startup = true보다 우선한다. 자동 로드를 끄면 모든 모델을 POST /models/load로 직접 올려야 한다.

$ curl -s -X POST http://localhost:8080/models/load \
  -H "Content-Type: application/json" \
  -d '{"model": "coder"}'

$ curl -s -X POST http://localhost:8080/models/unload \
  -H "Content-Type: application/json" \
  -d '{"model": "coder"}'

INI 포맷은 아직 완전히 표준화되지 않았고 키 이름이 빌드에 따라 달라진 적이 있다. ngl이 안 먹으면 n-gpu-layers로, ctx-size가 안 먹으면 c로 바꿔 보고, 막히면 역시 --help로 현재 빌드의 키를 확인한다.

4. 네 개가 한꺼번에 올라가면 VRAM이 터진다

여기서 가장 많이 걸리는 지점이다. --models-max의 기본값은 4인데, 이 숫자는 "개수" 제한이지 "용량" 제한이 아니다. 라우터는 네 번째 모델까지는 VRAM에 들어가든 말든 일단 올리려 든다. 각 모델이 혼자서는 들어가도, 서로 다른 모델 네 개를 동시에 요청받으면 합쳐서 VRAM을 넘겨 OOM이 난다.

예를 들어 24GB GPU에서 7B Q4 모델은 하나에 5~6GB 정도를 쓴다. 세 개까지는 들어가지만, 네 번째로 14B 모델을 요청하면 남은 공간이 모자라 로드가 실패한다. 이때 전형적으로 이런 에러가 뜬다.

ggml_backend_cuda_buffer_type_alloc_buffer: allocating 8192.00 MiB on device 0: cudaMalloc failed: out of memory
llama_model_load: error loading model: unable to allocate CUDA0 buffer
llama_load_model_from_file: failed to load model

첫 번째 대응은 --models-max를 실제로 동시에 올려도 되는 개수로 낮추는 것이다. 24GB에 7B Q4만 쓴다면 3 정도가 현실적이다. 한도를 낮추면 네 번째 모델을 요청할 때 LRU 순으로 하나를 내리고 자리를 만든 뒤 새 모델을 올린다.

$ llama-server --models-preset ./models.ini --models-max 3 --port 8080

다만 개수만으로는 크기가 제각각인 모델을 못 막는다. 7B 세 개는 들어가도 14B 두 개와 7B 하나는 안 들어갈 수 있다. 그래서 더 확실한 안전장치는 --models-memory-margin이다. 디바이스마다 지정한 MiB만큼 여유를 남겨 두고, 새 모델이 그 여유 안에 들어갈 때까지 LRU 모델을 내린다. 개수가 아니라 실제 남은 메모리를 보고 판단한다.

$ llama-server --models-preset ./models.ini \
    --models-max 4 \
    --models-memory-margin 2048 \
    --port 8080

이렇게 주면 라우터는 GPU에 최소 2GB를 비워 두려 하고, 새 모델이 안 들어가면 오래된 모델을 먼저 내린다. 멀티 GPU면 디바이스별로 적용된다. 실무에선 --models-max로 개수 상한을 두고, --models-memory-margin으로 용량 바닥을 받쳐 두는 조합을 쓴다.

로드 상태와 VRAM은 로드 직후 nvidia-smi로 같이 보면 감이 잡힌다. 모델을 하나씩 요청해 가며 메모리가 얼마나 차는지 재 보고, 그 수치에 맞춰 --models-max와 마진을 정하면 된다.

$ nvidia-smi --query-gpu=memory.used,memory.total --format=csv
memory.used [MiB], memory.total [MiB]
17820 MiB, 24564 MiB

5. systemd로 상주시키기

라우터를 상시 띄워 둘 거라면 유닛 파일로 묶는다. 모델 경로와 마진 값만 환경에 맞게 바꾸면 된다.

$ cat /etc/systemd/system/llama-router.service
[Unit]
Description=llama.cpp router server
After=network.target

[Service]
ExecStart=/opt/llama.cpp/llama-server \
  --models-preset /opt/models/models.ini \
  --models-max 3 \
  --models-memory-margin 2048 \
  --host 0.0.0.0 --port 8080
Restart=on-failure

[Install]
WantedBy=multi-user.target

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now llama-router
$ systemctl status llama-router

프리셋을 고쳤을 때는 재시작해야 반영된다. 모델을 추가하고 sudo systemctl restart llama-router를 하면 새 목록으로 올라온다. 다음으로 해 볼 만한 것은 자주 쓰는 모델 조합의 VRAM 합을 실제로 재서, 그 GPU에서 --models-max를 몇으로 둘지 숫자를 확정하는 일이다.

자주 묻는 질문

라우터 모드는 어떻게 켜나?

모델 파일을 인자로 주지 않고 llama-server를 띄우면 라우터 모드로 동작한다. --models-dir로 GGUF 디렉터리를 가리키거나 --models-preset으로 INI 프리셋 파일을 넘기면 된다. 모델을 지정해 실행하면 기존 단일 모델 서버로 뜬다.

동시에 몇 개까지 로드되나?

--models-max로 정하며 기본값은 4다. 0으로 주면 무제한이다. 한도를 넘으면 가장 오래 안 쓴(LRU) 모델부터 언로드하고 새 모델을 올린다. 다만 이 값은 개수 제한일 뿐 VRAM 용량을 보지 않는다.

모델 네 개가 한꺼번에 올라가 VRAM이 터진다. 어떻게 막나?

--models-max를 실제로 동시에 올려도 되는 개수로 낮추고, --models-memory-margin으로 디바이스마다 남겨 둘 여유 메모리(MiB)를 지정한다. 마진을 주면 새 모델이 그 공간에 들어갈 때까지 LRU 모델을 먼저 내리므로, 크기가 제각각인 모델도 용량 기준으로 막을 수 있다.

자주 쓰는 모델을 미리 올려 두려면?

프리셋 INI에서 해당 모델 섹션에 load-on-startup = true를 주면 서버 기동 시 미리 로드된다. 단 이 설정은 기동 시점에만 적용되고, 전역 플래그 --no-models-autoload를 주면 자동 로드가 꺼지면서 load-on-startup보다 우선한다.

모델마다 컨텍스트 크기를 다르게 줄 수 있나?

--models-dir로 띄우면 모든 모델이 라우터 레벨의 -c와 -ngl 값을 똑같이 물려받는다. 모델별로 다르게 주려면 --models-preset INI에서 섹션마다 ctx-size, ngl 등을 따로 지정한다. 키 이름은 빌드에 따라 다를 수 있으니 안 먹으면 n-gpu-layers 같은 대체 키를 확인한다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.