Ollama 동시 요청 처리: OLLAMA_NUM_PARALLEL 설정 방법
OLLAMA_NUM_PARALLEL과 OLLAMA_MAX_QUEUE 설정으로 동시 요청 대기 및 거부 문제를 해결합니다. 병렬 슬롯 추가 시 VRAM 사용량이 증가하며 모델 로드 실패가 발생할 수 있으므로 메모리 용량을 반드시 확인하십시오.
첫 번째 Ollama 요청이 생성 중일 때 두 번째 요청은 어떻게 처리되는가
Ollama의 동시성 처리는 세 가지 환경 변수에 의해 결정되며, 기본 설정에서는 로드된 모델 하나가 한 번에 하나의 요청만 처리합니다. 두 번째 요청이 거부되거나 불완전한 답변을 받는 일은 없습니다. 해당 요청은 슬롯이 비워질 때까지 대기열에서 기다린 뒤, 슬롯이 확보되면 정상 속도로 실행됩니다.
들어오는 요청은 세 가지 경로 중 하나를 따릅니다. 즉시 빈 슬롯에서 시작하거나, 대기열에서 기다리거나, 혹은 대기열이 이미 가득 차서 서버가 HTTP 503 오류로 요청을 거부합니다. 어떤 결과가 나타날지는 OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUE, OLLAMA_MAX_LOADED_MODELS 설정에 따라 결정됩니다.
기본 설정은 안전을 우선하며, 이것이 바로 두 번째 사용자가 서버가 "응답 없음" 상태라고 보고하는 이유입니다. 실제로는 아무런 문제가 발생하지 않은 상태입니다. 슬롯을 추가하는 작업은 두 줄의 설정 변경만으로 가능합니다. 하지만 주의할 점은 메모리입니다. 병렬 슬롯마다 모델이 이미 처리한 토큰을 보관하는 메모리 블록인 키/값 캐시(KV cache)가 각각 필요합니다. VRAM(GPU 비디오 메모리)을 추가하지 않은 채 슬롯만 늘리면, 느린 응답을 받게 되는 것이 아니라 모델 로드 실패로 이어질 수 있습니다.
OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUE 및 OLLAMA_MAX_LOADED_MODELS의 제어 대상
다음은 2026년 8월 기준 현재 Ollama 릴리스의 기본값입니다. 아래에 표시된 로그 라인을 사용하여 본인의 설정을 확인하십시오. 여기에 기재된 숫자를 그대로 신뢰해서는 안 됩니다.
OLLAMA_NUM_PARALLEL는 로드된 모델 하나가 동시에 처리하는 요청의 개수입니다. 기본값은 1이며, 요청은 순차적으로 처리됩니다.OLLAMA_MAX_LOADED_MODELS은 동시에 메모리에 상주하는 서로 다른 모델의 개수입니다. 기본값은 0이며, 이는 Ollama가 GPU당 3개, GPU가 없는 장비에서는 3개의 모델을 자동으로 선택함을 의미합니다.OLLAMA_MAX_QUEUE는 대기열에서 기다릴 수 있는 요청의 최대 개수입니다. 기본값은 512입니다. 대기열이 가득 찼을 때 들어오는 요청은 즉시 거부됩니다.
최악의 경우 메모리 사용량은 앞의 두 값을 곱한 결과입니다. 4개의 슬롯을 가진 모델 2개가 로드되면 총 8개의 KV 캐시 슬롯이 동시에 상주하게 되며, Ollama는 이를 충족하려고 시도합니다. 단일 GPU 장비에서는 모델 하나를 유지하면서 슬롯을 할당하는 것이 계산하기에 더 수월하므로 일반적으로 권장됩니다.
병렬 슬롯마다 VRAM 비용이 발생하는 이유
Ollama는 모델을 로드할 때 별도의 러너 프로세스를 실행합니다. 이때 전달되는 두 가지 인자가 중요한데, -c는 러너가 KV 캐시를 할당하는 전체 컨텍스트 크기이며, -np은 병렬 시퀀스의 개수입니다. Ollama는 -c을 요청당 컨텍스트 길이와 슬롯 개수를 곱한 값으로 설정합니다. 러너는 이 전체 크기를 슬롯별로 균등하게 나누므로, 각 요청은 사용자가 지정한 컨텍스트 길이를 그대로 유지합니다.
이것이 제약 사항의 전부이며, 병렬 처리에 비용이 따르는 이유입니다. 슬롯을 1개에서 4개로 늘리면 요청당 컨텍스트 길이는 같아도 KV 캐시는 4배가 필요합니다. 슬롯 간에 공유되는 자원은 없으며, 러너가 시작될 때 분할이 고정되므로 유휴 슬롯의 자원을 사용 중인 슬롯에 빌려줄 수도 없습니다.
설정한 값 대신 실제 적용된 수치를 확인하려면 다음 명령을 사용합니다.
journalctl -u ollama --no-pager -n 500 | grep "starting llama-server"이 줄에는 -c과 -np를 포함한 전체 러너 명령행이 표시됩니다. 변수를 설정한 후에도 -np이 1이라면 설정값이 서버에 전달되지 않은 것이며, 그 이유는 다음 섹션에서 다룹니다.
모델 가중치와 KV 캐시가 VRAM에 모두 들어가지 않으면, Ollama는 일부 레이어를 시스템 RAM으로 옮기고 해당 레이어를 CPU에서 실행합니다. CPU 레이어는 GPU 레이어보다 훨씬 느리므로, 처음에 시작한 단일 요청을 포함하여 모든 요청이 느려집니다. 따라서 병렬 처리 설정을 높이는 것이 오히려 처리량(throughput)을 감소시킬 수 있습니다.
ollama ps전체 모델이 VRAM에 적재되면 PROCESSOR 열의 값은 100% GPU로 표시됩니다. 35%/65% CPU/GPU과 같이 분할된 상태는 모델의 일부가 CPU에서 실행 중임을 의미합니다. SIZE 열에는 KV 캐시가 포함되어 있으므로, 슬롯 개수를 늘리고 모델을 다시 로드하면 이 값이 증가합니다. OLLAMA_NUM_PARALLEL를 높이고 재시작한 뒤 요청을 하나 보내고 ollama ps을 다시 실행해 보십시오. 이것이 추측이 아닌 실제 측정된 변경 사항의 메모리 비용입니다.
컨텍스트 길이와 슬롯 개수는 서로 곱해지므로 함께 고려하여 선택해야 합니다. 4개의 슬롯을 사용하는 큰 컨텍스트는 4개의 큰 컨텍스트와 같습니다. 만약 모델의 num_ctx 컨텍스트 윈도우를 조정 중이라면, 두 값 중 하나씩만 변경하십시오. 그렇지 않으면 무엇 때문에 메모리가 가득 찼는지 알 수 없게 됩니다.
재부팅 후에도 변수가 유지되도록 설정하는 방법
Linux에서 Ollama는 systemd 서비스로 실행됩니다. 셸에서 export OLLAMA_NUM_PARALLEL=4을 실행해도 systemd는 자체 환경에서 서비스를 시작하며 사용자의 셸 환경을 참조하지 않으므로 아무런 변화가 없습니다. drop-in 파일을 사용하십시오.
sudo systemctl edit ollama.service열리는 편집기에 다음 내용을 추가하십시오:
[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_MAX_QUEUE=32"그런 다음 설정을 다시 불러오고 서비스를 재시작하십시오:
sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environmentsystemctl show은 systemd가 프로세스에 전달할 내용을 출력합니다. 해당 변수가 보이지 않는다면 drop-in 파일이 저장되지 않았거나 daemon-reload 단계를 건너뛴 것입니다. 서버 측에서도 직접 확인하십시오:
journalctl -u ollama --no-pager | grep "server config" | tail -1Ollama는 시작 시 server config이라는 메시지가 포함된 줄에 전체 환경 변수를 기록합니다. 이 맵이 실제 적용된 값입니다. 변수가 적용되었는지 확인하는 가장 빠른 방법입니다.
이미 로드된 모델은 시작 시점에 슬롯 개수가 고정되므로, 위와 같이 재시작해야만 모든 모델이 언로드됩니다. 이후 다음 요청 시 새로운 설정으로 모델이 다시 로드되며, 이때 로드 시간이 한 번 발생합니다. 그 후 모델이 메모리에 상주하는 시간은 별도의 설정 항목이며, 요청 간 Ollama 모델 상주 유지에서 다룹니다.
클라이언트 입장에서 본 처리됨, 대기 중, 거부됨의 의미
여러 요청을 동시에 보내고 시간을 측정합니다. 다음 명령은 8개의 스트리밍 요청을 병렬로 실행하고 각 요청의 상태와 소요 시간을 출력합니다.
for i in $(seq 1 8); do
curl -s -o /dev/null \
-w "req$i http=%{http_code} ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
http://127.0.0.1:11434/api/generate \
-d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":true}' &
done
waitttfb은 스트림의 첫 번째 바이트가 도착하기까지 걸리는 시간입니다. 첫 번째 스트리밍 청크에 첫 번째 토큰이 포함되므로, 이는 TTFT(Time To First Token)와 거의 같습니다.
병렬 처리됨. 모든 요청이 비슷한 ttfb를 보고하며, total은 모든 요청에 대해 동시에 상승합니다. GPU는 실행 중인 슬롯 간에 공유되므로, 각 답변은 단독으로 실행될 때보다 느려지지만 분당 처리되는 답변의 수는 증가합니다. 이것이 바로 OLLAMA_NUM_PARALLEL를 높일 때 얻게 되는 운영 방식입니다.
대기 중. 첫 번째 요청들은 빠르게 응답하지만, 이후 요청들은 긴 ttfb를 보인 뒤 정상적으로 생성됩니다. 이 지연은 모델이 아닌 대기열(queue)로 인한 것입니다. 채팅창을 보는 사용자는 긴 공백을 경험한 뒤 텍스트가 최고 속도로 출력되는 것을 보게 됩니다. 시작은 느리고 이후 빨라지는 이러한 형태는 GPU 과부하가 아닌 대기열의 전형적인 특징입니다.
거부됨. 클라이언트는 즉시 http=503 응답을 받으며, 본문 내용은 다음과 같습니다.
{"error":"server busy, please try again. maximum pending requests exceeded"}이 메시지는 요청이 도착한 시점에 대기열이 가득 찼음을 의미합니다. VRAM이나 모델과는 아무런 관련이 없습니다.
한 가지 솔직한 제한 사항으로, Ollama는 대기열 깊이(queue depth)를 공개하지 않습니다. ollama ps 및 /api/ps 엔드포인트는 대기 중인 요청이 아니라 로드된 모델을 보고합니다. 따라서 클라이언트 측에서 첫 번째 바이트까지의 시간을 관찰하거나, 앞단에 위치한 서비스에서 503 응답 횟수를 집계하여 대기열 상태를 측정해야 합니다.
MAX_QUEUE 값을 작게 설정하는 것이 더 나은 이유
512 정도의 큐 크기는 넉넉해 보이지만, 단일 슬롯에서는 거의 쓸모가 없습니다. 300번째 요청은 앞선 299개의 작업이 완료될 때까지 기다려야 합니다. 이는 아무리 빨라도 수 분이 걸리는 시간입니다. 모든 HTTP 클라이언트는 그보다 훨씬 전에 연결을 끊으므로, 호출자는 클라이언트 측 타임아웃을 경험하게 됩니다. 이는 원인 파악에 아무런 도움이 되지 않으며, 모니터링 시스템도 경고를 발생시킬 수 없습니다.
큐 크기를 클라이언트의 타임아웃 시간 내에 서버가 처리할 수 있는 수준으로 설정하면, 초과되는 요청은 즉시 503 오류를 반환하게 됩니다. 503 오류는 유용합니다. 리버스 프록시는 이를 재시도할 수 있고, 클라이언트는 대기 시간을 가질 수 있으며, 대시보드는 이를 집계하고, 관리자는 상황을 파악할 수 있습니다. 이 수치는 직접 측정한 데이터를 바탕으로 계산하십시오. 만약 작업 하나를 생성하는 데 10초가 걸리고 클라이언트의 대기 시간이 60초라면, 슬롯당 약 6개의 요청만 처리할 수 있습니다. 따라서 이보다 훨씬 깊은 큐를 설정하는 것은 타임아웃만 유발할 뿐입니다.
Ollama 앞에 큐를 두어야 하는 경우
내장된 큐는 선입선출(FIFO) 방식이며 호출자가 누구인지 식별하지 못합니다. 단일 애플리케이션이 단일 서버와 통신하는 환경에서는 이것으로 충분하며, 인프라를 추가하는 것은 오히려 장애 지점만 늘릴 뿐입니다. 다음 중 하나에 해당한다면 앞단에 별도의 장치를 두어야 합니다.
- 우선순위가 필요한 경우: 대화형 채팅이 일괄 요약 작업 뒤에서 대기해서는 안 됩니다. Ollama의 큐에는 우선순위 기능이 없으므로, 일괄 작업은 외부에서 보관하다가 천천히 입력해야 합니다.
- 공정성이 필요한 경우: 특정 클라이언트 하나가 큐를 가득 채우면 다른 모든 사용자는 503 오류를 받게 됩니다.
- 재시작 후에도 작업이 유지되어야 하는 경우: 큐는 서버 메모리에 상주합니다. Ollama를 재시작하면 대기 중인 모든 요청이 사라집니다.
- 지수 백오프(backoff)를 포함한 실제 재시도 기능이 필요하고, 이를 나중에 확인할 수 있도록 기록해야 하는 경우.
가벼운 해결책은 리버스 프록시를 사용하는 것입니다. nginx에서 limit_conn는 동시 연결 수를 제한하고, limit_req은 클라이언트당 요청 유입 속도를 제한하므로, 초과된 요청은 프록시에서 거부되어 Ollama의 큐까지 도달하지 않습니다. 무거운 해결책은 Ollama를 호출하는 워커(worker) 앞단에 데이터베이스와 함께 작업 큐를 두는 방식이며, 요청이 프로세스 재시작 이후에도 생존해야 할 때 필요합니다. 실제 트래픽에 맞춰 규모를 산정하는 것은 별도의 작업이 필요합니다. 동시 사용자를 위한 자체 호스팅 LLM 계획하기에서 관련 계산법을 다루며, VPS에서 Ollama 실행하기에서는 이러한 변수들이 전제하는 기본 설치 과정을 설명합니다.
정직한 답변이 다른 서버를 가리킬 때
튜닝으로도 극복할 수 없는 한계가 존재합니다. Ollama는 모델을 로드할 때 KV 캐시를 동일한 크기의 고정된 슬롯으로 분할합니다. 유휴 상태인 슬롯의 메모리를 사용 중인 슬롯이 가져다 쓸 수 없으며, 모델을 언로드하지 않고는 슬롯 개수를 변경할 수 없습니다. 이러한 설계는 한 명의 사용자나 소규모 팀, 또는 코딩 에이전트에게는 적합합니다.
다수의 동시 사용자를 위해 구축된 서버는 다르게 작동합니다. 이러한 서버는 요청이 있을 때마다 KV 캐시를 작은 페이지 단위로 할당하고, 이미 실행 중인 배치에 새로 들어온 요청을 추가합니다. 따라서 메모리는 고정된 분할 방식이 아닌 실제 수요에 따라 유연하게 관리됩니다. 만약 단일 GPU에서 대규모 동시 사용자를 처리하는 것이 목표라면, OLLAMA_NUM_PARALLEL의 어떤 값보다 이러한 아키텍처 차이가 훨씬 중요합니다. Ollama와 vLLM 비교 문서를 참고하여 결정을 내리십시오. 단, 원칙적인 이유만으로 서버를 교체하지는 마십시오. 다른 서버를 운영하는 것은 관리 부담을 늘립니다. 만약 트래픽이 소수의 인원 수준이라면, 내장된 동작 방식이 올바른 선택입니다.
자신의 처리량과 첫 토큰 생성 시간 측정하기
초당 토큰 생성(tokens per second) 수치는 다른 사람의 GPU, 모델, 양자화, 컨텍스트 길이, 프롬프트를 기준으로 측정된 값입니다. 이 환경들은 사용자의 환경과 일치하지 않으므로, 읽게 되는 모든 수치는 대략적인 참고 자료로만 활용하고 현재 사용 중인 장비에서 직접 측정해야 합니다.
Ollama는 모든 응답의 마지막 JSON 객체에 타이밍 정보를 포함합니다. eval_count는 생성된 토큰의 개수이며, eval_duration은 토큰 생성에 소요된 시간(나노초 단위)입니다.
sudo apt install -y jq
curl -s http://127.0.0.1:11434/api/generate \
-d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":false}' \
| jq '{prompt_eval_count, eval_count, eval_duration, tokens_per_second: (.eval_count / (.eval_duration / 1000000000))}'이 명령을 슬롯 1개로 실행한 뒤, 실제로 예상하는 동시성 수준에서 다시 실행하여 사용자의 만족도를 결정짓는 두 가지 지표인 '첫 토큰 생성 시간'과 '요청당 초당 토큰 수'를 비교하십시오. 슬롯이 추가될수록 요청당 처리량은 항상 감소합니다. 중요한 것은 그 감소 폭이 사용자가 수용할 수 있는 범위를 벗어나는지 확인하는 것입니다. 로컬 LLM에서 초당 토큰 수 측정하기 문서에서는 실행 간 프롬프트를 일정하게 유지하는 방법을 포함하여 측정 방식을 더 자세히 다룹니다.
공개 엔드포인트와 넉넉한 대기열은 서비스 거부 공격의 표적입니다
OLLAMA_HOST=0.0.0.0:11434를 설정하면 API가 모든 인터페이스에 노출되며, Ollama에는 내장된 인증 기능이 없습니다. 기본 대기열이 설정된 상태로 엔드포인트를 개방하면, 이를 발견한 누구나 512개의 대기 요청을 보낼 수 있습니다. 공격자는 긴 프롬프트, 로그인 생략, 속도 제한 없음, 비용 부담 없음을 이용해 거의 비용을 들이지 않고 대기열을 가득 채울 수 있습니다. 이 경우 실제 사용자는 503 응답을 받거나 긴 대기 시간을 겪게 되며, 서버는 계속해서 부하 상태에 놓입니다.
리스너를 루프백(loopback) 주소로 유지하고 SSH 터널이나 사설 네트워크를 통해 접근하거나, 앞단에 인증 및 속도 제한 기능을 배치하십시오. Ollama API 엔드포인트 보안 설정에서 두 가지 방법을 모두 다룹니다. 대기열 길이는 용량 설정일 뿐 보안을 보장하지 않으므로, 보안 조치를 완료한 후에 대기열을 조정하십시오.
FAQ
두 번째 Ollama 요청이 왜 첫 번째 요청이 끝날 때까지 대기합니까?
OLLAMA_NUM_PARALLEL의 기본값이 1로 설정되어 있어, 로드된 모델은 한 번에 하나의 요청만 처리하고 나머지는 순서대로 대기하기 때문입니다. 대기 중인 요청은 HTTP 연결을 유지한 채 슬롯이 비워질 때까지 데이터를 전송하지 않으며, 클라이언트 측에서는 모델이 느린 것처럼 보입니다. 응답 시간을 확인해 보면 대기열의 특성을 알 수 있습니다. 긴 일시 정지 후 텍스트가 최고 속도로 출력된다면 대기열에 걸린 것이고, 첫 토큰부터 느리게 출력된다면 모델 자체가 느린 것입니다. systemd 드롭인 파일을 사용하여 슬롯 수를 늘린 뒤 서비스를 재시작하십시오.
"server busy, please try again. maximum pending requests exceeded"라는 메시지는 무엇을 의미합니까?
이는 Ollama의 대기열 오버플로우 오류로, HTTP 503 상태 코드로 반환됩니다. 대기 중인 요청 수가 OLLAMA_MAX_QUEUE(기본값 512)에 도달하여, 새로운 요청이 대기열에 추가되지 않고 거부된 것입니다. 이는 메모리 오류나 모델 오류가 아닙니다. 대기열 크기를 늘려도 결국 같은 거부 메시지를 받기까지 대기 시간만 길어질 뿐입니다. 근본적인 해결책은 VRAM 여유가 있다면 슬롯을 늘리거나, 유입되는 부하를 줄이거나, 앞단에 재시도 및 우선순위 처리가 가능한 대기열을 두는 것입니다.
OLLAMA_NUM_PARALLEL을 높이면 Ollama가 더 빨라집니까?
아니요. 더 많은 요청을 동시에 처리할 수 있게 되지만, 하나의 GPU를 공유하므로 각 요청의 처리 속도는 단독으로 실행할 때보다 느려집니다. 또한 Ollama는 전체 컨텍스트 길이에 슬롯 수를 곱한 만큼의 컨텍스트를 확보하여 러너를 시작하므로 KV 캐시 사용량도 증가합니다. 결과물이 VRAM에 들어가지 않게 되면 Ollama는 레이어를 CPU로 밀어내며, 이로 인해 단일 요청을 포함한 모든 요청의 속도가 저하됩니다. 변경 후 ollama ps을 확인하여 PROCESSOR 열이 여전히 100% GPU로 표시되는지 확인하십시오.
이 변수들을 변경한 후 Ollama를 재시작해야 합니까?
네. 서버는 시작 시점에 변수를 읽으며, 실행 중인 모델은 시작 시 고정된 슬롯 수를 유지합니다. sudo systemctl edit ollama.service으로 드롭인 파일을 편집한 다음, sudo systemctl daemon-reload과 sudo systemctl restart ollama를 실행하십시오. systemctl show ollama --property=Environment으로 확인한 뒤, 서버가 실제로 로드한 환경 변수를 나열하는 journalctl -u ollama 파일의 server config 라인을 확인하십시오.
병렬 슬롯은 몇 개로 설정해야 합니까?
1부터 시작하여 한 단계씩 늘리십시오. 각 단계마다 Ollama를 재시작하고, 모델을 로드하기 위해 요청을 하나 보낸 뒤 ollama ps을 실행하십시오. PROCESSOR이 여전히 100% GPU로 표시되고 SIZE 열에 서비스하려는 최대 컨텍스트 길이를 위한 여유 공간이 남아 있는 마지막 값에서 멈추십시오. 그 후 실제 동시성 환경에서 첫 토큰까지의 시간과 초당 토큰 수를 측정하고, 요청당 속도가 사용자가 허용하는 수준 이하로 떨어지면 한 단계 낮추십시오.